> 技术文档 > 安卓无障碍脚本开发全教程_安卓无障碍开发

安卓无障碍脚本开发全教程_安卓无障碍开发

安卓无障碍脚本开发全教程_安卓无障碍开发

文章目录

    • 第一部分:无障碍服务基础
      • 1.1 无障碍服务概述
        • 核心功能:
      • 1.2 基本原理与架构
      • 1.3 开发环境配置
        • 所需工具:
        • 关键依赖:
    • 第二部分:创建基础无障碍服务
      • 2.1 服务声明配置
      • 2.2 服务配置文件
        • 关键属性说明:
      • 2.3 实现服务类
    • 第三部分:高级功能实现
      • 3.1 节点查找与操作
        • 常用查找方法:
        • 节点操作示例:
      • 3.2 手势模拟
      • 3.3 全局事件监听
    • 第四部分:实战案例开发
      • 4.1 自动填写表单
      • 4.2 消息自动回复
      • 4.3 游戏自动化辅助
    • 第五部分:调试与优化
      • 5.1 调试技巧
        • ADB调试命令:
        • 日志记录最佳实践:
      • 5.2 性能优化
        • 优化建议:
        • 优化示例:
    • 第六部分:发布与安全
      • 6.1 权限与隐私
        • 必要权限声明:
        • 隐私注意事项:
      • 6.2 发布流程
    • 第七部分:高级主题
      • 7.1 与其他技术的结合
        • 与Tasker集成:
        • 使用机器学习:
      • 7.2 跨版本兼容性处理
        • 版本差异处理表:
        • 兼容性代码示例:

安卓无障碍脚本开发全教程_安卓无障碍开发

第一部分:无障碍服务基础

1.1 无障碍服务概述

安卓无障碍服务(Accessibility Service)是一种特殊类型的服务,旨在帮助残障用户或需要辅助功能的用户更好地使用设备。但它的功能远不止于此,开发者可以利用它实现自动化操作、界面监控和交互等功能。

核心功能:
  • 界面内容访问:获取屏幕上的UI元素信息
  • 自动化操作:模拟点击、滑动等用户操作
  • 事件监控:监听窗口变化、通知、焦点改变等系统事件
  • 增强交互:为特定应用提供定制化辅助功能

1.2 基本原理与架构

#mermaid-svg-tVsWD5RvEW6JNzjY {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-tVsWD5RvEW6JNzjY .error-icon{fill:#552222;}#mermaid-svg-tVsWD5RvEW6JNzjY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tVsWD5RvEW6JNzjY .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-tVsWD5RvEW6JNzjY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tVsWD5RvEW6JNzjY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tVsWD5RvEW6JNzjY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tVsWD5RvEW6JNzjY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tVsWD5RvEW6JNzjY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tVsWD5RvEW6JNzjY .marker.cross{stroke:#333333;}#mermaid-svg-tVsWD5RvEW6JNzjY svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tVsWD5RvEW6JNzjY .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tVsWD5RvEW6JNzjY text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-tVsWD5RvEW6JNzjY .actor-line{stroke:grey;}#mermaid-svg-tVsWD5RvEW6JNzjY .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-tVsWD5RvEW6JNzjY .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-tVsWD5RvEW6JNzjY #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-tVsWD5RvEW6JNzjY .sequenceNumber{fill:white;}#mermaid-svg-tVsWD5RvEW6JNzjY #sequencenumber{fill:#333;}#mermaid-svg-tVsWD5RvEW6JNzjY #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-tVsWD5RvEW6JNzjY .messageText{fill:#333;stroke:#333;}#mermaid-svg-tVsWD5RvEW6JNzjY .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tVsWD5RvEW6JNzjY .labelText,#mermaid-svg-tVsWD5RvEW6JNzjY .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-tVsWD5RvEW6JNzjY .loopText,#mermaid-svg-tVsWD5RvEW6JNzjY .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-tVsWD5RvEW6JNzjY .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tVsWD5RvEW6JNzjY .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-tVsWD5RvEW6JNzjY .noteText,#mermaid-svg-tVsWD5RvEW6JNzjY .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-tVsWD5RvEW6JNzjY .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tVsWD5RvEW6JNzjY .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tVsWD5RvEW6JNzjY .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tVsWD5RvEW6JNzjY .actorPopupMenu{position:absolute;}#mermaid-svg-tVsWD5RvEW6JNzjY .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-tVsWD5RvEW6JNzjY .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tVsWD5RvEW6JNzjY .actor-man circle,#mermaid-svg-tVsWD5RvEW6JNzjY line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-tVsWD5RvEW6JNzjY :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;} 用户操作 目标应用 无障碍服务 系统框架 触发界面变化 更新界面状态 发送AccessibilityEvent 处理事件 可选: 执行操作(点击/滑动等) 执行请求的操作 用户操作 目标应用 无障碍服务 系统框架

