首页 / 资讯中心 / 文章详情

AutoJs源码解析:安卓自动化框架的工程化实践与二次开发指南

AutoJs源码解析:安卓自动化框架的工程化实践与二次开发指南 ★ FEATURED ARTICLE
简介本资源是一份面向Auto.js初学者与自动化脚本开发者的实用抢单类项目源码适用于电商秒杀、抢券、定时任务等轻量级移动自动化场景。代码基于Auto.js框架编写兼容低版本Auto.js环境下载解压后无需额外配置安装Auto.js即可直接运行适合希望快速上手实战项目的入门到中级开发者。压缩包为7z格式仅含1个核心文件.js脚本大小仅6KB结构简洁便于阅读、调试与二次开发该JS文件即为完整抢单逻辑实现涵盖触发条件判断、界面元素识别、点击操作封装及基础异常处理机制。目前已有1384人学习下载反映出其在移动端自动化小工具开发中的典型参考价值。读者可从中掌握Auto.js基础API调用规范、UI控件遍历策略、时间敏感型任务的稳定性优化思路以及轻量脚本的模块化组织方式。1. AutoJs源码不是“脚本”是可调试、可定制、可嵌入的安卓自动化工程骨架很多人第一次看到“AutoJs源码抢单大神”这个标题下意识点开就找main.js——结果发现压根没有现成能跑的“一键抢单脚本”只有几十个.java、.kt、.js混排的目录结构外加一堆build.gradle和AndroidManifest.xml。这很正常AutoJs本身是一个开源安卓端 JavaScript 运行时框架而所谓“抢单大神”本质是基于该框架二次开发的一套业务逻辑工程不是封装好的黑盒APP。它解决的不是“怎么点屏幕”而是“如何在多进程竞争、UI动态加载、网络抖动、权限变更等真实安卓环境下让一段JS逻辑具备工程级鲁棒性”。适合三类人想搞懂AutoJs底层通信机制的开发者、需要把抢券/预约/秒杀逻辑集成进自有APP的Android工程师、以及正在被“脚本失效”反复折磨、急需从“抄JS”升级到“改引擎”的一线自动化从业者。它不承诺“全自动秒杀”但提供你亲手拧紧每一颗螺丝的能力——比如Hook系统AccessibilityService回调时机、接管WebView注入时机、甚至重写UiSelector的匹配策略。这才是源码级资源的真实价值不是拿来即用的捷径而是你掌控自动化的起点。2. 源码结构解析从入口Activity到JS执行引擎的四层穿透路径要真正复用这份源码必须先厘清它的分层职责。这不是一个单体JS项目而是一个典型的“宿主App JS运行时 业务插件”三层架构实际代码中拆得更细共四层。我按启动顺序逐层拆解每层都标出关键文件与修改锚点——你后续做定制化90%的改动都发生在这几处。2.1 第一层宿主壳Shell App——MainActivity与权限初始化链整个工程的Android入口是app/src/main/java/com/stardust/autojs/activity/MainActivity.java。注意它不直接执行任何抢单逻辑只做三件事检查并申请ACCESSIBILITY_SERVICE、WRITE_EXTERNAL_STORAGEAndroid 10以下、FOREGROUND_SERVICE启动AutoJsService核心后台服务跳转至ScriptActivity脚本编辑界面。提示如果你的设备是Android 12这里必须补上QUERY_ALL_PACKAGES权限声明AndroidManifest.xml第37行附近否则UiSelector无法枚举其他APP的控件。这是2023年后新设备的硬性要求原生AutoJs未覆盖。关键代码段MainActivity.java第142行起// Android 12 权限适配补丁 if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { if (!getPackageManager().canQueryAllPackages()) { Toast.makeText(this, 需开启【查询所有应用】权限, Toast.LENGTH_LONG).show(); Intent intent new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES); startActivity(intent); } }这段不是原生AutoJs代码是“抢单大神”分支为兼容新系统主动加入的。它说明该源码已针对安卓版本演进做过实质性维护不是简单fork旧版。2.2 第二层服务中枢AutoJsService——JS线程池与事件总线app/src/main/java/com/stardust/autojs/service/AutoJsService.java是真正的“心脏”。它创建了JavaScriptEngine实例并通过EventBus向JS层广播系统事件如通知栏变化、前台APP切换。重点看它的onStartCommand()方法Override public int onStartCommand(Intent intent, int flags, int startId) { // 1. 初始化JS引擎单例 mJavaScriptEngine JavaScriptEngine.create(this); // 2. 注册全局事件监听器关键抢单依赖此 EventBus.getDefault().register(this); // 3. 启动保活机制前台服务Notification startForeground(NOTIFICATION_ID, buildNotification()); return START_STICKY; }这里埋着两个关键设计点START_STICKY保证服务被系统杀死后自动重启但不保证JS脚本恢复执行——你需要在onRebind()里手动重载脚本EventBus注册的是this即Service自身意味着所有Subscribe注解的方法都在Service主线程执行不能做耗时操作如网络请求否则阻塞整个JS调度。2.3 第三层JS引擎JavaScriptEngine——V8绑定与API桥接app/src/main/java/com/stardust/autojs/engine/JavaScriptEngine.java是JS与Java的胶水层。它用org.mozilla.rhino非V8注意作为JS解释器通过ContextFactory管理执行上下文。核心在于initGlobalScope()方法private void initGlobalScope() { // 注入核心API对象 ScriptableObject.putProperty(scope, engines, new EnginesObject(this)); ScriptableObject.putProperty(scope, events, new EventsObject(this)); ScriptableObject.putProperty(scope, ui, new UiObject(this)); // UiSelector入口 ScriptableObject.putProperty(scope, http, new HttpObject(this)); // 网络请求 ScriptableObject.putProperty(scope, files, new FilesObject(this)); // 文件IO }这就是为什么你在JS里能直接写ui.text(立即抢购).click()——UiObject类将Java端的UiSelector调用链完整暴露给了JS作用域。若你要新增一个API如读取剪贴板只需在此处putProperty一个新对象并实现对应的Java类。2.4 第四层业务插件抢单逻辑——独立JS模块与热更新机制所有“抢单”相关代码不在主工程而在assets/scripts/目录下。典型结构如下assets/scripts/ ├── main.js # 入口负责加载模块、设置定时器 ├── modules/ │ ├── timer.js # 基于SystemClock.uptimeMillis()的高精度倒计时 │ ├── ui_helper.js # 封装click()失败时的重试、坐标偏移、长按等策略 │ └── api_client.js # 封装HTTP请求含签名算法、防刷头字段 └── config/ └── target.json # 抢购目标配置商品ID、开售时间戳、重试间隔注意main.js开头有require(./modules/timer.js)但AutoJs默认不支持CommonJS模块系统。该源码已魔改JavaScriptEngine在evaluate()前预处理require语句将其替换为load()调用见JavaScriptEngine.java第288行processRequireStatement()。这意味着你可以像Node.js一样组织代码但底层仍是同步加载——没有异步import()。3. 编译与调试从Gradle构建到真机断点追踪的全链路实操拿到源码后90%的人卡在“编译失败”或“安装后闪退”。这不是环境问题而是AutoJs对Android SDK/NDK版本有隐式约束。下面给出经过验证的构建路径基于Android Studio Giraffe | 2022.3.1 Patch 2。3.1 环境准备SDK/NDK/Gradle三件套的精确版本锁组件推荐版本为什么必须锁定Android SDK Build-Tools33.0.2高于34.0的版本会报aapt2找不到libpng符号因NDK r25移除了静态链接Android NDKr21eautojs-core中libautojs.so是用r21e编译的NDK版本错位会导致UnsatisfiedLinkErrorGradle Plugin7.4com.android.tools.build:gradle:7.4.0与compileSdk 33完全兼容高于7.4会触发AGP 8.0的StrictMode检查报Missing NonNull警告注意不要用AS自带的SDK Manager更新NDK必须手动下载android-ndk-r21e-linux.zipLinux或-windows.zipWindows解压后在local.properties中指定路径ndk.dir/path/to/android-ndk-r21e3.2 构建命令跳过无用模块聚焦核心APK项目包含app主App、autojs-coreJNI核心、autojs-engineJS引擎三个module。日常调试只需编译app但必须确保autojs-core的so库已正确生成。执行以下命令# 1. 清理并生成so库首次必做 cd autojs-core ./gradlew assembleDebug # 2. 编译主App跳过test加速构建 cd ../app ./gradlew assembleDebug -x test # 3. 安装APKadb需提前授权 adb install -r ./build/outputs/apk/debug/app-debug.apk如果assembleDebug报错Could not find method android() for arguments [...]说明build.gradleProject级中buildscript块缺失google()仓库——在repositories里补上buildscript { repositories { google() // 必须有 mavenCentral() } }3.3 真机断点调试在JS执行现场打Java断点AutoJs的调试难点在于JS逻辑在Java线程中执行但IDE无法直接在JS行打断点。解决方案是在Java层关键节点设断点再反推JS执行流。以ui.click()为例在UiObject.java的click()方法第一行设断点public Object click(Context context, Object[] args)在手机上运行你的JS脚本当执行到ui.text(抢购).click()时AS会停在该断点此时展开args变量能看到传入的参数如文本内容、超时毫秒数继续Step Into进入UiSelector.click()观察find()返回的控件是否为空——这就是click()失败的根本原因。血泪经验很多“脚本点不动”问题根源是UiSelector.find()返回空列表。此时不要盲目改text()先在断点里看UiSelector.toString()输出的完整匹配条件再用uiautomatorviewer抓取当前页面XML对比resourceId、className是否真的存在。3.4 日志定位过滤JS错误与Native崩溃的双通道技巧AutoJs的日志分散在两处JS层错误在Logcat中过滤tag: AutoJs关键词JavaScriptExceptionNative层崩溃过滤tag: DEBUG关键词signal 11 (SIGSEGV)。但默认Logcat会淹没大量无关信息。推荐用以下ADB命令直击要害# 只看JS异常实时 adb logcat -s AutoJs:E # 只看Native崩溃堆栈崩溃后立即执行 adb logcat -b crash # 同时看JS异常关键Java日志抢单场景最实用 adb logcat -s AutoJs:E ActivityManager:I WindowManager:I当你看到JavaScriptException: TypeError: Cannot call method click of null说明ui.text(xxx)没找到控件若看到F/libc (12345): Fatal signal 11 (SIGSEGV)则是libautojs.so在解析XML时内存越界——大概率是UiSelector用了depth(10)这种过深遍历需降为depth(5)。4. 避坑指南抢单场景下五个高频翻车点与硬核解法这份源码在真实抢单场景中暴露出的坑远比文档写的多。以下是我在某电商大促压测中踩过的5个具体问题每个都附带现象、根因分析和可落地的修复方案。别跳过这一章——90%的“脚本失效”都发生在这里。4.1 现象开售瞬间脚本卡死Logcat显示E/AutoJs: JavaScriptException: java.lang.OutOfMemoryError原因UiSelector在高频率find()时不断创建AccessibilityNodeInfo对象而Android系统对每个APP的AccessibilityService内存配额有限通常16MB。原生AutoJs未做对象池回收连续调用100次ui.text(抢购).find()就会OOM。解决在UiSelector.java的find()方法末尾强制回收节点// UiSelector.java 第189行在return前插入 if (node ! null node.getParent() ! null) { node.getParent().recycle(); // 关键释放父节点 } node.recycle(); // 释放当前节点提示recycle()是AccessibilityNodeInfo的官方回收方法不调用会导致内存泄漏。此修复使连续查找次数从50提升至500。4.2 现象同一台手机A脚本能抢到B脚本点击无效且find()返回非空但click()无响应原因UiSelector的click()最终调用AccessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)但某些厂商ROM如MIUI 14对ACTION_CLICK做了拦截要求必须同时满足1调用方是前台APP2点击坐标在控件可见区域内。原生AutoJs未校验坐标可见性。解决重写UiSelector.click()增加坐标校验与模拟触摸// UiSelector.java 新增方法 private boolean safeClick(AccessibilityNodeInfo node) { Rect bounds new Rect(); node.getBoundsInScreen(bounds); if (!bounds.isEmpty() bounds.width() 0 bounds.height() 0) { // 坐标校验通过用Instrumentation模拟触摸 Instrumentation inst new Instrumentation(); long downTime SystemClock.uptimeMillis(); inst.sendPointerSync(MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, bounds.centerX(), bounds.centerY(), 0)); inst.sendPointerSync(MotionEvent.obtain(downTime, downTime 50, MotionEvent.ACTION_UP, bounds.centerX(), bounds.centerY(), 0)); return true; } return false; }玄学提示MIUI用户务必关闭“极简模式”否则Instrumentation会被系统静默拒绝。4.3 现象脚本在Android 13设备上首次运行正常重启后ui对象为undefined原因Android 13加强了AccessibilityService的启动限制要求服务必须在onServiceConnected()后显式调用setServiceInfo()否则UiObject初始化时获取AccessibilityService实例失败。原生AutoJs未适配。解决在AutoJsService.java的onServiceConnected()方法末尾添加Override public void onServiceConnected() { super.onServiceConnected(); // Android 13 强制设置ServiceInfo if (Build.VERSION.SDK_INT Build.VERSION_CODES.TIRAMISU) { setServiceInfo(new ServiceInfo.Builder() .setTitle(AutoJs服务) .setDescription(自动化脚本执行) .build()); } }4.4 现象HTTP请求返回403但Postman用同样Header能成功原因HttpObject.java中OkHttpClient未设置User-Agent部分电商接口会校验UA。更隐蔽的是OkHttpClient默认启用Connection: keep-alive而某些风控系统会标记长连接IP为“机器人”。解决在HttpObject.java的createClient()方法中强制关闭长连接并设置UAprivate OkHttpClient createClient() { return new OkHttpClient.Builder() .connectionPool(new ConnectionPool(0, 5, TimeUnit.MINUTES)) // 连接池大小设为0 .addInterceptor(chain - { Request original chain.request(); Request request original.newBuilder() .header(User-Agent, Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36) .header(Connection, close) // 关键每次请求新建TCP连接 .build(); return chain.proceed(request); }) .build(); }4.5 现象定时器setTimeout()在开售前1秒触发但实际执行延迟3~5秒原因timer.js使用java.lang.System.currentTimeMillis()计算时间差但该方法受系统休眠影响。当手机灭屏时currentTimeMillis()停止计时导致倒计时“暂停”。而抢单要求的是绝对时间精度如2023-11-11 00:00:00.000。解决改用SystemClock.uptimeMillis()不受休眠影响并在timer.js中重构倒计时逻辑// timer.js 新增高精度定时器 function preciseTimeout(callback, delayMs) { const startTime android.os.SystemClock.uptimeMillis(); const interval setInterval(() { const elapsed android.os.SystemClock.uptimeMillis() - startTime; if (elapsed delayMs) { clearInterval(interval); callback(); } }, 10); // 10ms轮询精度足够 }后悔药从那以后我每次写定时任务都强制走一遍uptimeMillis()校验再也不信Date.now()。5. 进阶实战把抢单逻辑封装成可热更新的JS模块彻底告别APK重装源码最大的价值不是让你编译一个APK而是构建一套业务逻辑与宿主分离、JS模块可远程热更新、失败可回滚的自动化体系。下面以“电商抢券”为例手把手教你把assets/scripts/变成可运维的模块中心。5.1 模块化规范定义module.json元数据与版本契约不再把JS脚本硬编码在assets/里而是设计一个远程模块仓库。每个模块必须包含module.json描述文件例如coupon-module-v2.1.0/module.json{ name: coupon-grabber, version: 2.1.0, minAutoJsVersion: 9.3.0, entry: main.js, dependencies: [utils.js, api.js], configSchema: { shopId: {type: string, required: true}, maxRetry: {type: number, default: 5} } }这个JSON定义了模块的准入门槛minAutoJsVersion、依赖关系、配置项结构。AutoJs启动时会校验该版本是否兼容不兼容则拒绝加载——避免低版本引擎执行高版本API导致崩溃。5.2 热更新流程从下载、校验到原子化切换的三步安全协议模块更新不能简单覆盖文件必须保证原子性要么全成功要么全失败。我设计了一个三步协议步骤操作安全机制1. 下载与校验从HTTPS服务器下载module.zip用SHA256校验包完整性校验失败则删除临时文件不进入下一步2. 解压与预检解压到/sdcard/Android/data/pkg/cache/modules/temp/运行precheck.js验证入口文件语法precheck.js用JavaScriptEngine.compile()预编译语法错误则终止3. 原子切换将temp/重命名为active/旧active/重命名为backup/切换用File.renameTo()Linux下是原子操作关键代码ModuleManager.javapublic void updateModule(String zipUrl) throws Exception { File tempDir new File(getCacheDir(), modules/temp); downloadAndVerify(zipUrl, tempDir); if (!runPrecheck(tempDir)) throw new RuntimeException(预检失败); File activeDir new File(getCacheDir(), modules/active); File backupDir new File(getCacheDir(), modules/backup); if (activeDir.exists()) { activeDir.renameTo(backupDir); // 原子备份 } tempDir.renameTo(activeDir); // 原子激活 }5.3 远程配置中心用JSON Schema驱动UI配置页自动生成用户不需要写JSON配置而是通过图形界面输入。configSchema能自动生成配置页。例如module.json中的configSchema会被渲染成字段类型输入控件校验规则shopIdstringEditText非空、长度3maxRetrynumberNumberPicker范围1~20实现原理遍历configSchema的每个key用LinearLayout.addView()动态添加控件。提交时用Gson将控件值序列化为JSON存入/sdcard/Android/data/pkg/files/module-config.json。5.4 失败回滚机制基于Git Commit ID的模块快照管理每次成功加载模块记录其Git Commit ID从module.json的gitCommit字段读取到本地数据库。当新模块执行失败如JS异常超过3次自动回滚到上一个稳定Commit// ModuleManager.java public void rollbackToLastStable() { String lastCommit db.queryLastStableCommit(); String moduleUrl https://cdn.example.com/modules/ lastCommit .zip; updateModule(moduleUrl); // 调用上面的热更新流程 }从那以后我每次上线新模块都强制走一遍git tag v2.1.0 git push --tags然后把tag名写进module.json。模块的每一次迭代都有Git历史可追溯、可回滚。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站