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 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(
private val disableInterrupt: Boolean = false
) {
@ -35,6 +46,7 @@ class A11yContext(
private var parentCache = LruCache<AccessibilityNodeInfo, AccessibilityNodeInfo>(MAX_CACHE_SIZE)
var rootCache: AccessibilityNodeInfo? = null
private var lastClearTime = 0L
private fun clearNodeCache(t: Long = System.currentTimeMillis()) {
if (META.debuggable) {
val sizeList = listOf(childCache.size(), parentCache.size(), indexCache.size())
@ -59,18 +71,13 @@ class A11yContext(
}
}
private var lastClearTime = 0L
private var lastAppChangeTime = appChangeTime
private fun clearNodeCacheIfTimeout() {
fun clearOldAppNodeCache() {
if (appChangeTime != lastAppChangeTime) {
lastAppChangeTime = appChangeTime
clearNodeCache()
return
}
val t = System.currentTimeMillis()
if (t - lastClearTime > 30_000L) {
clearNodeCache(t)
}
}
var currentRule: ResolvedRule? = null
@ -84,6 +91,7 @@ class A11yContext(
if (interruptInnerKey == interruptKey) return
interruptInnerKey = interruptKey
val rule = currentRule ?: return
if (!activityRuleFlow.value.activePriority) return
if (!activityRuleFlow.value.currentRules.any { it === rule }) return
if (rule.isPriority()) return
if (META.debuggable) {
@ -99,12 +107,12 @@ class A11yContext(
private fun getA11Child(node: AccessibilityNodeInfo, index: Int): AccessibilityNodeInfo? {
guardInterrupt()
return node.getChild(index)
return node.getChild(index)?.apply { setGeneratedTime() }
}
private fun getA11Parent(node: AccessibilityNodeInfo): AccessibilityNodeInfo? {
guardInterrupt()
return node.parent
return node.parent?.apply { setGeneratedTime() }
}
private fun getA11ByText(
@ -112,7 +120,9 @@ class A11yContext(
value: String
): List<AccessibilityNodeInfo> {
guardInterrupt()
return node.findAccessibilityNodeInfosByText(value)
return node.findAccessibilityNodeInfosByText(value).apply {
forEach { it.setGeneratedTime() }
}
}
private fun getA11ById(
@ -120,7 +130,9 @@ class A11yContext(
value: String
): List<AccessibilityNodeInfo> {
guardInterrupt()
return node.findAccessibilityNodeInfosByViewId(value)
return node.findAccessibilityNodeInfosByViewId(value).apply {
forEach { it.setGeneratedTime() }
}
}
private fun getFastQueryNodes(
@ -135,20 +147,45 @@ class A11yContext(
}
private fun getCacheRoot(node: AccessibilityNodeInfo? = null): AccessibilityNodeInfo? {
if (rootCache == null) {
if (rootCache.notExpiredNode == null) {
rootCache = getA11Root()
}
if (node == rootCache) return null
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? {
return indexCache[node]
}
private fun getCacheIndex(node: AccessibilityNodeInfo): Int {
indexCache[node]?.let { return it }
getCacheParent(node)?.let(::getCacheChildren)?.forEachIndexed { index, child ->
getCacheChildren(getCacheParent(node)).forEachIndexed { index, child ->
if (child == node) {
indexCache[node] = index
return index
@ -157,20 +194,6 @@ class A11yContext(
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
}
private fun getCacheChild(node: AccessibilityNodeInfo, index: Int): AccessibilityNodeInfo? {
if (index !in 0 until node.childCount) {
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> {
private fun getCacheChildren(node: AccessibilityNodeInfo?): Sequence<AccessibilityNodeInfo> {
if (node == null) return emptySequence()
return sequence {
repeat(node.childCount.coerceAtMost(MAX_CHILD_SIZE)) { index ->
val child = getCacheChild(node, index) ?: return@sequence
@ -491,7 +504,6 @@ class A11yContext(
rule: ResolvedRule,
node: AccessibilityNodeInfo,
): AccessibilityNodeInfo? {
clearNodeCacheIfTimeout()
currentRule = rule
try {
val queryNode = if (rule.matchRoot) {

View File

@ -255,6 +255,7 @@ private fun A11yService.useMatchRule() {
}
}
var lastNodeUsed = false
a11yContext.clearOldAppNodeCache()
for (rule in activityRule.priorityRules) { // 规则数量有可能过多导致耗时过长
if (delayRule != null && delayRule !== rule) continue
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.
rootInActiveWindow.apply {
a11yContext.rootCache = this
}?.apply {
setGeneratedTime()
}
// 在主线程调用会阻塞界面导致卡顿
} catch (e: Exception) {
@ -40,7 +42,9 @@ val AccessibilityEvent.safeSource: AccessibilityNodeInfo?
} else {
try {
// 原因未知, 仍然报错 Cannot perform this action on a not sealed instance.
source
source?.apply {
setGeneratedTime()
}
} catch (_: Exception) {
null
}
@ -64,6 +68,20 @@ fun AccessibilityNodeInfo.getVid(): CharSequence? {
const val MAX_CHILD_SIZE = 512
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 }
fun Selector.checkSelector(): String? {