Skip to content

Commit 9c20375

Browse files
author
1483232332@qq.com
committed
feat(web): 增强截图和节点树保存功能
- 在 ASJavascriptInterfaceAsync 中新增 takeScreenshotToFile 方法,支持全屏和节点截图保存 - 实现节点树 JSON 保存功能,新增 saveRootNodeTreeJson 方法 - 更新 HttpJavascriptInterface 支持多文件上传,优化表单字段处理 - 在 ASWebView 中注册 ImageUtilsJavascriptInterface 以支持图像处理功能 - 调整 CallMethod 常量定义,添加相关方法标识
1 parent c0cbc1e commit 9c20375

File tree

7 files changed

+1376
-20
lines changed

7 files changed

+1376
-20
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ Copyright © 2025 [ven-coder][profile-link]
190190
[issues-link]: https://github.com/ven-coder/Assists/issues
191191
[profile-link]: https://github.com/ven-coder
192192
[demo-download]: https://www.pgyer.com/1zaijG
193-
[docs-link]: https://github.com/ven-coder/Assists/tree/master/documents
193+
[docs-link]: https://ahcirffybg.feishu.cn/wiki/space/7561797853589553156?ccm_open_type=lark_wiki_spaceLink&open_tab_from=wiki_home
194194
[assistsx-js-link]: https://github.com/ven-coder/assistsx-js
195195
[api-reference]: https://github.com/ven-coder/Assists/blob/master/API_REFERENCE.md
196196
[changelog]: https://github.com/ven-coder/Assists/releases

assists-web/src/main/java/com/ven/assists/web/ASJavascriptInterfaceAsync.kt

Lines changed: 168 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import java.nio.charset.StandardCharsets
7272
import java.util.concurrent.TimeUnit
7373
import androidx.core.net.toUri
7474
import com.blankj.utilcode.util.ActivityUtils
75+
import com.blankj.utilcode.util.ImageUtils
7576
import com.ven.assists.utils.AudioPlayerUtil
7677
import com.ven.assists.utils.ContactsUtil
7778
import com.ven.assists.utils.FileDownloadUtil
@@ -566,7 +567,7 @@ class ASJavascriptInterfaceAsync(val webView: WebView) {
566567
CompressFormat.PNG -> "png"
567568
CompressFormat.JPEG -> "jpg"
568569
CompressFormat.WEBP -> "webp"
569-
else -> {}
570+
else -> "png"
570571
}
571572

