安卓无障碍脚本开发全教程 - 详解


第一部分:无障碍服务基础
1.1 无障碍服务概述
安卓无障碍服务(Accessibility Service)是一种特殊类型的服务,旨在帮助残障用户或需辅助功能的用户更好地使用设备。但它的机制远不止于此,开发者可以利用它建立自动化管理、界面监控和交互等功能。
核心功能:
- 界面内容访问:获取屏幕上的UI元素信息
- 自动化操作:模拟点击、滑动等用户操作
- 事件监控:监听窗口变化、通知、焦点改变等系统事件
- 增强交互:为特定应用提供定制化辅助功能
1.2 基本原理与架构
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:
classMyAccessibilityService: AccessibilityService( ) { override fun onServiceConnected( ) { Log.d("A11yService" , "无障碍服务已连接" ) // 可以在此处进行服务配置更新 val info = AccessibilityServiceInfo( ).apply {eventTypes=AccessibilityEvent.TYPE_VIEW_CLICKEDorAccessibilityEvent.TYPE_WINDOW_STATE_CHANGED feedbackType=AccessibilityServiceInfo.FEEDBACK_GENERIC notificationTimeout= 100 flags =AccessibilityServiceInfo.DEFAULTorAccessibilityServiceInfo.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) { valrootNode=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 0until node.childCount) { node.getChild(i)?.let { child -> traverseNode(child, depth + 1 ) child.recycle( ) } } } } 第三部分:高级能力实现
3.1 节点查找与运行
常用查找方法:
fun findNodes(root:AccessibilityNodeInfo) { // 通过文本查找 valbyText= root.findAccessibilityNodeInfosByText("搜索" ) // 通过View ID查找(全限定ID) val byId = root.findAccessibilityNodeInfosByViewId("com.example.app:id/btnSubmit" ) // 借助类名查找 valeditTexts=mutableListOf<AccessibilityNodeInfo> ( ) val queue: Queue<AccessibilityNodeInfo> = LinkedList( ) queue.add(root) while (queue.isNotEmpty( ) ) { valcurrent= queue.poll( ) if (current.className== "android.widget.EditText" ) {editTexts.add(current) } for (i in 0until current.childCount) {current.getChild(i)?.let { queue.add(it) } } } } 节点操作示例:
fun performActions(node:AccessibilityNodeInfo) { // 点击操作 if (node.isClickable) { node.performAction(AccessibilityNodeInfo.ACTION_CLICK) } // 文本输入 valarguments= 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( ) // 闭合路径 } valgestureBuilder=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-> { valnotificationText= 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 自动填写表单
classFormFillerService: AccessibilityService( ) { private valformData= 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 valrootNode=rootInActiveWindow?: returnformData.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 消息自动回复
classAutoReplyService: AccessibilityService( ) { private valreplyMessages= listOf( "我正在开会,稍后回复您" , "好的,收到" , "谢谢通知" ) override fun onAccessibilityEvent(event:AccessibilityEvent) { if (event.packageName!= "com.whatsapp" ) return when (event.eventType) {AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED-> { // 处理通知事件 valmessages= 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 valmessageNodes= root.findAccessibilityNodeInfosByViewId("com.whatsapp:id/message_text" ) // 获取最后一条消息 vallastMessage=messageNodes.lastOrNull( )?.text ?: return // 随机选择回复内容 valrandomReply=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 游戏自动化辅助
classGameHelperService: AccessibilityService( ) { private varisRunning= false private valhandler= Handler(Looper.getMainLooper( ) ) private valclickRunnable= 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 valbattleNode= root.findAccessibilityNodeInfosByViewId("com.game.package:id/battle_indicator" ) if (battleNode.isNotEmpty( ) ) { startAutoClicking( ) } else { stopAutoClicking( ) } } private fun startAutoClicking( ) { if (!isRunning) {isRunning= truehandler.post(clickRunnable) } } private fun stopAutoClicking( ) {isRunning= falsehandler.removeCallbacks(clickRunnable) } private fun performAutoClick( ) { val root =rootInActiveWindow?: return valattackBtn= root.findAccessibilityNodeInfosByViewId("com.game.package:id/attack_button" ) .firstOrNull( )attackBtn?.performAction(AccessibilityNodeInfo.ACTION_CLICK) // 随机位置点击,避免被检测为机器人 if (Math.random( ) < 0.3 ) { valrandomX= (100..500 ).random( ) valrandomY= (200..800 ).random( ) dispatchGesture(createClickGesture(randomX,randomY) , null , null ) } } private fun createClickGesture(x: Int, y: Int):GestureDescription{ valclickPath= Path( ).apply { moveTo(x.toFloat( ) , y.toFloat( ) ) } returnGestureDescription.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-sAccessibilityEvent 日志记录最佳实践:
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 性能优化
优化建议:
- 减少遍历深度:只查找必要的节点层级
- 及时回收节点:调用
recycle()释放资源 - 事件过滤:只监听必要的事件类型
- 延迟处理:对频繁事件使用防抖
- 后台处理:将耗时操作移到工作线程
优化示例:
classOptimizedService: AccessibilityService( ) { private valeventQueue=LinkedBlockingQueue<AccessibilityEvent> ( ) private valworkerThread= HandlerThread("EventProcessor" ).apply { start( ) } private valworkerHandler= Handler(workerThread.looper) private valeventProcessor= 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" /> 隐私注意事项:
- 明确告知用户:在隐私政策中说明数据收集范围
- 最小权限原则:只请求必要的权限
- 敏感数据处理:避免收集密码等敏感信息
- 数据加密:对存储的日志和数据进行加密
6.2 发布流程
测试阶段:
- 在不同安卓版本上测试
- 国产ROM)就是在各种品牌设备上测试(特别
- 测试电池消耗情况
应用商店要求:
- 无障碍辅助工具就是明确说明
- 提供详细的使用说明视频
- 如果是自动化工具,需遵守各商店政策
持续更新:
- 定期适配新安卓版本
- 针对流行应用的特殊适配
- 根据用户反馈优化特性
第七部分:高级主题
7.1 与其他技术的结合
与Tasker集成:
// 接收Tasker的广播意图 private valtaskerReceiver= 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{ valrecognizer=TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS) val image =InputImage.fromBitmap(bitmap, 0 ) return try { valresult=recognizer.process(image).await( )result.text } catch (e:Exception) { Log.e("ML" , "识别失败" , e) "" } } // 截图并处理 fun captureAndAnalyze( ) { valprojection=MediaProjectionManager.createScreenCaptureIntent( ) // 需要先获取用户授权... valimageReader=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 ?: "" } } 
浙公网安备 33010602011771号