Skip to content

Commit

Permalink
perf: indexCache
Browse files Browse the repository at this point in the history
  • Loading branch information
lisonge committed Feb 25, 2024
1 parent 36719e2 commit ad7d39a
Show file tree
Hide file tree
Showing 35 changed files with 875 additions and 496 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ local.properties

*.jks
*.keystore

/_assets
36 changes: 30 additions & 6 deletions app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package li.songe.gkd.data
import android.accessibilityservice.AccessibilityService
import android.view.accessibility.AccessibilityNodeInfo
import kotlinx.coroutines.Job
import li.songe.gkd.service.CacheTransform
import li.songe.gkd.service.createCacheTransform
import li.songe.gkd.service.lastTriggerRule
import li.songe.gkd.service.lastTriggerTime
import li.songe.gkd.service.querySelector
Expand Down Expand Up @@ -119,15 +121,37 @@ sealed class ResolvedRule(
else -> true
}

fun query(nodeInfo: AccessibilityNodeInfo?): AccessibilityNodeInfo? {
private val canCacheIndex = (matches + excludeMatches).any { s -> s.canCacheIndex }

fun query(
nodeInfo: AccessibilityNodeInfo?,
cacheTransform: CacheTransform? = null
): AccessibilityNodeInfo? {
if (nodeInfo == null) return null
var target: AccessibilityNodeInfo? = null
for (selector in matches) {
target = nodeInfo.querySelector(selector, quickFind) ?: return null
}
for (selector in excludeMatches) {
if (nodeInfo.querySelector(selector, quickFind) != null) return null
if (canCacheIndex) {
val transform = cacheTransform ?: createCacheTransform()
for (selector in matches) {
target = nodeInfo.querySelector(selector, quickFind, transform.transform)
?: return null
}
for (selector in excludeMatches) {
if (nodeInfo.querySelector(
selector,
quickFind,
transform.transform
) != null
) return null
}
} else {
for (selector in matches) {
target = nodeInfo.querySelector(selector, quickFind) ?: return null
}
for (selector in excludeMatches) {
if (nodeInfo.querySelector(selector, quickFind) != null) return null
}
}

return target
}

Expand Down
3 changes: 2 additions & 1 deletion app/src/main/kotlin/li/songe/gkd/debug/KtorErrorPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.blankj.utilcode.util.LogUtils
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.createApplicationPlugin
import io.ktor.server.application.hooks.CallFailed
import io.ktor.server.plugins.origin
import io.ktor.server.request.uri
import io.ktor.server.response.respond
import li.songe.gkd.data.RpcError
Expand All @@ -13,7 +14,7 @@ val KtorErrorPlugin = createApplicationPlugin(name = "KtorErrorPlugin") {
onCall { call ->
// TODO 在局域网会被扫描工具批量请求多个路径
if (call.request.uri == "/" || call.request.uri.startsWith("/api/")) {
Log.d("Ktor", "onCall: ${call.request.uri}")
Log.d("Ktor", "onCall: ${call.request.origin.remoteAddress} -> ${call.request.uri}")
}
}
on(CallFailed) { call, cause ->
Expand Down
244 changes: 224 additions & 20 deletions app/src/main/kotlin/li/songe/gkd/service/AbExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,27 @@ fun AccessibilityNodeInfo.getDepth(): Int {
return depth
}

fun AccessibilityNodeInfo.getVid(): CharSequence? {
val id = viewIdResourceName ?: return null
val appId = packageName ?: return null
if (id.startsWith(appId) && id.startsWith(":id/", appId.length)) {
return id.subSequence(
appId.length + ":id/".length,
id.length
)
}
return null
}

fun AccessibilityNodeInfo.querySelector(
selector: Selector,
quickFind: Boolean = false,
transform: Transform<AccessibilityNodeInfo>? = null,
): AccessibilityNodeInfo? {
val t = (if (selector.canCacheIndex) transform else defaultTransform) ?: defaultTransform
if (selector.isMatchRoot) {
if (parent == null) {
val trackNodes = mutableListOf<AccessibilityNodeInfo>()
return selector.match(this, abTransform, trackNodes)
return selector.match(this, t)
}
return null
}
Expand All @@ -81,16 +94,16 @@ fun AccessibilityNodeInfo.querySelector(
emptyList()
})
if (nodes.isNotEmpty()) {
val trackNodes = mutableListOf<AccessibilityNodeInfo>()
val trackNodes = ArrayList<AccessibilityNodeInfo>(selector.tracks.size)
nodes.forEach { childNode ->
val targetNode = selector.match(childNode, abTransform, trackNodes)
val targetNode = selector.match(childNode, t, trackNodes)
if (targetNode != null) return targetNode
}
}
return null
}
// 在一些开屏广告的界面会造成1-2s的阻塞
return abTransform.querySelector(this, selector)
return t.querySelector(this, selector)
}

// 不可以在 多线程/不同协程作用域 里同时使用
Expand Down Expand Up @@ -148,17 +161,7 @@ val allowPropertyNames = setOf(
private val getAttr: (AccessibilityNodeInfo, String) -> Any? = { node, name ->
when (name) {
"id" -> node.viewIdResourceName
"vid" -> node.viewIdResourceName?.let { id ->
val appId = node.packageName
if (appId != null && id.startsWith(appId) && id.startsWith(":id/", appId.length)) {
id.subSequence(
appId.length + ":id/".length,
id.length
)
} else {
null
}
}
"vid" -> node.getVid()

"name" -> node.className
"text" -> node.text
Expand Down Expand Up @@ -189,21 +192,209 @@ private val getAttr: (AccessibilityNodeInfo, String) -> Any? = { node, name ->
}
}

val abTransform = Transform(
data class CacheTransform(
val transform: Transform<AccessibilityNodeInfo>,
val indexCache: HashMap<AccessibilityNodeInfo, Int>,
)

fun createCacheTransform(): CacheTransform {
val indexCache = HashMap<AccessibilityNodeInfo, Int>()
fun AccessibilityNodeInfo.getChildX(index: Int): AccessibilityNodeInfo? {
return getChild(index)?.also { child ->
indexCache[child] = index
}
}

fun AccessibilityNodeInfo.getIndexX(): Int {
indexCache[this]?.let { return it }
parent?.forEachIndexed { index, child ->
if (child != null) {
indexCache[child] = index
}
if (child == this) {
return index
}
}
return 0
}

val getChildrenCache: (AccessibilityNodeInfo) -> Sequence<AccessibilityNodeInfo> = { node ->
sequence {
repeat(node.childCount.coerceAtMost(MAX_CHILD_SIZE)) { index ->
val child = node.getChildX(index) ?: return@sequence
yield(child)
}
}
}
val transform = Transform(
getAttr = { node, name ->
if (name == "index") {
node.getIndexX()
} else {
getAttr(node, name)
}
},
getName = { node -> node.className },
getChildren = getChildrenCache,
getParent = { node -> node.parent },
getDescendants = { node ->
sequence {
val stack = getChildrenCache(node).toMutableList()
if (stack.isEmpty()) return@sequence
stack.reverse()
val tempNodes = mutableListOf<AccessibilityNodeInfo>()
do {
val top = stack.removeLast()
yield(top)
for (childNode in getChildrenCache(top)) {
tempNodes.add(childNode)
}
if (tempNodes.isNotEmpty()) {
for (i in tempNodes.size - 1 downTo 0) {
stack.add(tempNodes[i])
}
tempNodes.clear()
}
} while (stack.isNotEmpty())
}.take(MAX_DESCENDANTS_SIZE)
},
getChildrenX = { node, connectExpression ->
sequence {
repeat(node.childCount.coerceAtMost(MAX_CHILD_SIZE)) { offset ->
connectExpression.maxOffset?.let { maxOffset ->
if (offset > maxOffset) return@sequence
}
if (connectExpression.checkOffset(offset)) {
val child = node.getChildX(offset) ?: return@sequence
yield(child)
}
}
}
},
getBeforeBrothers = { node, connectExpression ->
sequence {
val parentVal = node.parent ?: return@sequence
val index = indexCache[node] // 如果 node 由 quickFind 得到, 则第一次调用此方法可能得到 indexCache 是空
if (index != null) {
var i = index - 1
var offset = 0
while (0 <= i && i < parentVal.childCount) {
connectExpression.maxOffset?.let { maxOffset ->
if (offset > maxOffset) return@sequence
}
if (connectExpression.checkOffset(offset)) {
val child = parentVal.getChild(i) ?: return@sequence
yield(child)
}
i--
offset++
}
} else {
val list = getChildrenCache(parentVal).takeWhile { it != node }.toMutableList()
list.reverse()
yieldAll(list.filterIndexed { i, _ ->
connectExpression.checkOffset(
i
)
})
}
}
},
getAfterBrothers = { node, connectExpression ->
val parentVal = node.parent
if (parentVal != null) {
val index = indexCache[node]
if (index != null) {
sequence {
var i = index + 1
var offset = 0
while (0 <= i && i < parentVal.childCount) {
connectExpression.maxOffset?.let { maxOffset ->
if (offset > maxOffset) return@sequence
}
if (connectExpression.checkOffset(offset)) {
val child = parentVal.getChild(i) ?: return@sequence
yield(child)
}
i--
offset++
}
}
} else {
getChildrenCache(parentVal).dropWhile { it != node }
.drop(1)
.let {
if (connectExpression.maxOffset != null) {
it.take(connectExpression.maxOffset!! + 1)
} else {
it
}
}
.filterIndexed { i, _ ->
connectExpression.checkOffset(
i
)
}
}
} else {
emptySequence()
}
},
getDescendantsX = { node, connectExpression ->
sequence {
val stack = getChildrenCache(node).toMutableList()
if (stack.isEmpty()) return@sequence
stack.reverse()
val tempNodes = mutableListOf<AccessibilityNodeInfo>()
var offset = 0
do {
val top = stack.removeLast()
if (connectExpression.checkOffset(offset)) {
yield(top)
}
offset++
connectExpression.maxOffset?.let { maxOffset ->
if (offset > maxOffset) return@sequence
}
for (childNode in getChildrenCache(top)) {
tempNodes.add(childNode)
}
if (tempNodes.isNotEmpty()) {
for (i in tempNodes.size - 1 downTo 0) {
stack.add(tempNodes[i])
}
tempNodes.clear()
}
} while (stack.isNotEmpty())
}
},
)

return CacheTransform(transform, indexCache)
}

val defaultCacheTransform = createCacheTransform()

// no cache
val defaultTransform = Transform(
getAttr = getAttr,
getName = { node -> node.className },
getChildren = getChildren,
getChild = { node, index -> if (index in 0..<node.childCount) node.getChild(index) else null },
getParent = { node -> node.parent },
getDescendants = { node ->
sequence {
val stack = getChildren(node).toMutableList()
if (stack.isEmpty()) return@sequence
stack.reverse()
val tempNodes = mutableListOf<AccessibilityNodeInfo>()
var offset = 0
do {
val top = stack.removeLast()
yield(top)
offset++
if (offset > MAX_DESCENDANTS_SIZE) {
return@sequence
}
for (childNode in getChildren(top)) {
tempNodes.add(childNode)
}
Expand All @@ -214,6 +405,19 @@ val abTransform = Transform(
tempNodes.clear()
}
} while (stack.isNotEmpty())
}.take(MAX_DESCENDANTS_SIZE)
}
}
},
getChildrenX = { node, connectExpression ->
sequence {
repeat(node.childCount.coerceAtMost(MAX_CHILD_SIZE)) { offset ->
connectExpression.maxOffset?.let { maxOffset ->
if (offset > maxOffset) return@sequence
}
if (connectExpression.checkOffset(offset)) {
val child = node.getChild(offset) ?: return@sequence
yield(child)
}
}
}
},
)
Loading

0 comments on commit ad7d39a

Please sign in to comment.