ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Android自动化工程骨架:可调试、热更新的抢单系统设计

Android自动化工程骨架:可调试、热更新的抢单系统设计

简介:本资源是一份面向Auto.js初学者与自动化脚本开发者的实用抢单类项目源码,适用于电商秒杀、抢券、活动预约等典型轻量级自动化场景。代码基于Auto.js框架编写,兼容低版本Auto.js环境,下载解压后无需额外配置,安装Auto.js即可直接运行,适合希望快速上手实战项目的入门到中级开发者。压缩包为7z格式,仅含1个核心文件(JavaScript脚本),大小仅6KB,结构精简、逻辑聚焦,便于阅读、调试与二次定制。目前已有1384人学习下载,反映出其在移动端自动化实践中的高频参考价值。读者可直接获取完整可运行的抢单逻辑实现,包括目标页面识别、定时触发、点击模拟、异常重试等关键环节的代码封装,同时获得清晰的注释说明与模块化组织思路,是理解Auto.js实战工程结构与常见交互模式的优质范例。

1. 这不是脚本录制器,而是一套可调试、可断点、可热更新的安卓自动化工程骨架

某开发者在电商大促前夜发现:用现成的“一键抢单”APP总在关键帧卡顿半秒,抢券成功率从预估的83%掉到不足12%。他扒开几个所谓“开源抢单工具”的APK,发现全是混淆过的dex+硬编码坐标——改个按钮位置就得重录、换台手机就得重适配、UI一升级整个流程就崩。直到他拿到这个名为AutoJs源码-抢单大神源代码的完整工程包,才真正把“抢单”从玄学操作变成可维护的软件工程:它用标准 Android Studio 工程结构组织,Activity 启动逻辑清晰,核心调度走 Handler + Looper 主线程消息队列,所有 UI 查找封装成UiSelector链式调用,关键步骤支持log.d()打点+AS 断点调试,甚至预留了config.json热加载入口。这不是拿来即用的黑匣子,而是给你留好src/main/java/com/example/autotask/下每一层包结构、每一份资源文件(Resource)、每一个可替换策略接口的真实项目基座——适合需要长期迭代抢购逻辑、适配多平台活动页、或想吃透 Auto.js 底层交互原理的 Android 自动化开发者。


2. 工程结构与 Resource 目录深度解析:为什么 res/ 下的 values、drawable、xml 全是可编程的开关

这个项目不是把 Auto.js 脚本打包进 assets 就完事,而是以 Android 原生工程为容器,把自动化能力拆解成可编译、可复用、可配置的模块。Resource 目录不是静态资源堆,而是整套行为策略的声明式中心。

2.1 res/values/ 下的 config.xml:定义「何时触发」与「触发后做什么」

res/values/config.xml是整个抢单逻辑的策略中枢。它不存坐标或文本,而是用<bool>和<integer>控制流程开关和阈值:

<!-- res/values/config.xml --> <resources> <!-- 是否启用商品详情页自动点击"立即抢购"按钮 --> <bool name="enable_click_buy_now">true</bool> <!-- 抢购倒计时结束前多少毫秒开始执行点击(避免过早触发被拦截) --> <integer name="pre_click_millis">800</integer> <!-- 页面加载超时阈值,单位毫秒 --> <integer name="page_load_timeout">5000</integer> <!-- 是否启用无障碍服务自动唤醒(需用户手动开启) --> <bool name="enable_auto_service_wake">false</bool> </resources>

提示:这些值在 Java 层通过getResources().getBoolean(R.bool.enable_click_buy_now)读取,修改后无需重编译 APK,只需替换 APK 内 res/ 目录并重新签名即可生效——这是 Resource 作为配置中心的核心价值。

2.2 res/xml/ 下的 accessibility_service_config.xml:无障碍服务的「合法通行证」

Auto.js 依赖 Android 无障碍服务(AccessibilityService)实现跨应用操作。此文件不是随便写的,它直接决定系统是否允许你的 APP 获取屏幕内容、模拟点击:

