fix: cache node (#752)

This commit is contained in:
lisonge 2024-10-21 17:44:06 +08:00
parent db60d266fc
commit cae215cab9
3 changed files with 71 additions and 40 deletions

View File

@ -26,6 +26,17 @@ private fun List<Any>.getInt(i: Int = 0) = get(i) as Int
private const val MAX_CACHE_SIZE = MAX_DESCENDANTS_SIZE private const val MAX_CACHE_SIZE = MAX_DESCENDANTS_SIZE
private val AccessibilityNodeInfo?.notExpiredNode: AccessibilityNodeInfo?
get() {
if (this != null) {
val expiryMillis = if (text == null) 2000L else 1000L
if (isExpired(expiryMillis)) {
return null
}
}
return this
}
class A11yContext( class A11yContext(
private val disableInterrupt: Boolean = false private val disableInterrupt: Boolean = false
) { ) {
@ -35,6 +46,7 @@ class A11yContext(
private var parentCache = LruCache<AccessibilityNodeInfo, AccessibilityNodeInfo>(MAX_CACHE_SIZE) private var parentCache = LruCache<AccessibilityNodeInfo, AccessibilityNodeInfo>(MAX_CACHE_SIZE)
var rootCache: AccessibilityNodeInfo? = null var rootCache: AccessibilityNodeInfo? = null
private var lastClearTime = 0L
private fun clearNodeCache(t: Long = System.currentTimeMillis()) { private fun clearNodeCache(t: Long = System.currentTimeMillis()) {
if (META.debuggable) { if (META.debuggable) {
val sizeList = listOf(childCache.size(), parentCache.size(), indexCache.size()) val sizeList = listOf(childCache.size(), parentCache.size(), indexCache.size())
@ -59,18 +71,13 @@ class A11yContext(
} }
} }
private var lastClearTime = 0L
private var lastAppChangeTime = appChangeTime private var lastAppChangeTime = appChangeTime
private fun clearNodeCacheIfTimeout() { fun clearOldAppNodeCache() {
if (appChangeTime != lastAppChangeTime) { if (appChangeTime != lastAppChangeTime) {
lastAppChangeTime = appChangeTime lastAppChangeTime = appChangeTime
clearNodeCache() clearNodeCache()
return return
} }
val t = System.currentTimeMillis()
if (t - lastClearTime > 30_000L) {
clearNodeCache(t)
}
} }
var currentRule: ResolvedRule? = null var currentRule: ResolvedRule? = null
@ -84,6 +91,7 @@ class A11yContext(
if (interruptInnerKey == interruptKey) return if (interruptInnerKey == interruptKey) return
interruptInnerKey = interruptKey interruptInnerKey = interruptKey
val rule = currentRule ?: return val rule = currentRule ?: return
if (!activityRuleFlow.value.activePriority) return
if (!activityRuleFlow.value.currentRules.any { it === rule }) return if (!activityRuleFlow.value.currentRules.any { it === rule }) return
if (rule.isPriority()) return if (rule.isPriority()) return
if (META.debuggable) { if (META.debuggable) {
@ -99,12 +107,12 @@ class A11yContext(
private fun getA11Child(node: AccessibilityNodeInfo, index: Int): AccessibilityNodeInfo? { private fun getA11Child(node: AccessibilityNodeInfo, index: Int): AccessibilityNodeInfo? {
guardInterrupt() guardInterrupt()
return node.getChild(index) return node.getChild(index)?.apply { setGeneratedTime() }
} }
private fun getA11Parent(node: AccessibilityNodeInfo): AccessibilityNodeInfo? { private fun getA11Parent(node: AccessibilityNodeInfo): AccessibilityNodeInfo? {
guardInterrupt() guardInterrupt()
return node.parent return node.parent?.apply { setGeneratedTime() }
} }
private fun getA11ByText( private fun getA11ByText(
@ -112,7 +120,9 @@ class A11yContext(
value: String value: String
): List<AccessibilityNodeInfo> { ): List<AccessibilityNodeInfo> {
guardInterrupt() guardInterrupt()
return node.findAccessibilityNodeInfosByText(value) return node.findAccessibilityNodeInfosByText(value).apply {
forEach { it.setGeneratedTime() }
}
} }
private fun getA11ById( private fun getA11ById(
@ -120,7 +130,9 @@ class A11yContext(
value: String value: String
): List<AccessibilityNodeInfo> { ): List<AccessibilityNodeInfo> {
guardInterrupt() guardInterrupt()
return node.findAccessibilityNodeInfosByViewId(value) return node.findAccessibilityNodeInfosByViewId(value).apply {
forEach { it.setGeneratedTime() }
}
} }
private fun getFastQueryNodes( private fun getFastQueryNodes(
@ -135,20 +147,45 @@ class A11yContext(
} }
private fun getCacheRoot(node: AccessibilityNodeInfo? = null): AccessibilityNodeInfo? { private fun getCacheRoot(node: AccessibilityNodeInfo? = null): AccessibilityNodeInfo? {
if (rootCache == null) { if (rootCache.notExpiredNode == null) {
rootCache = getA11Root() rootCache = getA11Root()
} }
if (node == rootCache) return null if (node == rootCache) return null
return rootCache return rootCache
} }
private fun getCacheParent(node: AccessibilityNodeInfo): AccessibilityNodeInfo? {
if (getCacheRoot() == node) {
return null
}
parentCache[node].notExpiredNode?.let { return it }
return getA11Parent(node).apply {
if (this != null) {
parentCache[node] = this
} else {
rootCache = node
}
}
}
private fun getCacheChild(node: AccessibilityNodeInfo, index: Int): AccessibilityNodeInfo? {
if (index !in 0 until node.childCount) {
return null
}
return childCache[node to index].notExpiredNode ?: getA11Child(node, index)?.also { child ->
indexCache[child] = index
parentCache[child] = node
childCache[node to index] = child
}
}
private fun getPureIndex(node: AccessibilityNodeInfo): Int? { private fun getPureIndex(node: AccessibilityNodeInfo): Int? {
return indexCache[node] return indexCache[node]
} }
private fun getCacheIndex(node: AccessibilityNodeInfo): Int { private fun getCacheIndex(node: AccessibilityNodeInfo): Int {
indexCache[node]?.let { return it } indexCache[node]?.let { return it }
getCacheParent(node)?.let(::getCacheChildren)?.forEachIndexed { index, child -> getCacheChildren(getCacheParent(node)).forEachIndexed { index, child ->
if (child == node) { if (child == node) {
indexCache[node] = index indexCache[node] = index
return index return index
@ -157,20 +194,6 @@ class A11yContext(
return 0 return 0
} }
private fun getCacheParent(node: AccessibilityNodeInfo): AccessibilityNodeInfo? {
if (rootCache == node) {
return null
}
parentCache[node]?.let { return it }
return getA11Parent(node).apply {
if (this != null) {
parentCache[node] = this
} else {
rootCache = node
}
}
}
/** /**
* 在无缓存时, 此方法小概率造成无限节点片段,底层原因未知 * 在无缓存时, 此方法小概率造成无限节点片段,底层原因未知
* *
@ -191,18 +214,8 @@ class A11yContext(
return depth return depth
} }
private fun getCacheChild(node: AccessibilityNodeInfo, index: Int): AccessibilityNodeInfo? { private fun getCacheChildren(node: AccessibilityNodeInfo?): Sequence<AccessibilityNodeInfo> {
if (index !in 0 until node.childCount) { if (node == null) return emptySequence()
return null
}
return childCache[node to index] ?: getA11Child(node, index)?.also { child ->
indexCache[child] = index
parentCache[child] = node
childCache[node to index] = child
}
}
private fun getCacheChildren(node: AccessibilityNodeInfo): Sequence<AccessibilityNodeInfo> {
return sequence { return sequence {
repeat(node.childCount.coerceAtMost(MAX_CHILD_SIZE)) { index -> repeat(node.childCount.coerceAtMost(MAX_CHILD_SIZE)) { index ->
val child = getCacheChild(node, index) ?: return@sequence val child = getCacheChild(node, index) ?: return@sequence
@ -491,7 +504,6 @@ class A11yContext(
rule: ResolvedRule, rule: ResolvedRule,
node: AccessibilityNodeInfo, node: AccessibilityNodeInfo,
): AccessibilityNodeInfo? { ): AccessibilityNodeInfo? {
clearNodeCacheIfTimeout()
currentRule = rule currentRule = rule
try { try {
val queryNode = if (rule.matchRoot) { val queryNode = if (rule.matchRoot) {

View File

@ -255,6 +255,7 @@ private fun A11yService.useMatchRule() {
} }
} }
var lastNodeUsed = false var lastNodeUsed = false
a11yContext.clearOldAppNodeCache()
for (rule in activityRule.priorityRules) { // 规则数量有可能过多导致耗时过长 for (rule in activityRule.priorityRules) { // 规则数量有可能过多导致耗时过长
if (delayRule != null && delayRule !== rule) continue if (delayRule != null && delayRule !== rule) continue
val statusCode = rule.status val statusCode = rule.status

View File

@ -23,6 +23,8 @@ val AccessibilityService.safeActiveWindow: AccessibilityNodeInfo?
// java.lang.SecurityException: Call from user 0 as user -2 without permission INTERACT_ACROSS_USERS or INTERACT_ACROSS_USERS_FULL not allowed. // java.lang.SecurityException: Call from user 0 as user -2 without permission INTERACT_ACROSS_USERS or INTERACT_ACROSS_USERS_FULL not allowed.
rootInActiveWindow.apply { rootInActiveWindow.apply {
a11yContext.rootCache = this a11yContext.rootCache = this
}?.apply {
setGeneratedTime()
} }
// 在主线程调用会阻塞界面导致卡顿 // 在主线程调用会阻塞界面导致卡顿
} catch (e: Exception) { } catch (e: Exception) {
@ -40,7 +42,9 @@ val AccessibilityEvent.safeSource: AccessibilityNodeInfo?
} else { } else {
try { try {
// 原因未知, 仍然报错 Cannot perform this action on a not sealed instance. // 原因未知, 仍然报错 Cannot perform this action on a not sealed instance.
source source?.apply {
setGeneratedTime()
}
} catch (_: Exception) { } catch (_: Exception) {
null null
} }
@ -64,6 +68,20 @@ fun AccessibilityNodeInfo.getVid(): CharSequence? {
const val MAX_CHILD_SIZE = 512 const val MAX_CHILD_SIZE = 512
const val MAX_DESCENDANTS_SIZE = 4096 const val MAX_DESCENDANTS_SIZE = 4096
private const val A11Y_NODE_TIME_KEY = "generatedTime"
fun AccessibilityNodeInfo.setGeneratedTime() {
extras.putLong(A11Y_NODE_TIME_KEY, System.currentTimeMillis())
}
fun AccessibilityNodeInfo.isExpired(expiryMillis: Long): Boolean {
val generatedTime = extras.getLong(A11Y_NODE_TIME_KEY, -1)
if (generatedTime == -1L) {
setGeneratedTime()
return false
}
return (System.currentTimeMillis() - generatedTime) > expiryMillis
}
private val typeInfo by lazy { initDefaultTypeInfo().globalType } private val typeInfo by lazy { initDefaultTypeInfo().globalType }
fun Selector.checkSelector(): String? { fun Selector.checkSelector(): String? {