perf: lru cache node
Some checks are pending
Build-Apk / build (push) Waiting to run

This commit is contained in:
lisonge 2024-07-05 16:55:20 +08:00
parent 2a16b33275
commit 274f0e7f29
3 changed files with 78 additions and 42 deletions

View File

@ -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()
}

View File

@ -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<Pair<AccessibilityNodeInfo, Int>, AccessibilityNodeInfo> = HashMap(),
val indexMap: MutableMap<AccessibilityNodeInfo, Int> = HashMap(),
val parentMap: MutableMap<AccessibilityNodeInfo, AccessibilityNodeInfo?> = HashMap(),
) {
private operator fun <K, V> LruCache<K, V>.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<Pair<AccessibilityNodeInfo, Int>, AccessibilityNodeInfo>(MAX_CACHE_SIZE)
val indexMap = LruCache<AccessibilityNodeInfo, Int>(MAX_CACHE_SIZE)
private val parentMap = LruCache<AccessibilityNodeInfo, AccessibilityNodeInfo>(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<Int>
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
}
}
}

View File

@ -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) {