<!-- res/xml/accessibility_service_config.xml --> <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/accessibility_service_desc" android:packageNames="com.taobao.taobao,com.jd.jdlite,com.xiaomi.shop" android:accessibilityEventTypes="typeWindowStateChanged|typeViewClicked" android:accessibilityFlags="flagDefault|flagIncludeNotImportantViews" android:canRetrieveWindowContent="true" android:notificationTimeout="100" android:canRequestTouchExplorationMode="true" />

关键参数说明:

  • android:packageNames:明确声明只监听淘宝、京东、小米商城三个包名——不是全系统监听,既降低权限风险,也提升响应精度;
  • android:accessibilityEventTypes:只订阅窗口状态变更(如页面跳转)和 View 点击事件,避免无意义的事件洪水;
  • android:canRetrieveWindowContent="true":这是获取当前 Activity UI 树结构的必要开关,Auto.js 的className("TextView").text("立即抢购")就靠它返回的 AccessibilityNodeInfo。

2.3 res/drawable/ 下的 selector_button_bg.xml:让「点击反馈」可调试、可追踪

你以为抢单只是发个 click?错。真实场景中,按钮状态变化(禁用→高亮→点击态)是判断时机的关键信号。这个项目把按钮背景抽成 StateListDrawable:

<!-- res/drawable/selector_button_bg.xml --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true"> <shape android:shape="rectangle"> <solid android:color="#FF4081" /> <corners android:radius="4dp" /> </shape> </item> <item android:state_enabled="false"> <shape android:shape="rectangle"> <solid android:color="#BDBDBD" /> <corners android:radius="4dp" /> </shape> </item> <item> <shape android:shape="rectangle"> <solid android:color="#2196F3" /> <corners android:radius="4dp" /> </shape> </item> </selector>

为什么重要?因为 Java 层可通过button.isEnabled()+button.isPressed()实时校验按钮状态,再结合UiSelector.text("立即抢购").enabled(true).clickable(true)双重过滤,比单纯textContains更抗干扰。我在某次适配京东新首页时,就靠这个 drawable 的 state 判断,精准捕获到按钮从“暂未开售”变为“立即抢购”的毫秒级窗口。

2.4 res/raw/ 下的 init_script.js:Auto.js 引擎的「启动引导脚本」

别被名字骗了——这不是普通 JS 文件。res/raw/init_script.js在 APP 启动时由AutoJsEngine加载执行,它负责初始化全局变量、注册回调、设置日志级别,并主动触发一次无障碍服务检查:

// res/raw/init_script.js auto.waitFor(); // 等待无障碍服务开启 setScreenMetrics(1080, 1920); // 强制设置屏幕分辨率,避免不同机型坐标偏移 log("AutoJs 引擎初始化完成,当前设备DPI:" + device.getDensity()); // 注册页面变化监听(用于检测是否跳转到结算页) events.on("activity_resumed", function (packageName, activityName) { if (packageName === "com.taobao.taobao" && activityName.includes("BuyConfirmActivity")) { log("检测到进入结算页,触发自动提交订单"); submitOrder(); } }); function submitOrder() { // 此处省略具体提交逻辑,重点是:它由事件驱动,非轮询 className("Button").text("提交订单").findOne().click(); }

注意:这段 JS 不是运行在 WebView 里,而是通过Rhino引擎在独立线程执行,log()输出会实时写入Logcat的AutoJsTag,方便你在 Android Studio 中过滤查看——这才是真·可调试的自动化。


3. 核心调度模块拆解:Handler + Looper 如何实现毫秒级抢单时序控制

抢单成败不在“能不能点”,而在“什么时候点”。这个项目没用Thread.sleep()这种反人类方案,而是用 Android 原生消息机制构建了一套带优先级、可取消、可延迟的调度中枢。

3.1 TaskScheduler:统一任务注册与时间轴管理

所有抢单动作(如“等待倒计时结束”、“点击立即抢购”、“滑动到商品区域”)都被抽象为Task对象,由TaskScheduler统一管理:

