Kotlin Android中AccessibilityService无法滚动的问题求助
AccessibilityService滚动失效问题修复
我正在使用AccessibilityService实现以下功能:自动返回主屏幕,然后向下滚动直到找到“Maps”文本,找到后点击该文本打开地图应用。我希望在设置中启用该服务后立即执行此操作,因此在onServiceConnected()方法中触发了相关逻辑,但目前滚动功能无法正常工作。
原代码
import android.accessibilityservice.AccessibilityService import android.os.Handler import android.os.Looper import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD class MyAccessibilityService : AccessibilityService() { override fun onAccessibilityEvent(event: AccessibilityEvent?) { } override fun onInterrupt() { } override fun onServiceConnected() { super.onServiceConnected() performActionsForMaps() } private fun performActionsForMaps() { performGlobalAction(GLOBAL_ACTION_HOME) Handler(Looper.getMainLooper()).postDelayed({ scrollDownUntilTextAppears("Maps") }, 2000) } private fun scrollDownUntilTextAppears(targetText: String) { val maxScrollAttempts = 10 var scrollAttempts = 0 while (!isTextVisible(targetText) && scrollAttempts < maxScrollAttempts) { val rootNode = rootInActiveWindow val scrollableNode = findScrollableNode(rootNode) if (scrollableNode != null) { Handler(Looper.getMainLooper()).postDelayed({ scrollableNode.performAction(ACTION_SCROLL_FORWARD) }, 1000) } scrollAttempts++ } if (isTextVisible(targetText)) { clickOnText(targetText) } } private fun findScrollableNode(nodeInfo: AccessibilityNodeInfo?): AccessibilityNodeInfo? { if (nodeInfo == null) return null for (i in 0 until nodeInfo.childCount) { val childNode = nodeInfo.getChild(i) if (childNode != null && childNode.isScrollable) { return childNode } val scrollableChild = findScrollableNode(childNode) if (scrollableChild != null) { return scrollableChild } } return null } private fun isTextVisible(targetText: String): Boolean { val rootNode = rootInActiveWindow return isTextVisibleRecursive(rootNode, targetText) } private fun isTextVisibleRecursive( nodeInfo: AccessibilityNodeInfo?, targetText: String ): Boolean { if (nodeInfo == null) return false for (i in 0 until nodeInfo.childCount) { val childNode = nodeInfo.getChild(i) if (childNode != null) { if (childNode.text != null && targetText.equals( childNode.text.toString(), ignoreCase = true ) ) { return true } if (isTextVisibleRecursive(childNode, targetText)) { return true } } } return false } private fun clickOnText(targetText: String) { val rootNode = rootInActiveWindow clickOnTextRecursive(rootNode, targetText) } private fun clickOnTextRecursive(nodeInfo: AccessibilityNodeInfo?, targetText: String) { if (nodeInfo == null) return for (i in 0 until nodeInfo.childCount) { val childNode = nodeInfo.getChild(i) if (childNode != null) { if (childNode.text != null && targetText.equals( childNode.text.toString(), ignoreCase = true ) ) { childNode.performAction(AccessibilityNodeInfo.ACTION_CLICK) return } clickOnTextRecursive(childNode, targetText) } } } companion object { private const val TAG = "MyAccessibilityService" } }
问题原因
- 同步循环与异步延迟冲突:
scrollDownUntilTextAppears方法中使用while同步循环,同时通过Handler.postDelayed异步执行滚动动作,导致循环瞬间跑完所有滚动尝试,完全没有等待滚动后的界面更新就判断文本是否存在。 - AccessibilityNodeInfo未回收:所有获取的节点对象未调用
recycle(),易引发内存泄漏。 - 未处理rootNode为空场景:
rootInActiveWindow可能在界面未加载完成时返回null,导致后续逻辑失效。
修复后的代码
import android.accessibilityservice.AccessibilityService import android.os.Handler import android.os.Looper import android.view.accessibility.AccessibilityNodeInfo import android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD class MyAccessibilityService : AccessibilityService() { private val mainHandler = Handler(Looper.getMainLooper()) private var scrollAttempts = 0 private val maxScrollAttempts = 10 private lateinit var targetText: String override fun onAccessibilityEvent(event: android.view.accessibility.AccessibilityEvent?) {} override fun onInterrupt() {} override fun onServiceConnected() { super.onServiceConnected() performActionsForMaps() } private fun performActionsForMaps() { performGlobalAction(GLOBAL_ACTION_HOME) targetText = "Maps" // 延迟确保主屏幕加载完成 mainHandler.postDelayed({ checkAndScroll() }, 2000) } private fun checkAndScroll() { val rootNode = rootInActiveWindow ?: run { // root为空时延迟重试 mainHandler.postDelayed(::checkAndScroll, 500) return } if (isTextVisible(rootNode, targetText)) { clickOnText(rootNode, targetText) rootNode.recycle() return } if (scrollAttempts >= maxScrollAttempts) { rootNode.recycle() return } val scrollableNode = findScrollableNode(rootNode) scrollableNode?.let { it.performAction(ACTION_SCROLL_FORWARD) it.recycle() } rootNode.recycle() scrollAttempts++ // 延迟等待滚动完成后再检查 mainHandler.postDelayed(::checkAndScroll, 1000) } private fun findScrollableNode(nodeInfo: AccessibilityNodeInfo?): AccessibilityNodeInfo? { if (nodeInfo == null) return null for (i in 0 until nodeInfo.childCount) { val childNode = nodeInfo.getChild(i) childNode?.let { if (it.isScrollable) return it val scrollableChild = findScrollableNode(it) if (scrollableChild != null) { it.recycle() return scrollableChild } it.recycle() } } return null } private fun isTextVisible(rootNode: AccessibilityNodeInfo, targetText: String): Boolean { return isTextVisibleRecursive(rootNode, targetText) } private fun isTextVisibleRecursive( nodeInfo: AccessibilityNodeInfo?, targetText: String ): Boolean { if (nodeInfo == null) return false for (i in 0 until nodeInfo.childCount) { val childNode = nodeInfo.getChild(i) childNode?.let { if (it.text != null && targetText.equals(it.text.toString(), ignoreCase = true)) { it.recycle() return true } if (isTextVisibleRecursive(it, targetText)) { it.recycle() return true } it.recycle() } } return false } private fun clickOnText(rootNode: AccessibilityNodeInfo, targetText: String) { clickOnTextRecursive(rootNode, targetText) } private fun clickOnTextRecursive(nodeInfo: AccessibilityNodeInfo?, targetText: String) { if (nodeInfo == null) return for (i in 0 until nodeInfo.childCount) { val childNode = nodeInfo.getChild(i) childNode?.let { if (it.text != null && targetText.equals(it.text.toString(), ignoreCase = true)) { it.performAction(AccessibilityNodeInfo.ACTION_CLICK) it.recycle() return } clickOnTextRecursive(it, targetText) it.recycle() } } } companion object { private const val TAG = "MyAccessibilityService" } }
关键修复点
- 异步递归替代同步循环:用
checkAndScroll方法配合Handler.postDelayed实现滚动-检查的异步循环,确保每次滚动后等待界面更新再判断文本状态。 - 节点资源回收:所有获取的
AccessibilityNodeInfo对象都调用recycle(),避免内存泄漏。 - 空值处理:当
rootInActiveWindow返回null时,延迟重试,确保界面加载完成后执行逻辑。 - 复用Handler实例:统一使用一个
mainHandler,避免重复创建资源。
内容的提问来源于stack exchange,提问作者user9541096
相关产品推荐
相关产品推荐

