简介:这份资源是面向AutoJs脚本学习者与自动化工具开发者的实战项目模板,核心内容为「抢单大神」类脚本的完整源码,适合已掌握AutoJs基础语法、希望研究实际项目结构与交互逻辑的中级用户参考。压缩包为7z格式,仅含1个js文件,体积约6KB,属于轻量级单文件脚本,便于直接导入AutoJs环境运行与调试。资源描述中明确提示支持低版本AutoJs,安装后即可打开,降低了环境适配门槛。目前已有1374人浏览学习,说明该模板在自动化脚本圈内具备一定参考热度。读者可从中获取抢单场景下的界面控件定位、事件监听与流程调度等实现思路,用于理解真实项目如何组织代码逻辑,并在此基础上进行二次学习与功能拆解。需注意,该源码仅供学习与参考,请勿用于商业用途,使用前建议确认自身AutoJs操作能力,谨慎下载。
1. 抢单脚本的真相:AutoJs 源码能跑通,但跑得久才是本事
很多人第一次搜「AutoJs源码-抢单大神源代码」,脑子里想的是一份拿来就能用的脚本,导入手机、点一下运行,订单就自动飞进来。我当初也是这么想的,直到自己把一套抢单逻辑从能跑到稳定跑了三个月,才明白真正的门槛根本不在「抢」这个动作,而在「判断该不该抢」和「抢完之后怎么收尾」。AutoJs 本身只是一套基于无障碍服务的自动化工具,它能模拟点击、读取控件文本、监听页面变化,但平台的风控、页面结构的随机化、网络延迟的抖动,才是决定这套源码能不能长期活下来的关键。这篇文章面向的是已经会写一点 AutoJs、想把抢单脚本做成可维护工程的人,我会把控件定位、状态机、防检测、异常恢复这几块拆开讲,每一步都给可复现的代码和参数,新手能照着搭,熟手能直接看到边界在哪。
2. 抢单脚本的骨架:从控件树到状态机
2.1 为什么不能靠固定坐标点击
固定坐标是新手最容易踩的坑。不同分辨率、不同字体大小、甚至同一台手机横竖屏切换,坐标全废。AutoJs 提供了selector()和className()这类基于控件属性的查找方式,抢单场景下最稳的是组合条件:文本包含关键词 + 控件可点击 + 在屏幕可见范围内。我一般会先写一个探测函数,把当前页面上所有可点击且带文本的控件打印出来,确认目标控件的真实属性,而不是凭想象写选择器。
// 探测当前页面可点击控件,用于确认抢单按钮的真实属性 function dumpClickableNodes() { var nodes = className("android.widget.Button").find(); nodes.forEach(function (node, i) { if (node.clickable()) { log("索引:" + i + " 文本:" + node.text() + " 描述:" + node.desc() + " 边界:" + node.bounds()); } }); } dumpClickableNodes();这段代码跑一次就能看到按钮的text或desc到底是什么。很多平台的抢单按钮文本会带动态数字,比如「立即抢单(3)」,这时候用textContains("立即抢单")比text("立即抢单")稳得多。参数上,className不要死磕Button,有些平台用TextView加点击事件,探测时把clickable(true)作为主条件更可靠。
2.2 用状态机替代 while 循环
我见过太多抢单脚本是一个大while(true)里塞满if,跑十分钟就卡死。正确的做法是把整个流程拆成有限状态:空闲、监听、发现目标、点击、确认结果、冷却。每个状态只做一件事,状态之间用事件驱动切换。这样做的好处是任何一步超时都能回到空闲态重新开始,不会因为一次点击失败就整个脚本挂掉。
// 简化状态机:监听 -> 点击 -> 确认 -> 冷却 var STATE = { IDLE: 0, WATCH: 1, TAP: 2, CONFIRM: 3, COOLDOWN: 4 }; var currentState = STATE.WATCH; var lastActionTime = 0; function tick() { switch (currentState) { case STATE.WATCH: var btn = textContains("立即抢单").findOne(800); if (btn) { currentState = STATE.TAP; } break; case STATE.TAP: var target = textContains("立即抢单").findOne(500); if (target) { target.click(); lastActionTime = Date.now(); currentState = STATE.CONFIRM; } else { currentState = STATE.WATCH; // 目标消失,回退 } break; case STATE.CONFIRM: // 等待结果页出现,最多等 2 秒 if (textContains("抢单成功").findOne(2000)) { log("抢单成功"); currentState = STATE.COOLDOWN; } else if (Date.now() - lastActionTime > 2000) { currentState = STATE.COOLDOWN; // 超时也进冷却 } break; case STATE.COOLDOWN: sleep(1200 + Math.random() * 800); // 随机冷却,避免机械节奏 currentState = STATE.WATCH; break; } }状态机的核心参数是每个findOne的超时时间。监听态给 800 毫秒,点击态给 500 毫秒,确认态给 2000 毫秒,这些数字不是拍脑袋,而是根据页面渲染速度和网络往返时间调的。冷却时间加随机抖动是必须的,固定 1000 毫秒的间隔在日志里一眼就能看出是脚本。
2.3 页面监听比轮询更省电也更准
轮询findOne会持续占用无障碍服务,手机发热快,而且容易漏掉瞬间出现的按钮。AutoJs 支持events.observeNotification和events.on("window_change"),但抢单场景最实用的是selector().watch()或者直接用setInterval配合短超时。我一般用setInterval每 300 毫秒触发一次tick(),比while循环可控,也方便在外部暂停。
// 用定时器驱动状态机,避免阻塞主线程 var timer = setInterval(function () { try { tick(); } catch (e) { log("状态机异常:" + e); currentState = STATE.WATCH; // 异常后强制回监听态 } }, 300); // 外部停止条件 setTimeout(function () { clearInterval(timer); log("脚本停止"); }, 3600000); // 最多跑一小时这里有个细节:tick()内部任何异常都必须捕获并重置状态,否则一次控件读取失败就会让整个定时器崩掉。setInterval的 300 毫秒是经验值,低于 200 毫秒会让无障碍服务响应变慢,高于 500 毫秒会漏掉快速消失的按钮。
3. 让脚本活得更久:防检测与异常恢复
3.1 随机化不是加个 sleep 就完事
平台检测脚本的逻辑通常看三点:点击间隔是否固定、点击位置是否总在控件正中心、操作序列是否每天一模一样。只加sleep(Math.random()*500)远远不够。点击位置要在控件边界内随机偏移,操作顺序要偶尔插入无意义的滑动或返回,甚至冷却时间也要按正态分布而不是均匀分布。
// 在控件边界内随机取点,避免每次点正中心 function randomClick(node) { var b = node.bounds(); var x = b.left + b.width() * (0.3 + Math.random() * 0.4); var y = b.top + b.height() * (0.3 + Math.random() * 0.4); click(x, y); } // 正态分布冷却:均值 1500ms,标准差 300ms function gaussianCooldown() { var u = 0, v = 0; while (u === 0) u = Math.random(); while (v === 0) v = Math.random(); var g = Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v); var ms = 1500 + g * 300; return Math.max(800, Math.min(3000, ms)); // 截断到合理区间 }randomClick里取 30% 到 70% 的边界范围,既不会点到控件外面,也不会每次都落在同一个像素。gaussianCooldown用 Box-Muller 变换生成正态分布,截断在 800 到 3000 毫秒之间,这样操作节奏看起来更像人。注意click(x, y)是绝对坐标,如果页面滚动了,bounds()会变,所以每次点击前都要重新获取节点。
3.2 异常恢复:断网、弹窗、页面跳转
抢单过程中最怕三种意外:网络断了、突然弹出广告、页面跳到登录页。脚本必须能识别这些状态并回到主流程。我的做法是在状态机里加一个「健康检查」分支,每 30 秒执行一次,检查当前包名是否还是目标应用、是否有系统弹窗、网络是否可用。
// 健康检查:包名、弹窗、网络 function healthCheck() { var currentPkg = currentPackage(); if (currentPkg !== "com.target.app") { log("包名异常:" + currentPkg + ",尝试返回"); back(); sleep(1000); return false; } // 检测常见弹窗关键词 var popup = textMatches(/允许|同意|更新|广告/).findOne(300); if (popup) { log("发现弹窗,尝试关闭"); popup.click(); sleep(500); } return true; }currentPackage()返回当前前台应用的包名,一旦不是目标应用就按返回键。弹窗检测用正则匹配「允许」「同意」「更新」「广告」这些高频词,找到就点掉。注意不要用textContains去匹配太宽泛的词,否则可能误点正常按钮。健康检查的频率不要太高,30 秒一次足够,太频繁反而增加被检测的风险。
3.3 日志与回放:出问题能查
脚本跑在手机上,出问题的时候你不可能一直盯着。我习惯把关键事件写到文件里,包括时间戳、状态切换、点击坐标、异常信息。AutoJs 的files.append()可以追加写,格式用简单的竖线分隔,方便后面用脚本分析。
// 写日志到 /sdcard/autojs_grab.log function writeLog(msg) { var line = new Date().toLocaleString() + "|" + msg + "\n"; files.append("/sdcard/autojs_grab.log", line); } // 在状态切换处调用 writeLog("STATE:" + currentState + " -> TAP");日志文件不要写太大,跑之前先清空或者按天切分。我一般会在脚本启动时检查文件大小,超过 5MB 就重命名归档。回放的时候直接看日志里状态切换的时间间隔,如果发现某个状态卡了很久,就知道是控件没找到还是网络慢。
4. 避坑与排查:抢单脚本最常见的五个翻车点
4.1 控件找到了但 click 无效
现象是findOne返回了节点,click()也执行了,但页面没反应。原因通常是节点在click()那一刻已经失效,或者控件被其他透明层遮挡。解决办法是点击前重新获取节点,并且用node.click()而不是click(x, y),如果还不行就加node.parent().click()试试。另外有些平台用WebView渲染,无障碍服务拿到的节点是虚拟的,这时候只能靠坐标点击,但坐标要从bounds()实时取。
4.2 脚本跑几分钟后无障碍服务断开
现象是日志突然停止,手机提示无障碍服务已关闭。原因是 AutoJs 的无障碍服务被系统省电策略杀掉,或者被其他应用抢占。解决办法是在手机设置里把 AutoJs 加入电池优化白名单,并且关闭「自动关闭无障碍」相关的系统选项。代码层面可以加一个定时检查,如果auto()返回 false 就尝试重新启用。
4.3 抢单成功但确认页没等到
现象是点击后进入了确认页,但脚本已经切到冷却态,导致漏掉后续的「确认接单」按钮。原因是确认页的加载时间超过了findOne的超时。解决办法是把确认态的超时从 2000 毫秒加到 4000 毫秒,并且在确认态里同时检测「抢单成功」和「确认接单」两个关键词,哪个先出现就处理哪个。
4.4 随机冷却反而让节奏更规律
现象是加了随机数,但日志里看间隔还是集中在某个值附近。原因是Math.random()是均匀分布,截断后大量值落在边界。解决办法是改用正态分布,并且把标准差调大一点,让 800 到 3000 毫秒之间的分布更自然。另外冷却时间不要只依赖随机数,可以结合当前页面加载速度动态调整。
4.5 多设备同时跑导致账号异常
现象是同一账号在两台手机上跑,很快被限制。原因是平台会检测同一账号的登录设备数和操作频率。解决办法是一台设备只跑一个账号,并且不同设备的操作节奏要有差异,比如 A 设备冷却均值 1500 毫秒,B 设备用 1800 毫秒。如果必须多设备,至少保证 IP 不同、操作时间错开。
5. 进阶技巧:用配置表驱动脚本,不改代码就能调参
写到后面你会发现,每次调参数都要改代码、重新导入、重启脚本,效率极低。我的做法是把所有可调参数抽到一个 JSON 配置文件里,脚本启动时读取,这样改参数只需要改文件内容,不用动逻辑代码。配置文件放在/sdcard/autojs_config.json,用files.read()读出来解析。
// 读取配置,缺省值兜底 function loadConfig() { var defaults = { watchTimeout: 800, tapTimeout: 500, confirmTimeout: 4000, cooldownMean: 1500, cooldownStd: 300, healthInterval: 30000, maxRunMinutes: 60 }; try { var raw = files.read("/sdcard/autojs_config.json"); var cfg = JSON.parse(raw); for (var k in defaults) { if (cfg[k] === undefined) cfg[k] = defaults[k]; } return cfg; } catch (e) { log("配置读取失败,使用默认值:" + e); return defaults; } }这个loadConfig的关键是兜底逻辑:任何字段缺失都用默认值补上,避免因为配置文件少写一个字段就整个脚本报错。参数含义上,watchTimeout是监听态单次查找超时,cooldownMean和cooldownStd控制冷却分布,healthInterval是健康检查间隔,maxRunMinutes是脚本最长运行时间。调参的时候我一般先动cooldownMean,把它从 1500 加到 2000 看账号异常是否减少,再动confirmTimeout,如果确认页加载慢就加到 5000。
还有一个技巧是把目标关键词也放进配置,比如targetText: "立即抢单",这样换平台只需要改配置不用改代码。但要注意关键词不要写太短,至少四个字,否则容易误匹配。配置文件的编码用 UTF-8,AutoJs 的files.read()默认就是 UTF-8,不用额外处理。
最后说一个我自己的习惯:每次改完配置,先让脚本空跑五分钟,只看日志不实际抢单,确认状态切换正常、没有异常抛出,再放开真实操作。这个习惯帮我省了很多次「跑了一晚上结果什么都没抢到」的后悔药。抢单脚本的本质不是比谁点得快,而是比谁跑得稳、活得久,把状态机、随机化、异常恢复、配置驱动这四块做扎实,比追求极限点击速度有用得多。希望帮到你。
本文还有配套的精品资源,点击获取