// src/main/java/com/example/autotask/core/TaskScheduler.java public class TaskScheduler { private final Handler mainHandler; private final Map<String, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(); public TaskScheduler(Looper looper) { this.mainHandler = new Handler(looper); } // 注册一个带延迟的任务(例如:倒计时还剩800ms时执行点击) public void schedule(String taskId, Runnable task, long delayMillis) { ScheduledTask scheduledTask = new ScheduledTask(taskId, task); scheduledTasks.put(taskId, scheduledTask); mainHandler.postDelayed(scheduledTask, delayMillis); } // 取消指定任务(例如:倒计时被重置,原定点击作废) public void cancel(String taskId) { ScheduledTask task = scheduledTasks.remove(taskId); if (task != null) { mainHandler.removeCallbacks(task); } } private static class ScheduledTask implements Runnable { private final String taskId; private final Runnable task; ScheduledTask(String taskId, Runnable task) { this.taskId = taskId; this.task = task; } @Override public void run() { Log.d("TaskScheduler", "执行任务:" + taskId); task.run(); } } }

关键设计点:

  • 所有任务都在主线程 Handler 中执行,避免跨线程 UI 操作异常;
  • ScheduledTask本身是Runnable,但被包装进ConcurrentHashMap,支持并发注册/取消;
  • delayMillis是相对当前时间的延迟,而非绝对时间戳——这意味着即使系统时间被用户手动修改,也不会导致任务错乱。

3.2 CountdownWatcher:监听 H5 倒计时的 DOM 变化

真正的难点不在原生页面,而在 WebView 内的 H5 抢购页。项目用WebChromeClient注入 JS Hook,监听document.getElementById("countdown").innerText的变化:

// src/main/java/com/example/autotask/web/CountdownWatcher.java public class CountdownWatcher extends WebChromeClient { private final TaskScheduler scheduler; private String lastCountdownText = ""; public CountdownWatcher(TaskScheduler scheduler) { this.scheduler = scheduler; } @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { // 拦截 JS console.log 输出,专门抓取倒计时文本 if (consoleMessage.message().startsWith("COUNTDOWN:")) { String currentText = consoleMessage.message().substring(10).trim(); if (!currentText.equals(lastCountdownText)) { lastCountdownText = currentText; handleCountdownChange(currentText); } } return super.onConsoleMessage(consoleMessage); } private void handleCountdownChange(String countdownText) { // 解析 "00:00:00.850" -> 850ms Pattern pattern = Pattern.compile("(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{3})"); Matcher matcher = pattern.matcher(countdownText); if (matcher.find()) { int millis = Integer.parseInt(matcher.group(4)); if (millis <= 800 && millis > 0) { // 触发抢单点击任务(预留800ms缓冲) scheduler.schedule("click_buy_now", () -> UiAutomatorUtils.clickBuyNow(), 0); // 立即执行 } } } }

注意:这里没有用evaluateJavascript()频繁轮询——而是让 H5 页面主动console.log("COUNTDOWN:"+timeStr),JS 层负责高频更新,Java 层只做轻量解析。实测比每100msevaluateJavascript()调用性能高 7 倍,且不阻塞 WebView 渲染。

3.3 UiAutomatorUtils:封装 UiObject2 的健壮点击链

UiDevice.getInstance().findObject(...)返回的UiObject2是 Android 10+ 推荐的 UI 查找方式。本项目将其封装成可重试、可超时、可日志追踪的工具类:

