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

在这里插入图片描述


在这里插入图片描述

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

1.1 无障碍服务概述

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

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

1.2 基本原理与架构

用户操作 目标应用 无障碍服务 系统框架 触发界面变化更新界面状态发送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

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 性能优化

优化建议:
  1. 减少遍历深度:只查找必要的节点层级
  2. 及时回收节点:调用recycle()释放资源
  3. 事件过滤:只监听必要的事件类型
  4. 延迟处理:对频繁事件使用防抖
  5. 后台处理:将耗时操作移到工作线程
优化示例:
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" />
隐私注意事项:
  1. 明确告知用户:在隐私政策中说明数据收集范围
  2. 最小权限原则:只请求必要的权限
  3. 敏感数据处理:避免收集密码等敏感信息
  4. 数据加密:对存储的日志和数据进行加密

6.2 发布流程

  1. 测试阶段

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

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

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

第七部分:高级主题

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-22API 23-28API 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 ?: "" } }

在这里插入图片描述

posted on 2025-06-07 20:15  ljbguanli  阅读(745)  评论(0)    收藏  举报