1.3 开发环境配置

所需工具:
  • Android Studio最新版
  • 安卓设备或模拟器(API 16+)
  • ADB调试工具
关键依赖:
dependencies { implementation \'androidx.core:core-ktx:1.7.0\' implementation \'androidx.appcompat:appcompat:1.4.1\'}

第二部分:创建基础无障碍服务

2.1 服务声明配置

AndroidManifest.xml中添加服务声明:

<service android:name=\".MyAccessibilityService\" android:permission=\"android.permission.BIND_ACCESSIBILITY_SERVICE\" android:exported=\"true\"> <intent-filter> <action android:name=\"android.accessibilityservice.AccessibilityService\" /> </intent-filter> <meta-data android:name=\"android.accessibilityservice\" android:resource=\"@xml/service_config\" /></service>

2.2 服务配置文件

创建res/xml/service_config.xml

<accessibility-service xmlns:android=\"http://schemas.android.com/apk/res/android\" android:description=\"@string/accessibility_service_description\" android:accessibilityEventTypes=\"typeAllMask\" android:accessibilityFlags=\"flagDefault|flagRetrieveInteractiveWindows\" android:canRetrieveWindowContent=\"true\" android:settingsActivity=\"com.example.android.accessibility.ServiceSettingsActivity\" android:canRequestFilterKeyEvents=\"true\" android:canPerformGestures=\"true\" android:notificationTimeout=\"100\" android:packageNames=\"com.example.targetapp\" />
关键属性说明:
  • accessibilityEventTypes:监听的事件类型
  • packageNames:指定监控的应用包名(可选)
  • canPerformGestures:允许执行手势操作(API 24+)

2.3 实现服务类

创建基础服务类MyAccessibilityService.kt

class MyAccessibilityService : AccessibilityService() { override fun onServiceConnected() { Log.d(\"A11yService\", \"无障碍服务已连接\") // 可以在此处进行服务配置更新 val info = AccessibilityServiceInfo().apply { eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC notificationTimeout = 100 flags = AccessibilityServiceInfo.DEFAULT or  AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS } this.serviceInfo = info } override fun onAccessibilityEvent(event: AccessibilityEvent?) { event ?: return when (event.eventType) { AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> { handleWindowChange(event) } AccessibilityEvent.TYPE_VIEW_CLICKED -> { handleViewClick(event) } } } override fun onInterrupt() { Log.w(\"A11yService\", \"无障碍服务被中断\") } private fun handleWindowChange(event: AccessibilityEvent) { val rootNode = rootInActiveWindow ?: return Log.d(\"A11yService\", \"窗口变化: ${event.packageName}\") // 遍历视图树 traverseNode(rootNode) } private fun traverseNode(node: AccessibilityNodeInfo, depth: Int = 0) { if (node.childCount == 0) { Log.d(\"A11yTree\", \"${\" \".repeat(depth)}${node.viewIdResourceName}\") return } for (i in 0 until node.childCount) { node.getChild(i)?.let { child -> traverseNode(child, depth + 1) child.recycle() } } }}

第三部分:高级功能实现

3.1 节点查找与操作

常用查找方法:
fun findNodes(root: AccessibilityNodeInfo) { // 通过文本查找 val byText = root.findAccessibilityNodeInfosByText(\"搜索\") // 通过View ID查找(全限定ID) val byId = root.findAccessibilityNodeInfosByViewId(\"com.example.app:id/btnSubmit\") // 通过类名查找 val editTexts = mutableListOf<AccessibilityNodeInfo>() val queue: Queue<AccessibilityNodeInfo> = LinkedList() queue.add(root) while (queue.isNotEmpty()) { val current = queue.poll() if (current.className == \"android.widget.EditText\") { editTexts.add(current) } for (i in 0 until current.childCount) { current.getChild(i)?.let { queue.add(it) } } }}
节点操作示例:
fun performActions(node: AccessibilityNodeInfo) { // 点击操作 if (node.isClickable) { node.performAction(AccessibilityNodeInfo.ACTION_CLICK) } // 文本输入 val arguments = Bundle().apply { putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, \"Hello\") } node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) // 焦点控制 node.performAction(AccessibilityNodeInfo.ACTION_FOCUS) // 滚动操作 node.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)}

3.2 手势模拟

Android支持通过无障碍服务模拟复杂手势:

fun performGesture(service: AccessibilityService) { val path = Path().apply { moveTo(100f, 100f) // 起点 lineTo(500f, 100f) // 移动到右侧 lineTo(500f, 500f) // 向下移动 lineTo(100f, 500f) // 向左移动 close()  // 闭合路径 } val gestureBuilder = GestureDescription.Builder() .addStroke(GestureDescription.StrokeDescription( path, 0L, // 开始时间 1000L, // 持续时间(毫秒) false // 是否持续 )) service.dispatchGesture(gestureBuilder.build(), object : AccessibilityService.GestureResultCallback() { override fun onCompleted(gestureDescription: GestureDescription?) { Log.d(\"Gesture\", \"手势完成\") } override onCancelled(gestureDescription: GestureDescription?) { Log.w(\"Gesture\", \"手势取消\") } }, null)}

3.3 全局事件监听

监听系统级事件:

override fun onAccessibilityEvent(event: AccessibilityEvent) { when (event.eventType) { AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED -> { val notificationText = event.text.joinToString() Log.d(\"Notification\", \"新通知: $notificationText\") } AccessibilityEvent.TYPE_ANNOUNCEMENT -> { Log.d(\"Announcement\", \"系统公告: ${event.text}\") } AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START -> { Log.d(\"Touch\", \"触摸探索开始\") } }}

第四部分:实战案例开发

4.1 自动填写表单

class FormFillerService : AccessibilityService() { private val formData = mapOf( \"username\" to \"testuser\", \"password\" to \"secure123\", \"email\" to \"test@example.com\" ) override fun onAccessibilityEvent(event: AccessibilityEvent) { if (event.eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) return val rootNode = rootInActiveWindow ?: return formData.forEach { (fieldName, value) -> val nodes = rootNode.findAccessibilityNodeInfosByViewId(\"com.example.app:id/$fieldName\") nodes.firstOrNull()?.let { field -> if (field.className == \"android.widget.EditText\") {  val args = Bundle().apply { putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, value)  }  field.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args) } } } // 自动提交表单 rootNode.findAccessibilityNodeInfosByViewId(\"com.example.app:id/submit\") .firstOrNull() ?.performAction(AccessibilityNodeInfo.ACTION_CLICK) }}

4.2 消息自动回复

class AutoReplyService : AccessibilityService() { private val replyMessages = listOf( \"我正在开会,稍后回复您\", \"好的,收到\", \"谢谢通知\" ) override fun onAccessibilityEvent(event: AccessibilityEvent) { if (event.packageName != \"com.whatsapp\") return when (event.eventType) { AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED -> { // 处理通知事件 val messages = event.text.filter { it.contains(\"发来消息\") } if (messages.isNotEmpty()) {  replyToLatestMessage() } } AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> { // 处理界面文本变化 if (isChatOpen()) {  autoReplyInChat() } } } } private fun replyToLatestMessage() { // 实现打开聊天界面并回复的逻辑 } private fun isChatOpen(): Boolean { // 检测当前是否在聊天界面 } private fun autoReplyInChat() { val root = rootInActiveWindow ?: return val messageNodes = root.findAccessibilityNodeInfosByViewId(\"com.whatsapp:id/message_text\") // 获取最后一条消息 val lastMessage = messageNodes.lastOrNull()?.text ?: return // 随机选择回复内容 val randomReply = replyMessages.random() // 找到输入框并发送 root.findAccessibilityNodeInfosByViewId(\"com.whatsapp:id/entry\") .firstOrNull() ?.let { input -> val args = Bundle().apply {  putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, randomReply) } input.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args) // 发送消息 root.findAccessibilityNodeInfosByViewId(\"com.whatsapp:id/send\")  .firstOrNull()  ?.performAction(AccessibilityNodeInfo.ACTION_CLICK) } }}

4.3 游戏自动化辅助

class GameHelperService : AccessibilityService() { private var isRunning = false private val handler = Handler(Looper.getMainLooper()) private val clickRunnable = object : Runnable { override fun run() { performAutoClick() if (isRunning) { handler.postDelayed(this, 1000) // 每秒点击一次 } } } override fun onServiceConnected() { val info = AccessibilityServiceInfo().apply { eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC flags = AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS } serviceInfo = info } override fun onAccessibilityEvent(event: AccessibilityEvent) { if (event.packageName != \"com.game.package\") return when (event.eventType) { AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> { checkGameState() } } } private fun checkGameState() { val root = rootInActiveWindow ?: return val battleNode = root.findAccessibilityNodeInfosByViewId(\"com.game.package:id/battle_indicator\") if (battleNode.isNotEmpty()) { startAutoClicking() } else { stopAutoClicking() } } private fun startAutoClicking() { if (!isRunning) { isRunning = true handler.post(clickRunnable) } } private fun stopAutoClicking() { isRunning = false handler.removeCallbacks(clickRunnable) } private fun performAutoClick() { val root = rootInActiveWindow ?: return val attackBtn = root.findAccessibilityNodeInfosByViewId(\"com.game.package:id/attack_button\") .firstOrNull() attackBtn?.performAction(AccessibilityNodeInfo.ACTION_CLICK) // 随机位置点击,避免被检测为机器人 if (Math.random() < 0.3) { val randomX = (100..500).random() val randomY = (200..800).random() dispatchGesture(createClickGesture(randomX, randomY), null, null) } } private fun createClickGesture(x: Int, y: Int): GestureDescription { val clickPath = Path().apply { moveTo(x.toFloat(), y.toFloat()) } return GestureDescription.Builder() .addStroke(GestureDescription.StrokeDescription( clickPath, 0, 50)) .build() }}

第五部分:调试与优化

5.1 调试技巧

ADB调试命令:
# 查看已启用的无障碍服务adb shell settings get secure enabled_accessibility_services# 启用服务adb shell settings put secure enabled_accessibility_services com.example.pkg/.MyAccessibilityService# 查看无障碍事件日志adb shell logcat -s AccessibilityEvent
日志记录最佳实践:
fun logNodeInfo(node: AccessibilityNodeInfo) { val sb = StringBuilder().apply { append(\"View ID: ${node.viewIdResourceName}\\n\") append(\"Text: ${node.text}\\n\") append(\"Class: ${node.className}\\n\") append(\"Bounds: ${node.boundsInScreen}\\n\") append(\"Actions: ${node.actionList.joinToString()}\\n\") append(\"ChildCount: ${node.childCount}\\n\") } Log.d(\"NodeInfo\", sb.toString())}

5.2 性能优化

优化建议:
  1. 减少遍历深度:只查找必要的节点层级
  2. 及时回收节点:调用recycle()释放资源
  3. 事件过滤:只监听必要的事件类型
  4. 延迟处理:对频繁事件使用防抖
  5. 后台处理:将耗时操作移到工作线程
优化示例:
class OptimizedService : AccessibilityService() { private val eventQueue = LinkedBlockingQueue<AccessibilityEvent>() private val workerThread = HandlerThread(\"EventProcessor\").apply { start() } private val workerHandler = Handler(workerThread.looper) private val eventProcessor = object : Runnable { override fun run() { while (true) { val event = eventQueue.take() processEvent(event) } } } override fun onCreate() { super.onCreate() workerHandler.post(eventProcessor) } override fun onAccessibilityEvent(event: AccessibilityEvent) { // 快速将事件加入队列,避免阻塞主线程 eventQueue.put(event) } private fun processEvent(event: AccessibilityEvent) { // 实际处理逻辑 } override fun onDestroy() { workerThread.quitSafely() super.onDestroy() }}

第六部分:发布与安全

6.1 权限与隐私

必要权限声明:
<uses-permission android:name=\"android.permission.BIND_ACCESSIBILITY_SERVICE\" /><uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />
隐私注意事项:
  1. 明确告知用户:在隐私政策中说明数据收集范围
  2. 最小权限原则:只请求必要的权限
  3. 敏感数据处理:避免收集密码等敏感信息
  4. 数据加密:对存储的日志和数据进行加密

6.2 发布流程

  1. 测试阶段

    • 在不同安卓版本上测试
    • 在各种品牌设备上测试(特别是国产ROM)
    • 测试电池消耗情况
  2. 应用商店要求

    • 明确说明是无障碍辅助工具
    • 提供详细的使用说明视频
    • 如果是自动化工具,需遵守各商店政策
  3. 持续更新

    • 定期适配新安卓版本
    • 针对流行应用的特殊适配
    • 根据用户反馈优化功能

第七部分:高级主题

7.1 与其他技术的结合

与Tasker集成:
// 接收Tasker的广播意图private val taskerReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.action == \"net.dinglisch.android.tasker.ACTION_TRIGGER\") { val task = intent.getStringExtra(\"task\") when (task) { \"start_automation\" -> startAutomation() \"stop_automation\" -> stopAutomation() } } }}override fun onCreate() { super.onCreate() registerReceiver(taskerReceiver, IntentFilter(\"net.dinglisch.android.tasker.ACTION_TRIGGER\"))}
使用机器学习:
// 使用ML Kit识别屏幕内容fun detectTextFromScreen(bitmap: Bitmap): String { val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS) val image = InputImage.fromBitmap(bitmap, 0) return try { val result = recognizer.process(image).await() result.text } catch (e: Exception) { Log.e(\"ML\", \"识别失败\", e) \"\" }}// 截图并处理fun captureAndAnalyze() { val projection = MediaProjectionManager.createScreenCaptureIntent() // 需要先获取用户授权... val imageReader = ImageReader.newInstance( screenWidth, screenHeight, PixelFormat.RGBA_8888, 2 ) imageReader.setOnImageAvailableListener({ reader -> val image = reader.acquireLatestImage() // 转换为Bitmap并传递给识别器 val text = detectTextFromScreen(convertImageToBitmap(image)) Log.d(\"ScreenText\", \"识别结果: $text\") image.close() }, handler)}

7.2 跨版本兼容性处理

版本差异处理表:
功能 API 16-22 API 23-28 API 29+ 节点信息获取 基本支持 增强支持 受限 手势模拟 不支持 部分支持 完全支持 隐私限制 无 部分 严格 后台服务 允许 限制 严格限制
兼容性代码示例:
fun performActionCompat(node: AccessibilityNodeInfo, action: Int, args: Bundle? = null): Boolean { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { node.performAction(action, args) } else { node.performAction(action) }}fun getNodeTextCompat(node: AccessibilityNodeInfo): String { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { node.text?.toString() ?: \"\" } else { node.text ?: \"\" }}

安卓无障碍脚本开发全教程_安卓无障碍开发