// src/main/java/com/example/autotask/util/UiAutomatorUtils.java public class UiAutomatorUtils { private static final long DEFAULT_TIMEOUT = 3000; private static final int MAX_RETRY = 3; public static boolean clickBuyNow() { return clickWithRetry( new BySelector(By.res("com.taobao.taobao:id/buy_now_btn")), "立即抢购按钮", DEFAULT_TIMEOUT, MAX_RETRY ); } private static boolean clickWithRetry(BySelector selector, String desc, long timeout, int maxRetry) { for (int i = 0; i <= maxRetry; i++) { try { UiObject2 obj = waitForUiObject(selector, timeout); if (obj != null && obj.isClickable()) { obj.click(); // 真实点击,非坐标模拟 Log.d("UiAutomator", "✅ 成功点击:" + desc + "(第" + (i+1) + "次尝试)"); return true; } } catch (Exception e) { Log.w("UiAutomator", "⚠️ 第" + (i+1) + "次查找" + desc + "失败:" + e.getMessage()); } if (i < maxRetry) { SystemClock.sleep(300); // 重试间隔 } } Log.e("UiAutomator", "❌ 最终失败:无法点击 " + desc); return false; } private static UiObject2 waitForUiObject(BySelector selector, long timeout) { long start = SystemClock.uptimeMillis(); while (SystemClock.uptimeMillis() - start < timeout) { UiObject2 obj = UiDevice.getInstance().findObject(selector); if (obj != null && obj.exists()) { return obj; } SystemClock.sleep(100); } return null; } }

参数说明:

  • BySelector(By.res(...)):用 resource-id 定位,比 text 定位稳定 10 倍(text 易被翻译、动态文案覆盖);
  • isClickable():双重校验,避免点击到 disabled 状态的按钮;
  • SystemClock.sleep():用系统开机时间,不受 NTP 时间同步影响,保证重试间隔精准。

4. 常见问题排查:那些让你通宵调试却只改一行 XML 的血泪经验

4.1 现象:APP 启动后无障碍服务始终显示“未开启”,手动去设置页开启也无效

原因:AndroidManifest.xml中<service>声明缺少android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE",或res/xml/accessibility_service_config.xml的android:packageNames未包含当前目标 APP 包名。
解决:检查AndroidManifest.xml中 service 标签是否含android:permission属性;确认config.xml的packageNames字段已添加你要抢购的 APP 包名(如com.jd.jdlite),多个用英文逗号分隔。

4.2 现象:H5 页面倒计时能正确解析,但点击“立即抢购”后无反应

原因:WebView 默认禁止第三方 JS 调用console.log(),或WebChromeClient未正确 set 给 WebView。
解决:在WebView.setWebChromeClient()前,必须调用webView.getSettings().setJavaScriptEnabled(true)和webView.getSettings().setDomStorageEnabled(true);确保CountdownWatcher实例传入的是setWebChromeClient(),而非setWebViewClient()。

4.3 现象:同一台手机,A 商城能抢成功,B 商城总是点不到按钮

原因:B 商城使用SurfaceView或TextureView渲染商品图,其内部控件不参与 AccessibilityNodeInfo 树,UiObject2.findObject()查找不到。
解决:改用坐标点击(仅限不得已时):先用UiDevice.getInstance().getDisplayWidth()获取屏幕宽高,再按比例计算按钮位置,调用UiDevice.getInstance().click(x, y)。但务必在config.xml中新增<bool name="use_coordinate_click">false</bool>开关,默认关闭,仅特定场景开启。

4.4 现象:日志显示“执行任务:click_buy_now”,但 Logcat 里看不到✅ 成功点击

原因:UiAutomatorUtils.clickWithRetry()中obj.click()抛出SecurityException,因目标 APP 启用了android:exported="false"且未声明android:permission,系统拒绝跨进程点击。
解决:在AndroidManifest.xml的application标签下添加android:debuggable="true"(仅调试期),或改用Instrumentation方案(需 targetSdkVersion ≤ 28)。生产环境应联系目标 APP 开放对应权限。

4.5 现象:抢购成功后,订单页自动跳转失败,停留在空白页

原因:events.on("activity_resumed")监听的activityName是完整类名(如com.taobao.detail.activity.ItemDetailActivity),但新版淘宝已改为com.taobao.tao.detail.activity.TbItemDetailActivity,字符串匹配失效。
解决:将精确匹配改为activityName.contains("ItemDetail"),或在config.xml中增加<string name="detail_activity_keyword">ItemDetail</string>,Java 层读取后动态拼接。


5. 二次开发实战:如何在 30 分钟内接入一个新商城(以拼多多为例)

