diff --git a/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt b/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt index da0ddfe..45c6aca 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt @@ -1,8 +1,10 @@ package li.songe.gkd.data import android.accessibilityservice.AccessibilityService +import android.util.Log import android.view.accessibility.AccessibilityNodeInfo import kotlinx.coroutines.Job +import li.songe.gkd.BuildConfig import li.songe.gkd.service.GkdAbService import li.songe.gkd.service.createCacheTransform import li.songe.gkd.service.createNoCacheTransform @@ -133,13 +135,17 @@ sealed class ResolvedRule( else -> true } - private val useCache = (matches + excludeMatches).any { s -> s.useCache } + private val useCache = (matches + excludeMatches + anyMatches).any { s -> s.useCache } private val transform = if (useCache) defaultCacheTransform else defaultTransform fun query( node: AccessibilityNodeInfo?, isRootNode: Boolean, ): AccessibilityNodeInfo? { + val t = System.currentTimeMillis() + if (t - lastCacheTime > MIN_CACHE_INTERVAL) { + clearNodeCache(t) + } val nodeInfo = if (matchRoot) { val rootNode = (if (isRootNode) { node @@ -147,46 +153,41 @@ sealed class ResolvedRule( GkdAbService.service?.safeActiveWindow }) ?: return null rootNode.apply { - transform.cache.parentMap[this] = null + transform.cache.rootNode = this } } else { node } - try { - if (nodeInfo == null) return null - var target: AccessibilityNodeInfo? = null - if (anyMatches.isNotEmpty()) { - for (selector in anyMatches) { - target = nodeInfo.querySelector( - selector, - quickFind, - transform.transform, - isRootNode || matchRoot - ) ?: break - } - if (target == null) return null - } - for (selector in matches) { + if (nodeInfo == null) return null + var target: AccessibilityNodeInfo? = null + if (anyMatches.isNotEmpty()) { + for (selector in anyMatches) { target = nodeInfo.querySelector( selector, quickFind, transform.transform, isRootNode || matchRoot - ) ?: return null + ) ?: break } - for (selector in excludeMatches) { - nodeInfo.querySelector( - selector, - quickFind, - transform.transform, - isRootNode || matchRoot - )?.let { return null } - } - return target - } finally { - defaultTransform.cache.clear() - defaultCacheTransform.cache.clear() + if (target == null) return null } + for (selector in matches) { + target = nodeInfo.querySelector( + selector, + quickFind, + transform.transform, + isRootNode || matchRoot + ) ?: return null + } + for (selector in excludeMatches) { + nodeInfo.querySelector( + selector, + quickFind, + transform.transform, + isRootNode || matchRoot + )?.let { return null } + } + return target } private val performer = ActionPerformer.getAction( @@ -275,5 +276,15 @@ fun getFixActivityIds( private val defaultTransform by lazy { createNoCacheTransform() } private val defaultCacheTransform by lazy { createCacheTransform() } +private var lastCacheTime = 0L +private const val MIN_CACHE_INTERVAL = 2000L - +fun clearNodeCache(t: Long = System.currentTimeMillis()) { + lastCacheTime = t + if (BuildConfig.DEBUG) { + val sizeList = defaultCacheTransform.cache.sizeList + Log.d("cache", "clear cache, sizeList=$sizeList") + } + defaultTransform.cache.clear() + defaultCacheTransform.cache.clear() +} diff --git a/app/src/main/kotlin/li/songe/gkd/service/AbExt.kt b/app/src/main/kotlin/li/songe/gkd/service/AbExt.kt index bf7f784..dffc462 100644 --- a/app/src/main/kotlin/li/songe/gkd/service/AbExt.kt +++ b/app/src/main/kotlin/li/songe/gkd/service/AbExt.kt @@ -2,6 +2,7 @@ package li.songe.gkd.service import android.accessibilityservice.AccessibilityService import android.graphics.Rect +import android.util.LruCache import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import com.blankj.utilcode.util.LogUtils @@ -108,6 +109,7 @@ fun AccessibilityNodeInfo.querySelector( // https://github.com/gkd-kit/gkd/issues/115 +// https://github.com/gkd-kit/gkd/issues/650 // 限制节点遍历的数量避免内存溢出 private const val MAX_CHILD_SIZE = 512 private const val MAX_DESCENDANTS_SIZE = 4096 @@ -209,17 +211,30 @@ data class CacheTransform( val cache: NodeCache, ) -data class NodeCache( - val childMap: MutableMap, AccessibilityNodeInfo> = HashMap(), - val indexMap: MutableMap = HashMap(), - val parentMap: MutableMap = HashMap(), -) { + +private operator fun LruCache.set(child: K, value: V): V { + return put(child, value) +} + +private const val MAX_CACHE_SIZE = MAX_DESCENDANTS_SIZE + +class NodeCache { + private val childMap = + LruCache, AccessibilityNodeInfo>(MAX_CACHE_SIZE) + val indexMap = LruCache(MAX_CACHE_SIZE) + private val parentMap = LruCache(MAX_CACHE_SIZE) + var rootNode: AccessibilityNodeInfo? = null + fun clear() { - childMap.clear() - parentMap.clear() - indexMap.clear() + rootNode = null + childMap.evictAll() + parentMap.evictAll() + indexMap.evictAll() } + val sizeList: List + get() = listOf(childMap.size(), parentMap.size(), indexMap.size()) + fun getIndex(node: AccessibilityNodeInfo): Int { indexMap[node]?.let { return it } getParent(node)?.forEachIndexed { index, child -> @@ -234,14 +249,19 @@ data class NodeCache( } fun getParent(node: AccessibilityNodeInfo): AccessibilityNodeInfo? { + if (rootNode == node) { + return null + } val parent = parentMap[node] if (parent != null) { return parent - } else if (parentMap.contains(node)) { - return null } return node.parent.apply { - parentMap[node] = this + if (this != null) { + parentMap[node] = this + } else { + rootNode = node + } } } diff --git a/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt b/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt index b317ae9..00cf0aa 100644 --- a/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt +++ b/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt @@ -41,6 +41,7 @@ import li.songe.gkd.data.AttrInfo import li.songe.gkd.data.GkdAction import li.songe.gkd.data.RpcError import li.songe.gkd.data.RuleStatus +import li.songe.gkd.data.clearNodeCache import li.songe.gkd.debug.SnapshotExt import li.songe.gkd.shizuku.getShizukuCanUsedFlow import li.songe.gkd.shizuku.shizukuIsSafeOK @@ -173,6 +174,10 @@ class GkdAbService : CompositionAbService({ pair } val activityRule = getAndUpdateCurrentRules() + if (activityRule.currentRules.isEmpty()) { + return@launchTry + } + clearNodeCache() for (rule in (activityRule.currentRules)) { // 规则数量有可能过多导致耗时过长 val statusCode = rule.status if (statusCode == RuleStatus.Status3 && rule.matchDelayJob == null) {