572573
AssistsWindowManager.hideAll()
@@ -575,13 +576,20 @@ class ASJavascriptInterfaceAsync(val webView: WebView) {
575576
// 构建文件对象
576577
val file = filePath?.let { File(it) } ?: File(PathUtils.getInternalAppFilesPath() + "/${System.currentTimeMillis()}.$fileExtension")
577578

578-
// 全屏截图保存
579-
val savedFile = AssistsCore.takeScreenshotSave(file = file, format = format)
579+
// 如果提供了节点,则保存节点截图;否则保存全屏截图
580+
val savedFile = if (request.node?.nodeId.isNullOrEmpty()) {
581+
// 全屏截图保存
582+
AssistsCore.takeScreenshotSave(file = file, format = format)
583+
} else {
584+
// 节点截图保存
585+
NodeCacheManager.get(request.node?.nodeId ?: "")?.takeScreenshotSave(file = file, format = format)
586+
}
580587

581588
AssistsWindowManager.showTop()
582589

583590
val response = request.createResponse(
584591
if (savedFile == null) -1 else 0,
592+
message = if (savedFile == null) "截图保存失败" else "截图保存成功",
585593
data = JsonObject().apply {
586594
addProperty("file", savedFile?.path ?: "")
587595
}
@@ -590,6 +598,137 @@ class ASJavascriptInterfaceAsync(val webView: WebView) {
590598
}
591599
}
592600

601+
CallMethod.takeScreenshotToFile -> {
602+
val overlayHiddenScreenshotDelayMillis = request.arguments?.get("overlayHiddenScreenshotDelayMillis")?.asLong ?: 250
603+
val formatStr = request.arguments?.get("format")?.asString ?: "PNG"
604+
val baseFilePath = request.arguments?.get("filePath")?.asString
605+
606+
// 解析格式参数
607+
val format = when (formatStr.uppercase()) {
608+
"PNG" -> CompressFormat.PNG
609+
"JPEG", "JPG" -> CompressFormat.JPEG
610+
"WEBP" -> CompressFormat.WEBP
611+
else -> CompressFormat.PNG
612+
}
613+
614+
// 获取文件扩展名
615+
val fileExtension = when (format) {
616+
CompressFormat.PNG -> "png"
617+
CompressFormat.JPEG -> "jpg"
618+
CompressFormat.WEBP -> "webp"
619+
else -> "png"
620+
}
621+
622+
AssistsWindowManager.hideAll()
623+
delay(overlayHiddenScreenshotDelayMillis)
624+
625+
val filePaths = arrayListOf<String>()
626+
627+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
628+
// Android R 及以上版本
629+
val screenshot = AssistsCore.takeScreenshot()
630+
AssistsWindowManager.showTop()
631+
632+
if (request.nodes.isNullOrEmpty()) {
633+
// 如果没有指定节点,保存全屏截图
634+
val file = baseFilePath?.let { File(it) } ?: File(PathUtils.getInternalAppFilesPath() + "/screenshot_${System.currentTimeMillis()}.$fileExtension")
635+
file.parentFile?.mkdirs()
636+
screenshot?.let {
637+
val success = ImageUtils.save(it, file, format)
638+
if (success) {
639+
filePaths.add(file.absolutePath)
640+
}
641+
it.recycle()
642+
}
643+
} else {
644+
// 保存多个节点截图
645+
request.nodes?.forEachIndexed { index, nodeRequest ->
646+
val bitmap = NodeCacheManager.get(nodeRequest.nodeId)?.takeScreenshot(screenshot = screenshot)
647+
bitmap?.let {
648+
val file = if (baseFilePath != null && request.nodes?.size == 1) {
649+
// 如果只有一个节点且指定了路径,使用指定路径
650+
File(baseFilePath)
651+
} else {
652+
// 多个节点时,使用索引生成文件名
653+
val fileName = if (baseFilePath != null) {
654+
val baseFile = File(baseFilePath)
655+
val nameWithoutExt = baseFile.nameWithoutExtension
656+
val parent = baseFile.parent ?: PathUtils.getInternalAppFilesPath()
657+
File(parent, "${nameWithoutExt}_${index}.$fileExtension")
658+
} else {
659+
File(PathUtils.getInternalAppFilesPath() + "/screenshot_${System.currentTimeMillis()}_${index}.$fileExtension")
660+
}
661+
fileName
662+
}
663+
file.parentFile?.mkdirs()
664+
val success = ImageUtils.save(it, file, format)
665+
if (success) {
666+
filePaths.add(file.absolutePath)
667+
}
668+
it.recycle()
669+
}
670+
}
671+
screenshot?.recycle()
672+
}
673+
} else {
674+
// Android R 以下版本使用 MPManager
675+
val takeScreenshot2Bitmap = MPManager.takeScreenshot2Bitmap()
676+
AssistsWindowManager.showTop()
677+
678+
takeScreenshot2Bitmap?.let {
679+
if (request.nodes.isNullOrEmpty()) {
680+
// 如果没有指定节点,保存全屏截图
681+
val file = baseFilePath?.let { File(it) } ?: File(PathUtils.getInternalAppFilesPath() + "/screenshot_${System.currentTimeMillis()}.$fileExtension")
682+
file.parentFile?.mkdirs()
683+
val success = ImageUtils.save(it, file, format)
684+
if (success) {
685+
filePaths.add(file.absolutePath)
686+
}
687+
} else {
688+
// 保存多个节点截图
689+
request.nodes?.forEachIndexed { index, nodeRequest ->
690+
val bitmap = NodeCacheManager.get(nodeRequest.nodeId)?.getBitmap(screenshot = it)
691+
bitmap?.let { nodeBitmap ->
692+
val file = if (baseFilePath != null && request.nodes?.size == 1) {
693+
// 如果只有一个节点且指定了路径,使用指定路径
694+
File(baseFilePath)
695+
} else {
696+
// 多个节点时,使用索引生成文件名
697+
val fileName = if (baseFilePath != null) {
698+
val baseFile = File(baseFilePath)
699+
val nameWithoutExt = baseFile.nameWithoutExtension
700+
val parent = baseFile.parent ?: PathUtils.getInternalAppFilesPath()
701+
File(parent, "${nameWithoutExt}_${index}.$fileExtension")
702+
} else {
703+
File(PathUtils.getInternalAppFilesPath() + "/screenshot_${System.currentTimeMillis()}_${index}.$fileExtension")
704+
}
705+
fileName
706+
}
707+
file.parentFile?.mkdirs()
708+
val success = ImageUtils.save(nodeBitmap, file, format)
709+
if (success) {
710+
filePaths.add(file.absolutePath)
711+
}
712+
nodeBitmap.recycle()
713+
}
714+
}
715+
}
716+
it.recycle()
717+
}
718+
}
719+
720+
val response = request.createResponse(
721+
if (filePaths.isEmpty()) -1 else 0,
722+
message = if (filePaths.isEmpty()) "截图保存失败" else "截图保存成功",
723+
data = JsonObject().apply {
724+
add("files", JsonArray().apply {
725+
filePaths.forEach { add(it) }
726+
})
727+
}
728+
)
729+
response
730+
}
731+
593732
CallMethod.performLinearGesture -> {
594733
val startPoint = request.arguments?.get("startPoint")?.asJsonObject ?: JsonObject()
595734
val endPoint = request.arguments?.get("endPoint")?.asJsonObject ?: JsonObject()
@@ -1082,6 +1221,32 @@ class ASJavascriptInterfaceAsync(val webView: WebView) {
10821221
}
10831222
}
10841223

