Skip to content

Commit c0cbc1e

Browse files
author
1483232332@qq.com
committed
feat(core): 添加Gson库支持并实现节点树结构的JSON转换功能
- 在build.gradle中引入Gson库以支持JSON处理 - 在AssistsCore.kt中新增NodeBounds和NodeTree数据类,表示节点边界信息和树形结构 - 实现AccessibilityNodeInfo到NodeBounds和NodeTree的转换方法 - 添加获取根节点树的功能,并支持将节点树转换为JSON字符串 - 实现将节点树JSON保存到文件的功能,支持格式化输出选项
1 parent 6433559 commit c0cbc1e

File tree

2 files changed

+164
-0
lines changed

2 files changed

+164
-0
lines changed

assists/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ dependencies {
4141

4242
// OkHttp 网络库
4343
api "com.squareup.okhttp3:okhttp:4.10.0"
44+
45+
// Gson JSON库
46+
api "com.google.code.gson:gson:2.11.0"
4447
}
4548

4649
dokkaHtml {

assists/src/main/java/com/ven/assists/AssistsCore.kt

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ import com.blankj.utilcode.util.SizeUtils
5050
import com.ven.assists.window.AssistsWindowManager.nonTouchableByWrapper
5151
import androidx.core.graphics.createBitmap
5252
import com.blankj.utilcode.util.FileUtils
53+
import com.google.gson.Gson
54+
import com.google.gson.GsonBuilder
5355
import com.ven.assists.utils.BitmapUtils
5456

5557
/**
@@ -1316,4 +1318,163 @@ object AssistsCore {
13161318
Log.d(tag, toString())
13171319
}
13181320
}
1321+
1322+
/**
1323+
* 节点边界信息数据类
1324+
* 包含节点在屏幕中的位置和尺寸信息
1325+
*/
1326+
data class NodeBounds(
1327+
val left: Int,
1328+
val top: Int,
1329+
val right: Int,
1330+
val bottom: Int,
1331+
val width: Int,
1332+
val height: Int,
1333+
val centerX: Int,
1334+
val centerY: Int,
1335+
val exactCenterX: Float,
1336+
val exactCenterY: Float,
1337+
val isEmpty: Boolean,
1338+
)
1339+
1340+
/**
1341+
* 节点树数据类
1342+
* 包含节点的所有属性和子节点列表,用于生成树形结构的JSON
1343+
*/
1344+
data class NodeTree(
1345+
val packageName: String,
1346+
val text: String,
1347+
val des: String,
1348+
val viewId: String,
1349+
val className: String,
1350+
val isScrollable: Boolean,
1351+
val isClickable: Boolean,
1352+
val isEnabled: Boolean,
1353+
val boundsInScreen: NodeBounds,
1354+
val hintText: String,
1355+
val isCheckable: Boolean,
1356+
val isChecked: Boolean,
1357+
val isFocusable: Boolean,
1358+
val isFocused: Boolean,
1359+
val isLongClickable: Boolean,
1360+
val isPassword: Boolean,
1361+
val isSelected: Boolean,
1362+
val isVisibleToUser: Boolean,
1363+
val drawingOrder: Int,
1364+
val children: List<NodeTree>
1365+
)
1366+
1367+
/**
1368+
* 将AccessibilityNodeInfo转换为NodeBounds
1369+
* @return 节点边界信息
1370+
*/
1371+
private fun AccessibilityNodeInfo.toNodeBounds(): NodeBounds {
1372+
val rect = getBoundsInScreen()
1373+
return NodeBounds(
1374+
left = rect.left,
1375+
top = rect.top,
1376+
right = rect.right,
1377+
bottom = rect.bottom,
1378+
width = rect.width(),
1379+
height = rect.height(),
1380+
centerX = rect.centerX(),
1381+
centerY = rect.centerY(),
1382+
exactCenterX = rect.exactCenterX(),
1383+
exactCenterY = rect.exactCenterY(),
1384+
isEmpty = rect.isEmpty
1385+
)
1386+
}
1387+
1388+
/**
1389+
* 将AccessibilityNodeInfo转换为NodeTree(递归包含子节点)
1390+
* @return 节点树对象
1391+
*/
1392+
fun AccessibilityNodeInfo.toNodeTree(): NodeTree {
1393+
val children = mutableListOf<NodeTree>()
1394+
for (i in 0 until childCount) {
1395+
getChild(i)?.let { child ->
1396+
children.add(child.toNodeTree())
1397+
}
1398+
}
1399+
1400+
return NodeTree(
1401+
packageName = packageName?.toString() ?: "",
1402+
text = text?.toString() ?: "",
1403+
des = contentDescription?.toString() ?: "",
1404+
viewId = viewIdResourceName ?: "",
1405+
className = className?.toString() ?: "",
1406+
isScrollable = isScrollable,
1407+
isClickable = isClickable,
1408+
isEnabled = isEnabled,
1409+
boundsInScreen = toNodeBounds(),
1410+
hintText = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) hintText?.toString() ?: "" else "",
1411+
isCheckable = isCheckable,
1412+
isChecked = isChecked,
1413+
isFocusable = isFocusable,
1414+
isFocused = isFocused,
1415+
isLongClickable = isLongClickable,
1416+
isPassword = isPassword,
1417+
isSelected = isSelected,
1418+
isVisibleToUser = isVisibleToUser,
1419+
drawingOrder = drawingOrder,
1420+
children = children
1421+
)
1422+
}
1423+
1424+
/**
1425+
* 获取当前窗口所有节点的树形结构
1426+
* @return 根节点的NodeTree对象,如果获取失败则返回null
1427+
*/
1428+
fun getRootNodeTree(): NodeTree? {
1429+
return AssistsService.instance?.rootInActiveWindow?.toNodeTree()
1430+
}
1431+
1432+
/**
1433+
* 获取当前窗口所有节点的JSON字符串(树形结构)
1434+
* @param prettyPrint 是否格式化输出JSON,默认为false
1435+
* @return JSON字符串,如果获取失败则返回空字符串
1436+
*/
1437+
fun getRootNodeTreeJson(prettyPrint: Boolean = false): String {
1438+
val nodeTree = getRootNodeTree() ?: return ""
1439+
val gson = if (prettyPrint) {
1440+
GsonBuilder().setPrettyPrinting().create()
1441+
} else {
1442+
Gson()
1443+
}
1444+
return gson.toJson(nodeTree)
1445+
}
1446+
1447+
/**
1448+
* 将指定节点及其所有子节点转换为JSON字符串(树形结构)
1449+
* @param prettyPrint 是否格式化输出JSON,默认为false
1450+
* @return JSON字符串
1451+
*/
1452+
fun AccessibilityNodeInfo.toNodeTreeJson(prettyPrint: Boolean = false): String {
1453+
val nodeTree = toNodeTree()
1454+
val gson = if (prettyPrint) {
1455+
GsonBuilder().setPrettyPrinting().create()
1456+
} else {
1457+
Gson()
1458+
}
1459+
return gson.toJson(nodeTree)
1460+
}
1461+
1462+
/**
1463+
* 获取当前窗口所有节点的JSON字符串并保存到文件
1464+
* @param file 保存JSON的文件,默认为应用内部文件路径下的时间戳命名文件
1465+
* @param prettyPrint 是否格式化输出JSON,默认为true
1466+
* @return 保存成功时返回文件对象,失败时返回null
1467+
*/
1468+
fun saveRootNodeTreeJson(
1469+
file: File = File(PathUtils.getInternalAppFilesPath() + "/node_tree_${System.currentTimeMillis()}.json"),
1470+
prettyPrint: Boolean = true
1471+
): File? {
1472+
val json = getRootNodeTreeJson(prettyPrint)
1473+
if (json.isEmpty()) return null
1474+
return runCatching {
1475+
file.parentFile?.mkdirs()
1476+
file.writeText(json)
1477+
file
1478+
}.getOrNull()
1479+
}
13191480
}

0 commit comments

Comments
 (0)