AutoJs源码与无障碍服务:从配置到自检的完整指南
简介面向安卓自动化初学者的AutoJs无障碍开启脚本提供的是一个可直接运行的脚本模板用来自动打开系统无障碍服务解决自动化任务中因权限未开启而无法执行点击、滑动等操作的问题这类脚本常见于辅助自动抢单、批量处理或日常手机操作等场景适合依靠Android辅助功能实现自动化交互的开发者直接套用。核心逻辑集中在唯一的源码文件内支持低版本AutoJs环境要求不高无需额外依赖既能满足临时调用无障碍能力的需求也便于拆解学习权限开启流程。压缩后大小仅581B压缩包内只有1个js文件内容精简可无缝融入个人自动化项目或作为排查无障碍调用问题的对照样本。目前已有872人学习下载虽然体积极小但结构清晰、开箱即用对快速理解无障碍服务API写法比较有帮助。此脚本仅供学习与个人研究使用请勿用于商业场景并注意遵守相关平台规则。1. AutoJs源码与开启无障碍脚本能不能动关键就在这一步源码都编译出来了装到手机上跑 Demo结果控制台第一句就是“请先开启无障碍服务”——这是不少接触 AutoJs 源码的人遇到的第一道坎。标题里的“AutoJs源码”和“开启无障碍”其实是同一件事的两端前者是你准备改造成自己工具的自动化框架后者是让这套框架能真正控制界面的总开关。无障碍服务不开启Auto.js 能启动但什么都干不了就像引擎点着了但没挂挡。这篇文章要做的是把无障碍服务从系统机制、源码配置、脚本自检到日常排障整条链路拆开让你拿到的不是一句“去设置里打开”而是能自编译、能自检、能兜底的一套完整方案。适合自己改源码打包的人、写自动化脚本的开发者以及被无障碍服务反复折腾到没脾气的老用户。2. 无障碍服务在AutoJs源码里的调用链路从系统API到JS桥接2.1 无障碍服务是一根系统级别的“外接操纵杆”无障碍服务的本质是安卓系统对外暴露的一套辅助功能接口。应用声明一个无障碍服务后系统会往这个服务的回调里持续推送当前界面的事件包括窗口打开、窗口内容变化、节点获得焦点等。服务拿到这些事件后可以进一步调用系统提供的 API 去读取界面节点树、获取控件的位置和尺寸甚至直接注入点击、长按、滑动、手势这些操作。整个过程不需要额外授权弹窗也不需要设备处于调试状态所以它成了很多自动化框架首选的底层通道。Auto.js 之所以选无障碍服务而不是别的方式核心原因是这套接口同时覆盖了信息读取和操作注入并且适配面广。普通 App 无法读取其他应用界面但无障碍服务可以想让脚本知道当前按钮坐标在哪里靠截图加 OCR 不仅慢而且受分辨率影响直接读节点树则稳定得多事件推送基本是实时的。Auto.js 在源码层面把这一大套系统 API 收敛成了脚本里常见的auto()、auto.waitFor()、click()、text()这些简单接口脚本作者不需要理解 Binder 和进程通信只管调用即可。也正因为这是系统级能力它的生死不归 Auto.js 主进程掌控。用户手动关闭服务、系统重启后服务没有被自动绑定、省电策略把服务进程杀掉这些情况都会让代码里的auto.service变成 null。源码能做的只是注册这个服务并在运行时检测状态真正决定服务是否活着的还是系统侧。把这一点想透后面很多玄学问题就都好解释了。2.2 源码里管无障碍的三个部分缺一不可在常见的 AutoJs 源码工程里与无障碍能力相关的内容主要分布在三个地方缺一个都会导致自动化失败。源码模块承载内容与脚本的关系无障碍 Service 实现类真正的无障碍服务逻辑处理系统回调与注入请求编译期注册运行期由系统绑定accessibility_service_config.xml告诉系统这个服务要监听什么事件、能否读取窗口、能否手势注入决定服务能力的边界配置错了服务会显示但功能残缺桥接与 API 封装层把 Service 能力封装成 JS 可调用的auto、click、swipe等对象脚本直接打交道的这一层Service 实现类通常会放在源码里类似core/accessibility的目录下作用是继承系统的无障碍服务基类并重写onAccessibilityEvent和onInterrupt两个核心方法。前者是事件入口Auto.js 在这里做节点缓存、事件分发后者是服务被系统中断时的兜底回调一般用来释放资源。脚本层面的auto()最终会通过桥接层去绑定这个 Service绑定成功后返回一个封装好的服务对象之后脚本每次查找控件都会通过它向系统请求节点快照。配置 XML 是另一个容易忽略的坑。这里描述的是“这个服务想拥有哪些能力”系统会在用户点开无障碍开关前展示给用户看并把配置落实到运行期。如果你在自编译时把canRetrieveWindowContent设置为 false那么即使服务开启脚本也没法读取界面节点只会得到一个空节点树。后面我专门列了一张参数表逐项说明改动后果。2.3 为什么不是 root不是 ADB而是无障碍有人会问源码都到自己手里了直接塞 root 权限干活不是更干脆Root 方案确实能绕过很多限制但它的用户门槛太高国内很多设备市场版本根本不开放 bootloader普通用户拿到源码编译产物后装到手机上大概率没有 root 环境。ADB 方案则需要电脑端持续连接或依赖无线调试不适合手机作为独立工具长期运行。无障碍方案不需要越狱、不需要数据线、不需要额外守护进程用户只需要在系统设置里点一个开关这是它成为 AutoJs 默认操作通道的根本原因。但这不意味着无障碍方案毫无代价。系统对无障碍服务的限制这些年一直在收紧一部分设备上服务会进入“耗电优化名单”屏幕关闭一段时间后服务进程被冻结部分系统还会在用户手动停止应用时强制回收无障碍服务。因此围绕“开启无障碍”这个动作真正重要的不只是设置里的那一下点击而是源码配置正确、脚本检测到位、异常恢复及时这三件事都做好。3. 自编译源码时把无障碍声明对Manifest、配置XML与三个打包盲区3.1 在 AndroidManifest 里注册 Service并把绑定权限写对如果是从官方发布包直接安装的用户无障碍服务早就注册好了不需要关心 Manifest。但如果你拿到了源码想改包名、改界面、精简体积重新编译后第一步就是把无障碍服务重新声明进清单文件。常见的注册模板长这样service android:name.service.MyAccessibilityService android:permissionandroid.permission.BIND_ACCESSIBILITY_SERVICE android:exportedfalse android:label自动化脚本辅助服务 intent-filter action android:nameandroid.accessibilityservice.AccessibilityService / /intent-filter meta-data android:nameandroid.accessibilityservice android:resourcexml/accessibility_service_config / /service这里最关键的一行是android:permissionandroid.permission.BIND_ACCESSIBILITY_SERVICE。无障碍服务不能像普通权限那样由应用主动申请必须声明这个绑定权限系统才有资格在用户开启开关时把这个服务绑到自己的进程里。android:name要替换成你源码里实际的服务类完整路径如果改了包名但忘了同步这里编译能过装上去之后设置页里根本看不到这个服务。android:exportedfalse看起来和直觉相反一个系统要绑定的服务怎么不导出呢实际上无障碍服务的绑定走的是系统内部的特殊通道不依赖 exported 导出保持 false 反而能减少外部应用恶意调用。如果你在自编译时把 exported 改成 true部分系统会直接拒绝在无障碍列表里展示这个服务。这个容易踩先记住了。3.2 accessibility_service_config.xml 的参数决定了服务是“能用”还是“虚胖”注册好服务之后还要在res/xml目录下准备好配置文件名字和 Manifest 里meta-data指定的保持一致。下面是一份适合 Auto.js 场景的最小配置accessibility-service xmlns:androidhttp://schemas.android.com/apk/res/android android:accessibilityEventTypestypeWindowStateChanged|typeWindowContentChanged|typeWindowStateChanged android:accessibilityFeedbackTypefeedbackGeneric android:notificationTimeout100 android:canRetrieveWindowContenttrue android:canPerformGesturestrue android:isAccessibilityTooltrue android:descriptionstring/accessibility_service_description /逐个参数说清楚参数推荐值不这样配置的后果accessibilityEventTypes至少包含typeWindowStateChanged和typeWindowContentChanged事件收不到脚本只能用轮询方式找节点延迟明显accessibilityFeedbackTypefeedbackGeneric只影响系统提示形式不配可能导致服务无法启用notificationTimeout100事件合并窗口值太大会让节点刷新变慢canRetrieveWindowContenttrue不设为 true脚本拿到的是空节点树findOne 永远失败canPerformGesturestrue不设为 truegesture、swipe等接口全部失效isAccessibilityTooltrue部分系统会让服务更不容易被省电策略杀掉但兼容性需实测packageNames留空留空代表监听所有应用适合通用自动化只填单个包名则服务只在那个应用里生效packageNames这个参数需要多说一句。如果你只打算自动化某个特定应用填上包名能减少系统事件推送量服务也会更省电但对于 Auto.js 这种通用框架通常建议留空否则脚本跳到其他应用时服务会处于“不监听”状态节点查询照样失败。3.3 自编译打包时三个最容易翻车的盲区第一个盲区是混淆规则。源码默认不开混淆通常没事但如果你为了缩小包体开启了混淆一定要在 ProGuard 规则里 keep 住无障碍 Service 类以及它重写的系统回调方法。混淆之后类名和方法名被改写而系统绑定服务时是按 Manifest 里的类名去反射加载的类名对不上直接找不到服务症状是设置里看不到入口。常见做法是加一条-keep class * extends android.accessibilityservice.AccessibilityService把整个继承体系保下来。第二个盲区是资源合并。有的源码工程里已经有一份accessibility_service_config.xml你在做界面定制时不小心改了resConfig或清理了 “无用资源”编译时很容易把这份配置丢掉或者被其他依赖模块的同名资源覆盖。排查方法很直接编译产物打包后用解压工具看 APK 里的res/xml目录确保服务配置和 Manifest 引用对得上文件不在的话装上去根本不会出现在无障碍列表。第三个盲区是签名问题。自编译的 APK 如果没有正确签名部分系统会拒绝将无障碍服务识别为可绑定状态表现就是开关能点但立刻弹回关闭状态。这不是服务代码的锅是签名校验没过。务必在构建流程里配置好 release 签名不要拿 debug 签名应付长期使用的设备debug 签名在部分定制系统上同样会有兼容问题。4. 用JS脚本拉起无障碍并自检从auto()到运行态检测4.1 为什么源码配好了脚本里还得写 auto()源码和配置文件解决的是“系统允许这个服务存在”的问题但脚本运行又是另一回事。Auto.js 脚本运行在应用自己的进程里而无障碍服务是系统绑定到应用进程里的一个独立组件脚本进程不会自动持有它的引用。因此每个脚本在真正开始操作界面之前都必须先执行一次auto()让桥接层去获取无障碍服务实例并把服务绑定到脚本可调用的对象上。auto()和auto.waitFor()的差别很多新手没搞懂。auto()调用后会立刻返回如果此时服务没开启它可能只会弹一个提示脚本继续往下走等你访问节点时才报错auto.waitFor()则会进入等待状态在服务可用之前阻塞当前脚本。我一般建议脚本入口统一使用auto.waitFor()避免后面所有节点操作都建立在空服务上。如果你写的是定时任务脚本在服务未就绪时宁可卡住等待也不要带着 null 服务跑进业务逻辑然后留下一堆找不到节点的报错。4.2 一套可复制的服务自检最小代码把下面这段放到你脚本的最前面可以解决大部分“开着服务但脚本不认”的问题// 等待无障碍服务就绪最多等 10 秒 auto.waitFor(); // 如果超时仍未拿到服务主动引导用户去设置页 if (!auto.service) { toast(未检测到无障碍服务现在为你打开设置页); app.startActivity({ action: android.settings.ACCESSIBILITY_SETTINGS }); exit(); } // 确认服务不仅能拿到而且能读取窗口内容 if (!auto.service.isAccessibilityEnabled()) { toast(无障碍服务已开启但未生效请尝试重新开关一次); exit(); } log(无障碍服务状态正常脚本继续执行);这段代码的价值不在长而在把“服务状态”这个黑匣子显式化了。auto.waitFor()会阻塞当前脚本默认等待时间是系统决定的下一次服务回调但如果用户一直没有去设置页打开服务它会一直等下去所以要在等待之后判断auto.service是否为 null。isAccessibilityEnabled()是服务对象提供的实时状态查询方法返回 false 代表当前虽然拿到了对象但系统实际没把它标记为运行中这种状态通常发生在服务被系统强制停止但应用进程还活着的时候。跳转设置页那一段用的是标准系统 Intentaction指定为无障碍设置页。不同设备可能对入口名称有差异但大多数系统都认这个 action。如果你的源码定制过国际化字符串这里的 toast 文本可以换成对应语言的文案逻辑不用动。这里有一点容易误解auto.waitFor()等待成功并不代表服务和当前脚本处于同一个上下文比如定时任务触发时屏幕还锁着服务虽然活着但界面节点不可用所以业务逻辑前还要做界面可用性检查。4.3 场景化的状态检测定时任务启动前与页面切换后服务自检不只在脚本开头做一次页面切换后也应该有个“服务仍然有效”的确认机制。比如一个脚本先切到微信再切回桌面再进另一个应用中间如果服务被系统回收auto.service的引用可能已经失效但脚本不会主动感知。这时再调用findOne会走一个很长的超时或者直接抛异常。常见的做法是封装一个ensureService()工具函数在每次关键节点操作前调用function ensureService() { if (auto.service auto.service.isAccessibilityEnabled()) { return true; } log(服务已掉线重新连接); auto.waitFor(); return auto.service ! null; } // 每次切完应用后先确认服务在线 app.startActivity({ packageName: target.package.name }); sleep(1200); ensureService();注意这里的auto.waitFor()在服务掉线后重新调用并不会自动重新连接系统侧服务它只能等待服务重新可被获取。真正的重连动作发生在用户重新打开服务或者系统自己恢复了服务绑定之后。所以这个函数的意义是让你尽早发现掉线而不是帮你修好系统侧的问题。发现掉线后要么提示用户手动处理要么让你的脚本主动跳回设置页引导重开。5. 开启无障碍后的五类高频问题现象、根因与排查5.1 设置页显示已开启脚本却检测不到服务现象用户在系统设置里明明看到无障碍服务开关是绿色的但脚本跑起来auto.service仍然是 null或者isAccessibilityEnabled()返回 false。很多人会怀疑是源码没配好进源码里反复检查变量一直是好的。原因这类问题常见于系统重启后。手机重启时系统恢复无障碍服务的顺序和应用自启动的顺序不一致应用进程先被拉起来了但服务还没来得及绑定设置页显示的“已开启”只是保存了用户偏好不代表服务此刻已运行。另一种原因是前置版本的应用被覆盖安装老服务实例已失效但系统设置里残留下一次绑定状态。解决在代码里不要只判断一次启动后每隔 2 秒轮询一次最长等 10 秒。多数情况下系统会在应用进程稳定后自动重绑服务轮询可以等到那个时机。如果一直等不到引导用户把服务关掉再打开一次强制系统重新执行绑定流程。源码层面也可以做一点优化在主进程启动时主动请求服务分类但不同系统行为差异较大不要指望一条命令修复所有问题。5.2 半夜屏幕熄灭后自动任务怎么都跑不醒现象定时任务设置的是夜间两点执行日志显示脚本确实被系统调度起来了但所有节点操作全部失败提示无障碍服务不可用。手动解锁屏幕后再点一次任务执行又能正常完成任务。原因很多系统对无障碍服务有省电优化。屏幕熄灭一段时间后系统把包含服务进程的应用放进了“待机模式”进程还在但服务被冻结或者应用整体被暂停JS 常驻任务无法触发。定时任务由 JobScheduler 或系统闹钟拉起时进程被唤醒的速度和服务恢复绑定的速度之间存在竞态窗口。解决在一个源码工程里常做的方法是给应用申请“忽略电池优化”权限同时在系统设置里把应用加入“自启动名单”和“后台运行不受限制名单”。代码层可以在任务执行前先强制点亮屏幕并解锁让系统从待机状态完整恢复再执行自动化操作。点亮屏幕可以调用device.wakeUpIfNeeded()之类封好的接口如果没有现成接口就直接模拟电源键短按点亮后再继续。5.3 服务处于“已停止”状态开关怎么也打不开现象在系统无障碍列表里服务选项显示“已停止”或“耗电异常”点击开关后它会立刻弹回关闭状态甚至没有任何错误提示。重装 App 也一样只有重启手机后才能短暂恢复。原因这种情况高发于系统更新之后定制 ROM 对无障碍服务的管控策略变了例如新系统要求无障碍服务必须在前台运行时才能绑定或者要求服务声明新增权限字段。旧版本源码构建出的 APK 没有适配新规则绑定请求被系统静默拒绝。解决先在配置 XML 里补上android:isAccessibilityTooltrue这个字段告诉系统该服务属于辅助工具部分系统会因此走更宽松的白名单逻辑。接着检查 Manifest 里有没有申请FOREGROUND_SERVICE相关权限并把无障碍服务进程提升为前台服务防止被杀。如果你在源码里能定位到服务拉起逻辑改成在前台启动时主动请求绑定带上通知栏常驻通知掉线概率会明显降低。5.4 多开应用抢走了无障碍服务的绑定现象手机里有应用多开功能其中一个分身装了 Auto.js原版也装了。两个版本都出现在无障碍列表里但只能开启一个开启另一个时前一个自动关闭。脚本运行时的行为和预期不一致时好时坏。原因部分系统的无障碍框架限制同一应用签名下只能有一个服务实例处于绑定状态。两个包名不同但服务名相同的 APK 同时注册系统把它们视为同一服务的不同版本互相抢占绑定。这个问题在普通安装场景下不常见但自编译后改过包名的人很容易踩到。解决不要在同一个设备里保留两个自编译变体。如果必须共存把其中一个的service类名和配置 XML 改成另一个不同值像给服务换个“名字”一样让系统认为这是两个完全不同的服务。改名后注意混淆规则同步更新否则又会出现 3.3 小节里的问题。5.5 服务在线节点能找到但点击和滑动没有反应现象日志里findOne能正常拿到控件对象坐标也打出来了但执行click()后界面没有任何响应也没有报错。复杂手势gesture()有时生效有时不生效看着非常随机。原因节点能读到说明canRetrieveWindowContent没问题点击没反应大概率是手势注入权限没拿到或者注入的目标窗口不是当前活跃窗口。部分系统要求无障碍服务必须同时声明canPerformGestures才能在 Android 11 以上的设备上执行手势老版本构建产物只声明了节点读取权限自然只能看不能摸。另一个因素是目标界面处于半透明浮层或输入法弹窗上方注入坐标被其他窗口拦截。解决先确认配置 XML 里canPerformGestures是否为 true不是就改掉并重新编译。接着检查click()的坐标点是否属于有实际点击响应的区域比如某些控件只把“点击区域”绘制在一个小的子 View 上直接点击整个父布局的中心点是没有效果的。更稳的做法是调用控件对象的click()而不是先拿坐标再注入让 Auto.js 自己计算注入位置。6. 让无障碍服务长期在线每个早上的定时自检方案前面所有问题都指向一个事实无障碍服务不是一个“开一次就一劳永逸”的东西。源码配好了用户也开启了系统一次重启、一次省电清理、一次应用更新都可能把它从运行态里踢出去。与其等任务翻车了再去排查不如让脚本自己承担巡检职责。这里分享一个我常用的定时自检方案每天早上 8 点跑一个轻量脚本检查服务状态发现异常就跳转到设置页并弹窗提醒。代码很简单但能把很多隐患消灭在任务高峰之前。auto.waitFor(); // 自检重点不只是服务对象存在还要确认能读取窗口内容 let serviceOk auto.service ! null auto.service.isAccessibilityEnabled(); if (serviceOk) { // 再尝试获取当前界面的根节点能拿到才算真正可用 let root auto.service.getRootNode(); if (root null) { log(根节点为空服务可能处于休眠状态); serviceOk false; } } if (!serviceOk) { toast(自检发现无障碍服务异常正在打开设置页); app.startActivity({ action: android.settings.ACCESSIBILITY_SETTINGS }); } else { toast(无障碍服务运行正常); }为什么还要额外取一次根节点因为isAccessibilityEnabled()只代表服务被系统标记为运行中不代表它此刻能拿到界面节点。比如屏幕处于锁屏状态下无障碍服务虽然在线但当前窗口属于系统锁屏应用上下文里拿不到预期节点取一次root就能把这层休眠状态暴露出来。getRootNode()在节点不可用时通常返回 null不需要特殊处理。验证这套自检是否有效可以分三步第一保持服务开启运行脚本应输出“运行正常”且弹一次 toast第二手动关闭无障碍服务再运行脚本应看到自动跳转设置页第三开启服务但不解锁屏幕执行一次定时任务看是否能在日志里看到“根节点为空”的分支。三步都通过就说明这个脚本确实在监控真实可用性而不只是检查开关状态。我自己的习惯是把这段脚本挂成每日早上的定时任务同时把设备关闭“电池优化”。生活里总有意外比如某次手机重启后被丢在抽屉里服务一直没恢复等中午我拿起来才发现定时同步全失败了。从那以后我明白了一个道理任何依赖无障碍服务的自动化方案都应该先假定“服务一定会掉”再围绕这个假定设计兜底。这套自检脚本就是我的后悔药每天花不到五秒省去的不只是排查时间还有任务失败后的连带补救。希望帮到你。本文还有配套的精品资源点击获取