1224+
CallMethod.saveRootNodeTreeJson -> {
1225+
try {
1226+
val filePath = request.arguments?.get("filePath")?.asString
1227+
val prettyPrint = request.arguments?.get("prettyPrint")?.asBoolean ?: true
1228+
1229+
// 构建文件对象
1230+
val file = filePath?.let { File(it) } ?: File(PathUtils.getInternalAppFilesPath() + "/node_tree_${System.currentTimeMillis()}.json")
1231+
1232+
// 保存节点树JSON到文件
1233+
val savedFile = AssistsCore.saveRootNodeTreeJson(file = file, prettyPrint = prettyPrint)
1234+
1235+
val response = request.createResponse(
1236+
if (savedFile == null) -1 else 0,
1237+
message = if (savedFile == null) "保存节点树JSON失败" else "保存成功",
1238+
data = JsonObject().apply {
1239+
addProperty("file", savedFile?.path ?: "")
1240+
}
1241+
)
1242+
response
1243+
} catch (e: Exception) {
1244+
LogUtils.e(e)
1245+
val response = request.createResponse(-1, message = "Error: ${e.message}", data = JsonObject())
1246+
response
1247+
}
1248+
}
1249+
10851250
else -> {
10861251
request.createResponse(-1, message = "方法未支持")
10871252
}

assists-web/src/main/java/com/ven/assists/web/ASWebView.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.ven.assists.web.filesystem.fileio.FileIOJavascriptInterface
2424
import com.ven.assists.web.filesystem.fileutils.FileUtilsJavascriptInterface
2525
import com.ven.assists.web.network.HttpJavascriptInterface
2626
import com.ven.assists.web.ime.ImeJavascriptInterface
27+
import com.ven.assists.web.imageutils.ImageUtilsJavascriptInterface
2728
import kotlinx.coroutines.CoroutineScope
2829
import kotlinx.coroutines.Dispatchers
2930
import kotlinx.coroutines.Job
@@ -119,6 +120,7 @@ open class ASWebView @JvmOverloads constructor(
119120
val fileUtilsJavascriptInterface = FileUtilsJavascriptInterface(webView = this)
120121
val httpJavascriptInterface = HttpJavascriptInterface(webView = this)
121122
val imeJavascriptInterface = ImeJavascriptInterface(webView = this)
123+
val imageUtilsJavascriptInterface = ImageUtilsJavascriptInterface(webView = this)
122124

123125

124126
val assistsServiceListener = object : AssistsServiceListener {
@@ -217,6 +219,7 @@ open class ASWebView @JvmOverloads constructor(
217219
addJavascriptInterface(fileUtilsJavascriptInterface, "assistsxFileUtils")
218220
addJavascriptInterface(httpJavascriptInterface, "assistsxHttp")
219221
addJavascriptInterface(imeJavascriptInterface, "assistsxIme")
222+
addJavascriptInterface(imageUtilsJavascriptInterface, "assistsxImageUtils")
220223
AssistsService.listeners.add(assistsServiceListener)
221224
}
222225

assists-web/src/main/java/com/ven/assists/web/CallMethod.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ object CallMethod {
3434
const val getAppScreenSize = "getAppScreenSize"
3535
const val takeScreenshot = "takeScreenshot"
3636
const val takeScreenshotSave = "takeScreenshotSave"
37+
const val takeScreenshotToFile = "takeScreenshotToFile"
3738
const val setOverlayFlags = "setOverlayFlags"
3839
const val scanQR = "scanQR"
3940
const val loadWebViewOverlay = "loadWebViewOverlay"
@@ -76,4 +77,7 @@ object CallMethod {
7677
const val addContact = "addContact"
7778
const val getAllContacts = "getAllContacts"
7879

80+
// 节点树相关方法
81+
const val saveRootNodeTreeJson = "saveRootNodeTreeJson"
82+
7983
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.ven.assists.web.imageutils
2+
3+
/**
4+
* 图片工具相关的方法常量定义
5+
*/
6+
object ImageUtilsCallMethod {
7+
// 获取相关
8+
const val getSize = "getSize"
9+
const val getImageType = "getImageType"
10+
const val isImage = "isImage"
11+
const val getRotateDegree = "getRotateDegree"
12+
13+
// 图片处理相关
14+
const val scale = "scale"
15+
const val clip = "clip"
16+
const val skew = "skew"
17+
const val rotate = "rotate"
18+
const val toRound = "toRound"
19+
const val toRoundCorner = "toRoundCorner"
20+
const val addCornerBorder = "addCornerBorder"
21+
const val addCircleBorder = "addCircleBorder"
22+
const val addReflection = "addReflection"
23+
const val addTextWatermark = "addTextWatermark"
24+
const val addImageWatermark = "addImageWatermark"
25+
const val toAlpha = "toAlpha"
26+
const val toGray = "toGray"
27+
const val fastBlur = "fastBlur"
28+
const val renderScriptBlur = "renderScriptBlur"
29+
const val stackBlur = "stackBlur"
30+
31+
// 压缩相关
32+
const val compressByScale = "compressByScale"
33+
const val compressByQuality = "compressByQuality"
34+
const val compressBySampleSize = "compressBySampleSize"
35+
36+
// 保存相关
37+
const val save = "save"
38+
const val save2Album = "save2Album"
39+
}

0 commit comments

Comments
 (0)