我第一次听说“clsq客户端”这个名字时,第一反应是某个内部工具,后来被朋友拉到一起折腾才发现,它背后真正有价值的东西其实是基于AutoJs 4.1.0的一套Android自动化脚本方案。AutoJs这个工具在国内Android圈子里名声很大,它是一个把JavaScript和Android无障碍服务结合在一起的自动化引擎,不需要root就能模拟点击、滑动、填表、读取界面控件,被人称作“手机上的按键精灵”。今天我不讲任何灰色玩法,就说清楚AutoJs 4.1.0到底是个什么东西、它的底层运行逻辑是什么、怎么装怎么配、怎么写第一个能跑的自动化脚本,以及各种让人头大的坑。想用Android做自动化测试、做个人效率工具,或者对无障碍机制感兴趣的朋友,这篇文章应该能给你一个比较完整的起点。
1. AutoJs 4.1.0 是什么,以及它到底能干什么
1.1 用一句话先建立认知
AutoJs是一个运行在Android系统上的JavaScript自动化工具,它的本质是一个APK应用,应用内部内置了JavaScript引擎和一套封装好的Android自动化API。你写的每一行JS代码,最终都会通过无障碍服务(AccessibilityService)转成系统级别的UI操作,比如点击某个坐标、滑动屏幕、在输入框里填入文字、按返回键等。
用生活化的比喻来说,AutoJs就像给手机请了一个“看不见的机械手臂”,而这个手臂的控制器是JavaScript脚本。你不用去改系统、不用刷机、也不用root,只需要在系统设置里打开“无障碍服务”,AutoJs就能读取当前屏幕的控件信息,并且代替你的手指去操作手机。
那么“clsq客户端”扮演什么角色?从我们当时的项目实践来看,它更像是一个基于AutoJs的定制化封装项目:把AutoJs的脚本能力收进一个独立的客户端壳里,对外提供更简洁的交互入口,核心内核仍然是AutoJs 4.1.0那套运行机制。所以本质上,你要理解这套东西,学AutoJs准没错。
1.2 在哪些场景里真正用得上
我先把能说、且合规的典型使用场景列出来,再看它的能力边界。
第一类是自动化测试。App在上线之前经常要做反复的冒烟测试,比如一直重复“点击登录-输入错误密码-查看提示-返回”,这类工作让测试人员手工点几千次是很痛苦的。用AutoJs写一个循环脚本,就能让手机自己去跑,跑完输出日志,方便判断有没有崩溃。
第二类是个人效率工具。比如每天上班前自动打开某个办公App,点进某个应用,完成打卡操作;又比如批量整理手机里的照片文件,把特定目录下的文件按规则重命名、移动。AutoJs可以通过文件API操作本地存储,配合界面自动化能做到“全自动流水线”。
第三类是辅助Android开发者做界面调试。AutoJs自带“布局分析”功能,能看到当前界面的控件树,包括控件的id、text、desc、className等属性。这有点像Android Studio里的Layout Inspector,但用起来更轻便,不需要连接电脑,直接在手机上就能看。
不过AutoJs的边界也很清楚:它不应该也最好不要被用来做破解、刷量、外挂、群控灰产等事情。这类行为既不稳定(很容易被风控识别),也有明确的合规风险。我后文所有示例都围绕“你自己设备上的、合规的、个人可操作的界面流程”来展开。
1.3 4.1.0 这个版本有什么讲究
AutoJs现在版本站有很多,4.1.0是其中流传非常广的一个稳定版本。网上能看到大量基于这个版本的脚本和教程,生态比较成熟。
从技术角度说,AutoJs 4.1.0有几个特点:
- 基于Rhino作为JavaScript引擎,对ES5支持很完整,ES6的部分特性也兼容,但很多新语法(比如箭头函数、async/await)支持有限,写脚本时尽量用ES5风格更稳。
- 内置了强大的控件选择器API,支持text、id、desc、className、bounds等属性组合查找,这是它相比传统“坐标脚本”最大的优势。
- 提供打包功能,可以把你的JS脚本打包成独立APK,做成一个不依赖AutoJs主程序的成品工具。
- 基于Android目标版本比较老,在Android 10以上系统可能存在文件权限、悬浮窗显示等兼容问题。
现在社区里也有很多从AutoJs fork出来的分支项目,比如AutoX.js、AutoJs6等,它们主要是补齐了新版Android的适配,API基本兼容。如果你想长期维护项目,我建议你直接在AutoJs 4.1.0上把基础概念弄清楚,再迁移到新分支也很容易,迁移成本主要就是权限初始化那部分代码。
2. 运行原理:AutoJs 是怎么“看见”屏幕、执行操作的
2.1 无障碍服务:系统的“眼睛”和“手”
要理解AutoJs,绕不开“无障碍服务”这个概念。它原本是Android系统为视力障碍、肢体障碍用户设计的一套辅助能力,比如屏幕朗读、焦点导航、手势替代等。对这个服务,系统赋予了很高的权限:它可以监听窗口变化、读取当前窗口的控件树、模拟手势操作。
AutoJs做的事情,就是以一个“无障碍服务”的身份运行在系统里。当你在脚本里写一句text("登录").click()时,AutoJs会通过无障碍服务去查询当前界面上所有控件的属性,找到一个text恰好是“登录”的控件节点,再调用无障碍API对这个节点执行点击动作。
这里有个关键点:点击是控件级的,不是像素级的。像素级脚本只是记录某个坐标点,然后模拟触摸;控件级脚本是根据UI控件属性动态寻找目标,哪怕界面布局变了,只要控件的text或id不变,脚本依然能点对位置。这也是AutoJs比传统“脚本精灵”一类工具稳定得多的根本原因。
当然,如果你面对的是游戏画面、视频画面这种没有控件树的环境,AutoJs就只能退回成像素级操作,靠坐标和颜色识别来工作,这就很不稳定了,我在后面会说。
2.2 JavaScript 运行环境与内置 API
AutoJs 4.1.0内部集成了Rhino引擎,可以在Android上执行JavaScript。所谓“内置API”,是AutoJs对这个JS环境做的扩展,让脚本能调用Android系统和UI自动化相关的能力。
比如最常用的一组API大致包括:
sleep(milliseconds):线程休眠,控制操作间隔。toast(message):显示一个短暂的提示气泡。click(x, y):点击屏幕坐标。swipe(x1, y1, x2, y2, duration):模拟滑动。text("xxx")、id("xxx")、desc("xxx")、className("xxx"):构建控件选择器。findOne(timeout):在当前界面查找符合条件的控件,可以指定超时时间。launch("com.example.app"):通过包名启动应用。readFile(path)、writeFile(path, content):读写文本文件。images.captureScreen():截图,配合图像识别API可以做简单的找图、找色。
这些API和原生Android开发中的Activity、Service、View体系联系非常紧密。如果你有Android开发经验,看AutoJs的API会觉得很亲切;如果完全没有,那就把它当成一套“手机操控指令集”来记忆就好。
2.3 存储与 content:// 的边界问题
网上很多教程在讲AutoJs文件操作时会提到/storage/emulated/0/Android/data/这类路径,或者content://形式的URI。这部分容易踩坑,也特别容易产生合规性问题,我单独说一下。
content://是Android的ContentProvider机制,用来跨应用共享数据。比如相册App可以让其他应用通过content://media/...访问图片,文件管理器通过content://com.android.externalstorage.documents/...来浏览文件。AutoJs支持通过ContentResolver去查询和操作这些URI,但注意,它只能访问“该应用有权限访问的URI”。
从Android 10开始,系统强制分区存储,每个应用只能方便地访问自己的专属目录和公共媒体目录,第三方应用的Android/data目录限制非常严格。网上偶尔能看到一段“神奇脚本”,宣称能读取微信、QQ、抖音等App的私有目录,这种基本要么过时、要么靠root绕过,要么涉及非常规授权路径。这里我不展开任何操作方法,因为从合规和安全角度看都是高风险行为:轻则被系统拦截,重则涉及侵犯他人数据。
我的建议很简单:脚本里尽量只处理自己创建的目录和文件,比如AutoJs自己的工作目录/sdcard/脚本/,或者公共存储下的自定义文件夹。你在自己的设备上处理自己的文件,这才是一个真实从业者该守住的底线。
3. 安装配置与准备工作(含 Android Studio 视角)
3.1 Apk 安装、权限开启与首次引导
AutoJs 4.1.0的安装方式很传统:下载APK包,然后在手机上允许“安装未知来源应用”,点击安装即可。装完后首次打开,它会引导你开启两个关键权限:无障碍服务和悬浮窗。
无障碍服务在系统设置里的路径一般是“设置-辅助功能-已安装的服务-AutoJs”,不同厂商ROM表述略有差异,比如小米叫“无障碍-已下载的服务”,华为在“辅助功能-无障碍”。你需要把对应的开关打开,系统会弹一个“是否允许AutoJs监控您的操作并执行手势”的确认框,这里必须点允许,否则脚本跑不了。
悬浮窗权限在安卓里通常叫“显示在其他应用上层”,因为AutoJs的悬浮控制台、悬浮按钮都依赖这个权限。在MIUI、EMUI、ColorOS这类定制ROM里,还需要额外把AutoJs设置为“允许后台弹出界面”、“允许自启动”,否则手机息屏或后台清理时,脚本会被干掉。
按照我个人经验,新手最容易卡的不是安装,而是权限开启后没有真正生效。一个有效检查方法是:打开AutoJs,在控制台输入auto()并运行,如果返回true并且悬浮窗出现,说明无障碍服务已经正常连接;如果报错,再去设置里确认开关状态。
3.2 用开发者选项辅助脚本调试
我强烈建议你在开发调试阶段,打开Android的开发者选项。具体路径是“设置-关于手机-连续点击版本号7次”,然后在开发者选项里找到“指针位置”和“显示触摸操作”。
开启指针位置后,屏幕上会实时显示当前触摸点的坐标,并且顶部状态栏会展示按压坐标值。这非常重要,因为写坐标脚本时,你不可能靠肉眼估算坐标,有了指针位置功能,你就能精确知道按钮在屏幕上的x和y值。
“显示触摸操作”会以小白点和轨迹的方式展示每次触摸事件,便于你观察脚本点击是否真的生效、点击位置是否偏了。对用AutoJs做“坐标型脚本”调试来说,这两个开关能省掉大量时间。
当然,这只是辅助手段,真正的自动化脚本不应该依赖坐标值来保证稳定,而是应该优先使用控件选择器。坐标脚本只适合游戏、视频等没有UI控件的场景。
3.3 AutoJs 与 Android Studio:同源不同路
很多接触Android开发的人会疑惑:AutoJs和Android Studio有什么关系?我的理解是,AutoJs本质上是一个已经写好的Android工程项目,它把JS引擎和无障碍服务封装好了,你只需要在上面写JS脚本即可;而Android Studio是更底层的Android原生开发环境,你可以自己写一个类似AutoJs的应用。
从学习角度,你可以在Android Studio里打开AutoJs的开源分支项目,看看它是如何注册AccessibilityService的,如何把JS的click()方法映射到无障碍节点的performAction()调用。这一步对加深理解很有用,但并不是写AutoJs脚本的前置条件。
从工程落地角度,AutoJs 4.1.0提供了“打包APK”的功能。你可以把写好的JS脚本打包成一个独立的Android应用,安装到手机上直接运行,而不需要先安装AutoJs主程序。打包出来的APK在首次启动时,依然需要用户手动打开无障碍服务,这是Android系统的安全要求,任何应用都无法绕过。
所以我的建议是:如果你只是做自动化,就直接在AutoJs上面写脚本;如果你想把自动化能力做成产品,那可以看Android Studio项目源码,把AutoJs内核嵌入你自己的App里,做好权限引导和UI界面,这就是“clsq客户端”那类项目的技术路径来源。
4. 手把手写第一个脚本:坐标点击、控件选择与自动登录
4.1 环境准备:新建文件、运行脚本、查看日志
打开AutoJs 4.1.0之后,首页是一个文件管理器,默认目录是/sdcard/脚本/。你可以点击右下角的“+”新建一个JS文件,取名叫hello.js,然后编辑器会自动打开。
AutoJs的脚本运行方式有两种:
- 直接在编辑器中点击“运行”,脚本会在当前环境执行。
- 通过悬浮窗上的按钮运行,适合退出编辑器后用。
日志查看入口在软件的抽屉菜单里,叫“日志”。日志窗口会显示console.log()输出的内容,也会显示脚本运行时的错误堆栈。写脚本时,我习惯在关键节点打日志,这是我排查问题的主要手段。
第一次运行,可以写一个最简单的脚本:
auto(); toast("hello autojs"); console.log("脚本运行成功"); sleep(2000);auto()的作用是请求无障碍服务权限,检测服务是否可用。如果服务没开启,运行到这里会弹出提示并自动跳转设置页。toast会在屏幕底部弹一个气泡,“console.log”会把内容输出到日志面板。
4.2 坐标操作:click、swipe 与 sleep 的组合
坐标操作是最直观的自动化方式。点击某个位置用click(x, y),滑动用swipe(x1, y1, x2, y2, 时长毫秒)。比如我想打开一个桌面上的App图标,假设图标坐标是(540, 1200),可以写:
auto(); click(540, 1200); sleep(3000);这里的sleep(3000)非常关键。界面切换、页面加载都需要时间,如果点击之后马上执行下一步操作,大概率会因为控件还没出现而失败。通常我建议,页面跳转后至少等2到3秒,再继续操作。
还有一个实用API是press(x, y, duration),它模拟“长按”,比如长按图标触发编辑模式。坐标脚本写起来简单,但缺点很明显:换一台不同分辨率的手机,所有坐标全部失效;应用改版后按钮位置变了,脚本也得跟着改。所以能用控件选择器的地方,就尽量不要用纯坐标。
4.3 控件选择器:让脚本稳定复用
控件选择器是AutoJs的灵魂。它的用法很直观:先用text()、id()、desc()等函数描述你要找的控件,再用.findOne()去查找,找到后调用.click()执行点击。
比如当前界面有一个按钮,文本内容是“同意”,我想点击它,可以写:
auto(); var button = text("同意").findOne(5000); if (button) { button.click(); console.log("点击了同意按钮"); } else { console.log("没有找到同意按钮"); }这里findOne(5000)的含义是:在5秒内持续查找,每100毫秒左右刷新一次控件树,直到找到第一个符合条件的控件。如果超时还没找到,会返回null,所以要做空值判断,否则下一行调用.click()会直接报错。
desc()通常用于查找那些没有文本但有“内容描述”的控件,比如图片按钮。id()则用于查找带资源ID的控件,ID一般长成这样:com.example.app:id/btn_login。AutoJs会自动识别当前前台App的包名,所以写ID时可以直接去掉前面部分,比如id("btn_login")。
另外还有className("android.widget.Button")这种按控件类型查找的写法。实际开发中,组合使用更有效,比如:
var loginButton = className("android.widget.Button").text("登录").findOne(3000);这样既限定了类型,又限定了文本,可以有效避免界面上存在多个“登录”文本的干扰。
4.4 实战:自动打开应用并完成一次登录
下面我给出一个完整的实战示例,场景很简单:Android手机上安装了一个自己开发的测试应用,包名是com.example.demo,登录页有账号输入框、密码输入框、登录按钮。我们的目标是:用AutoJs打开这个应用,输入账号密码,点击登录,并判断是否登录成功。
完整脚本如下:
auto(); // 启动应用 launch("com.example.demo"); sleep(3000); // 查找账号输入框:根据hint提示文本“请输入账号”定位 var accountInput = hint("请输入账号").findOne(5000); if (accountInput) { accountInput.click(); accountInput.setText("testuser001"); console.log("账号输入成功"); } else { console.log("未找到账号输入框"); exit(); } sleep(1000); // 查找密码输入框 var passwordInput = hint("请输入密码").findOne(5000); if (passwordInput) { passwordInput.click(); passwordInput.setText("123456"); console.log("密码输入成功"); } else { console.log("未找到密码输入框"); exit(); } sleep(1000); // 点击登录按钮 var loginBtn = text("登录").findOne(5000); if (loginBtn) { loginBtn.click(); console.log("已点击登录"); } else { console.log("未找到登录按钮"); exit(); } // 等待页面跳转,然后判断是否出现欢迎文字 sleep(4000); var welcome = text("欢迎回来").findOne(2000); if (welcome) { console.log("登录成功,已进入主界面"); } else { console.log("登录可能失败,请检查账号密码"); }注意事项有几个:
setText()不是所有输入框都能生效,部分自定义键盘或安全键盘会拦截,这时候需要改用inputText()或者通过剪贴板粘贴的方式。- 账号密码这种敏感信息建议只在本地脚本里保存,如果要发布给别人,不要把明文密码写在脚本里,否则会泄露。
hint()查找方式是AutoJs提供的一个很实用的API,专用于查找带placeholder提示文字的输入框,比用className定位更可靠。
5. 常见问题与排查经验
5.1 无障碍服务频繁失效
用户反馈最多的就是:脚本跑着跑着突然没反应了,打开设置一看,AutoJs的无障碍服务被系统自动关闭了。这种情况在国内手机上尤其常见,各种ROM为了省电和安全,会把“长时间不使用的无障碍服务”自动回收掉。
解决思路主要有三层:
- 第一,在AutoJs内部设置里开启“保持唤醒”或“防止服务被回收”,不同版本名称不同,本质是申请后台运行权限。
- 第二,在系统设置里把AutoJs加入电池优化白名单,禁止系统休眠时杀掉它。路径通常是“设置-电池-应用省电策略-选择不限制”。
- 第三,在最近任务列表里把AutoJs锁定,避免用户或系统清理后台时把它划掉。
除此之外,个别ROM还有“读取已安装应用列表”权限、自启动权限等,需要一并授权。做完这些之后,稳定性会好很多,但仍然不能保证100%不被系统回收,所以脚本里最好做好异常重启机制。比如在脚本入口判断auto()返回值,如果服务不可用就用toast提醒用户去开启。
5.2 控件定位不到或选择器超时
控件找不到几乎每个用AutoJs的人都会遇到。最常见的几个原因,我按出现频率排序:
- 页面还没加载完,控件不够5秒还没出现。解决方法是把
findOne()的超时时间从5000改成10000,或者先用sleep()做固定等待,再查找。 - 控件属性不对。有些按钮显示的文字不是存在
text属性里,而是存在content-desc里,或者界面用了自定义View,控件树里根本没有文本信息。这种要用AutoJs的布局分析功能看一下真实属性。 - 目标App是网页内容,运行在WebView里。WebView的控件树在无障碍服务下呈现得并不完整,可能需要切换到
accessibility模式或者用坐标方案兜底。 - 屏幕上有弹窗遮住了目标控件,比如登录页的隐私协议弹窗。这时候要先关闭弹窗,再继续执行后续操作。
排查技巧:在脚本里捕获异常并打印控件树。AutoJs提供了currentPackage()获取当前前台应用包名,currentActivity()获取当前Activity。你也可以用clickable()、editable()等方法去遍历当前页面的所有可点击控件,把它们的文本信息全部打印出来,再决定选择器怎么写。
5.3 Android 版本差异与文件访问限制
AutoJs 4.1.0发布时面向的系统还没那么新,放到现在,Android 10、11、12、13、14上都会遇到版本兼容问题。
首先是文件路径。Android 10以前,直接写/sdcard/xxx读文件没有问题;Android 10以后,应用要访问公共目录必须申请存储权限,并且很多敏感目录访问会受到拦截。AutoJs 4.1.0在部分Android 11以上设备上,会出现“无法读取本地文件”的报错,原因是targetSdkVersion过低,系统直接把它当作旧应用处理。社区给出的临时方案是手动去设置里给AutoJs授予“所有文件访问权限”,路径是“设置-应用-AutoJs-权限-文件和媒体-允许管理所有文件”。
其次是前台服务限制。从Android 8开始,后台应用不能随便启动前台服务;Android 12之后,如果应用开启无障碍服务,系统会频繁弹出“AutoJs正在运行”的通知,这是正常现象,不要关掉通知,关掉可能导致服务被系统踢掉。
第三是悬浮窗限制。在Android 10以上全屏展示时,悬浮窗容易被系统自动隐藏,需要手动点击“显示”按钮或者重新触发脚本的悬浮窗开关。如果发现悬浮窗按钮突然消失,优先检查“显示在其他应用上层”权限是否仍然有效。
5.4 调试技巧:日志、截图与异常落盘
我给AutoJs脚本做调试时,最重要的三个习惯是:多打日志、多次截图、异常落盘。
多打日志不用多解释,每执行完一个关键步骤加一条console.log(),能快速定位脚本卡在哪一步。截图比日志更能还原现场,AutoJs 4.1.0可以用images.captureScreen()抓取当前屏幕,把它保存成文件:
var img = images.captureScreen(); var time = new Date().getTime(); images.save(img, "/sdcard/脚本/screenshot_" + time + ".png"); img.recycle();异常落盘的意思是,在脚本外层套一个try...catch,把捕获到的错误堆栈写到本地日志文件中,避免屏幕没盯着的时候丢失信息:
try { main(); } catch (e) { var logFile = "/sdcard/脚本/error.log"; files.append(logFile, new Date().toString() + " " + e + "\n"); toast("执行异常,详情见日志"); }这些习惯能在脚本上百次运行中帮你快速定位偶发问题,而不是靠肉眼盯屏。
6. 从 AutoJs 出去:脚本打包、原生能力与 AI 扩展
6.1 把脚本打包成独立 APK
如果你的脚本给别人用,让人家先装AutoJs再导入脚本,体验很不好。AutoJs 4.1.0内置了打包功能:打开脚本文件,点击菜单里的“打包APK”,设置好应用名称、图标、包名,就能生成一个独立安装包。
打出来的APK运行原理和AutoJs相同,仍然需要无障碍服务授权,但用户不需要理解JS、不需要碰脚本文件。这其实就是“clsq客户端”这类定制化客户端的一种做法:里面跑的是AutoJs内核,外面包了一层自己的产品外壳,用户看到的是一个独立的App。
打包的时候需要注意签名问题。生成APK时AutoJs默认会使用它的调试签名,这种签名无法上架Google Play等正规渠道,也不能用于覆盖安装已发布的正式包。如果要长期维护,建议在Android Studio里把AutoJs内核工程拷出来,换成自己的签名配置重新构建。
6.2 脚本调用原生能力与 AI 模型的衔接
AutoJs虽然强大,但毕竟运行在JS层,访问硬件和一些深度系统API时会有局限。这时候可以用AutoJs的adb命令配合,或者在打包时通过Android Studio给应用增加原生模块,由JS通过桥梁调用。
举一个我实际探索过的方向:在Android上本地运行AI大模型。现在社区里很流行用GGUF格式的量化模型配合llama.cpp在手机上跑,AutoJs脚本可以做“调度层”,负责监听界面变化、读取输入、把用户输入交给一个本地HTTP服务,再由服务调用大模型生成结果,最后把结果通过AutoJs的UI控件回填到界面里。脚本能力和AI能力的这个组合,能解决不少真实场景的问题。
具体到工程结构,可以这样设计:Android原生应用内置一个轻量级Web服务,AutoJs用http.get("http://127.0.0.1:8080/api/generate?q=xxx")发起请求,拿到结果后再执行界面操作。AutoJs 4.1.0内置了http模块,发送GET和POST请求都很方便。
这类扩展的方向潜力很大,但入口还是得先把AutoJs的基础原理吃透,知道它哪能干、哪不能干,才能把自动化脚本真正用对地方。
最后分享一点我个人的体会:使用AutoJs写脚本,最核心的竞争力不在于API背了多少,而在于你愿不愿意打开布局分析去读真实的控件树,愿不愿意一遍遍调试、把每一个“偶尔失败”的原因都排查清楚。做自动化这件事,一个人能写出来能跑的脚本不算本事,能把脚本在不同系统、不同版本上都跑稳,才算真正入了门。