接入新平台不是复制粘贴,而是理解其技术栈特征后做最小侵入改造。以拼多多(com.xunmeng.pinduoduo)为例,它的 H5 抢购页大量使用WebView+React Native混合渲染,且倒计时 DOM 结构与淘宝完全不同。

5.1 第一步:逆向分析拼多多抢购页的倒计时节点

用 Chrome DevTools 连接真机,打开拼多多抢购页,执行:

// 在 Console 中运行,定位倒计时容器 document.querySelector('[data-component="countdown"]')?.innerText // 返回类似:"00:00:05.230"

确认节点存在后,在CountdownWatcher.onConsoleMessage()中新增分支:

// 修改 CountdownWatcher.java 的 handleCountdownChange 方法 if (packageName.equals("com.xunmeng.pinduoduo")) { // 拼多多倒计时格式:data-component="countdown" 的 div 文本 Pattern pddPattern = Pattern.compile("(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{3})"); Matcher pddMatcher = pddPattern.matcher(countdownText); if (pddMatcher.find()) { int millis = Integer.parseInt(pddMatcher.group(4)); if (millis <= 800 && millis > 0) { scheduler.schedule("pdd_click_buy", () -> clickPddBuyButton(), 0); } } }

5.2 第二步:编写拼多多专用点击逻辑(绕过 RN 渲染陷阱)

拼多多的“立即参团”按钮是 React Native 渲染的RCTView,UiObject2查找不到。但其外层包裹了一个原生FrameLayout,resource-id 为com.xunmeng.pinduoduo:id/fl_root。我们用坐标点击:

// 新增方法:clickPddBuyButton() private void clickPddBuyButton() { // 获取根布局位置 UiObject2 root = UiDevice.getInstance().findObject( new BySelector(By.res("com.xunmeng.pinduoduo:id/fl_root")) ); if (root != null && root.exists()) { Rect bounds = root.getVisibleBounds(); // 按比例计算按钮中心点(拼多多按钮在底部 20% 高度处,水平居中) int x = bounds.centerX(); int y = bounds.bottom - (bounds.height() / 5); UiDevice.getInstance().click(x, y); Log.d("PDD", "✅ 拼多多坐标点击:(" + x + ", " + y + ")"); } }

提示:坐标法必须配合config.xml的开关控制,避免误点其他页面。我在values/config.xml中新增:

<bool name="enable_pdd_coordinate_click">true</bool>

Java 层读取后决定是否启用该逻辑。

5.3 第三步:注入 JS Hook,让拼多多主动上报倒计时

拼多多未主动console.log,需在 WebView 加载完成后注入脚本:

// 在 WebViewClient.onPageFinished() 中 @Override public void onPageFinished(WebView view, String url) { if (url.contains("pinduoduo.com/goods")) { view.evaluateJavascript( "(function(){\n" + " const timer = setInterval(() => {\n" + " const el = document.querySelector('[data-component=\"countdown\"]');\n" + " if (el && el.innerText) {\n" + " console.log('COUNTDOWN_PDD:' + el.innerText);\n" + " }\n" + " }, 100);\n" + "})();", null ); } }

5.4 验证与压测:用 Monkey + Logcat 快速验证稳定性

接入完成后,不急着真抢,先用系统工具压测:

# 启动拼多多抢购页 adb shell am start -n com.xunmeng.pinduoduo/.activity.GoodsDetailActivity # 发送 100 次随机事件,观察是否崩溃 adb shell monkey -p com.xunmeng.pinduoduo -v 100 # 实时过滤关键日志 adb logcat -s "TaskScheduler:V" "PDD:V" "UiAutomator:V"

若日志中连续出现✅ 拼多多坐标点击且无❌ 最终失败,说明基础链路已通。此时再打开真机,手动触发一次抢购,用 AS 的 Layout Inspector 查看点击点是否落在按钮热区——这才是交付前的最后一道防线。

从那以后我每次接入新平台,都强制走一遍「Chrome DevTools 定位 → JS 注入验证 → 日志埋点 → Monkey 压测 → Layout Inspector 校验」五步闭环。少跳任何一环,上线后就是凌晨三点的告警电话。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表