ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从无障碍服务到自动抢红包:Android自动化脚本原理与实现

从无障碍服务到自动抢红包:Android自动化脚本原理与实现 “龙虾版支付宝”这说法你肯定刷到过一开始我也当段子看。直到有次平台发晚八点红包我手速慢了一秒点了两次都只看到“已被抢光”旁边一朋友却连着两天在群里晒截图说他十二点睡的红包却是凌晨一点半到账的。追问半天他才承认手机上装过一个叫“小助手”的辅助工具睡前打开让它盯着看见可领的红包直接点。那东西被网友起外号叫“龙虾版支付宝”——像龙虾一样夹住了就不松口全网自动抢红包你睡觉它干活。我当时第一反应是不信后来自己动手研究了一遍它的原理又搭了个最小可用版本实测一个月才敢说这里面的门道值得写出来。这篇文章不讲神话就讲三件事这类工具到底靠什么抢的、怎么从零搭一个、以及长期用会遇到哪些坑。适合对手机自动化感兴趣、想写点小工具省事的开发者也适合只想知道“这东西靠不靠谱”的普通用户。1. 标题里的三层需求和这套系统的真实定位先把这个花名拆开看。“龙虾版支付宝”不是一个真实产品它是网友对一类自动化辅助脚本的戏称。拆成三层需求就很好理解第一层是“抢红包”目标明确盯着特定入口第二层是“自动”不需要人盯着靠事件触发替代手动点击第三层是“睡觉都在工作”意味着它必须长期稳定挂在后台不能闪退、不能被杀、不能误触。这三层需求对应到技术上恰好是一个典型的事件驱动型自动化系统感知层接收界面变化事件识别层遍历当前页面的控件树执行层模拟点击目标节点。把它想成一个人替你打工你需要给他眼睛、手和脑子。眼睛负责看屏幕手负责点按钮脑子负责判断什么时候点、点哪里、点了之后怎么确认结果。所以这套系统的真实定位不是外挂也不是专门用来薅羊毛的工具它本质上就是一个无障碍辅助服务利用操作系统的辅助功能框架读取当前界面节点信息并向符合条件的节点发送点击指令。你把它用在抢红包上是“龙虾版支付宝”用在抢课、秒杀、挂号、签到上就是另一种效率工具。理解这个定位很重要因为决定你能做什么、不能做什么的不是工具本身而是你把它用在什么场景里。这也就回答了很多人问的第一个问题是不是只有安卓能做不是。安卓有无障碍服务接口iOS也有对应的辅助触控和快捷指令自动化但安卓的开源生态和节点访问能力更强多数人选择从安卓入手。下面所有实操内容我默认以Android为实验环境来讲。2. 三大基础件拆解事件监听、布局树识别、模拟点击2.1 为什么是“无障碍服务”而不是录屏模拟想实现“自动抢”第一直觉是用屏幕录制加坐标点击。早期确实有很多脚本这么干录一段屏幕记录用户点击位置然后定时重放。这种方法在游戏辅助里最常见但它有三个致命缺点坐标会随屏幕分辨率失效、界面结构一变就废、而且系统对你的“录屏悬窗点击”行为会格外警惕。无障碍服务AccessibilityService走的是另一条路系统在界面内容变化时会主动向注册的无障碍服务发送事件回调服务可以通过rootInActiveWindow拿到当前整个界面的控件树再通过节点属性判断该不该执行操作。它不是“我在旁边看见屏幕后在点”而是“系统直接告诉我屏幕上有什么我决定要不要碰它”。这种模式不需要屏幕录制权限也不需要悬浮窗模拟物理点击相对轻量而且事件由系统推送具备天然的实时性。代价是它必须由用户在系统设置里手动开启而且每台手机的开启路径不一样。有的在“设置-辅助功能-无障碍”有的在“更多设置-无障碍”品牌ROM不同入口和名称都不同。这一步没办法用代码绕过必须用户自己操作。2.2 布局树就是“眼睛”ACTION_CLICK就是“手”当无障碍服务拿到rootInActiveWindow后你获得的是一个AccessibilityNodeInfo对象它代表当前界面上一个具体的可视化控件。整个界面就是一棵树根节点是页面子节点是各个布局容器再往下是按钮、文本、图片。每个节点都有text、contentDescription、className、viewIdResourceName等属性。识别“该抢什么”的逻辑本质上就是在这棵树上做关键词匹配。我自己的规则很朴素遍历整棵树把每个节点的text和contentDescription拼接成字符串如果包含“开”“立即抢”“领取红包”“再抢一次”“立即领取”这些关键词之一就认为它是个可点击目标然后对节点调用performAction(ACTION_CLICK)。这里有个新手最容易踩的坑很多红包按钮的text是空的真正的文案写在“上层的容器”或者“同级的兄弟节点”里而按钮本身只是 ImageView。比如支付宝的“抢红包”按钮经常是一个红底圆角图片旁边一个 Text 写着“开”。只查按钮自身文本找不到关键词得往上找父节点再把父节点下的全部子节点文本拼起来判断。也就是说关键词匹配的对象不是一个点而是一个“候选区域”。2.3 识别规则要做“口子宽收手快”规则词太窄遇到“喊好友一起抢”“还有3秒开抢”这种非目标文案你会漏掉真正能点的入口规则词太宽又会把“开会”“开心”“开始”全盘接收然后你这工具就变成“见什么点什么”页面里所有含关键词的控件都会被点击场面极其混乱。我的调法是分两层。第一层是“准入关键词”用于候选节点粗筛词表放宽像“抢”“领”“开”“红包”“补贴”“福利”都算第二层是“排除规则”候选节点近亲属文本里如果出现“签到任务”“完成挑战”“邀请好友”“活动规则”这些词直接丢弃。这套简单规则在真实页面里已经能处理绝大多数情况。另一个细节是“去空白点击”很多节点匹配到了关键词但它本身isClickable是 false点了没反应也没错误白白浪费一次操作。所以执行点击之前必须校验节点是否可点击如果不可点击就向上或向下找离它最近的可点击父/子节点。顺手记下来找到了不可点击节点而不处理你会看到日志里一堆无效点击实际什么东西也没抢到。3. 动手搭一套睡眠自动抢的最小原型Android实践这一部分可以直接照着做。我会用 Kotlin 写一个最小实现工程结构很简单不依赖任何第三方库Android Studio 新建一个空项目就能跑。3.1 工程准备与配置声明首先在res/xml/下新建一个配置文件声明无障碍服务的行为accessibility-service xmlns:androidhttp://schemas.android.com/apk/res/android android:accessibilityEventTypestypeWindowStateChanged|typeWindowContentChanged android:accessibilityFeedbackTypefeedbackGeneric android:notificationTimeout100 android:canRetrieveWindowContenttrue android:canPerformGesturestrue /重点解释几个属性accessibilityEventTypes注册感兴趣的事件类型。我主要监听两个typeWindowStateChanged窗口切换和typeWindowContentChanged内容变化。红包出现往往伴随窗口切换或部分页面刷新这两个事件足够覆盖大部分场景。notificationTimeout事件节流时间间隔单位毫秒。设成 100 表示系统在 100ms 内合并相同类型的事件避免回调轰炸。canRetrieveWindowContent允许读取窗口内容这是拿到布局树的前提。canPerformGestures允许执行手势部分页面按钮需要滑动或长按后面扩展会用。然后在AndroidManifest.xml里注册服务service android:name.AutoSnatchService android:permissionandroid.permission.BIND_ACCESSIBILITY_SERVICE android:exportedtrue intent-filter action android:nameandroid.accessibilityservice.AccessibilityService / /intent-filter meta-data android:nameandroid.accessibilityservice android:resourcexml/accessibility_config / /service注意那个BIND_ACCESSIBILITY_SERVICE权限不能少这是系统绑定无障碍服务的硬性要求。漏了它你在系统设置里根本看不到这个服务。3.2 核心服务事件回调与节点遍历主类长这样去掉防护代码后就是一个最基础的框架class AutoSnatchService : AccessibilityService() { companion object { private const val TARGET_KEYWORDS 抢|开|立即领取|领取红包|再抢一次 private const val EXCLUDE_KEYWORDS 邀请|任务|签到|完成挑战|活动规则 } override fun onAccessibilityEvent(event: AccessibilityEvent?) { event ?: return val root rootInActiveWindow ?: return val candidates mutableListOfAccessibilityNodeInfo() val queue ArrayDequeAccessibilityNodeInfo() queue.add(root) var guard 0 while (queue.isNotEmpty() guard 5000) { guard val node queue.removeFirst() if (node null) continue val text buildNodeText(node) if (text.matches(Regex(TARGET_KEYWORDS)) !text.matches(Regex(EXCLUDE_KEYWORDS))) { candidates.add(node) } for (i in 0 until node.childCount) { node.getChild(i)?.let { queue.add(it) } } } for (node in candidates) { val clickTarget findClickableAncestorOrSelf(node) if (clickTarget ! null) { clickTarget.performAction(ACTION_CLICK) } } } override fun onInterrupt() { // 系统中断服务时回调 } }解释几个设计选择。用 BFS 而非递归遍历是因为真实的控件树深度可控但同一层节点数量很大BFS 能按层级稳定推进不会栈溢出。guard 5000是遍历深度护栏防止极端情况下死循环。buildNodeText这个函数要把节点自身的text、contentDescription以及它所有子节点的文本一起拼出来private fun buildNodeText(node: AccessibilityNodeInfo): String { val sb StringBuilder() node.text?.let { sb.append(it) } node.contentDescription?.let { sb.append(it) } for (i in 0 until node.childCount) { node.getChild(i)?.let { child - child.text?.let { sb.append(it) } child.contentDescription?.let { sb.append(it) } } } return sb.toString() }这里其实隐藏了一个效率问题每层 BFS 都会重复拼接子节点文本理论上会带来平方级开销。实际测试下来一个正常页面节点数在几百个以内加上notificationTimeout做了合并性能没有压力。但如果你要做的页面特别复杂就得考虑只读取节点自身属性把子节点拼接放到点击校验阶段再做否则响应会慢一截。3.3 去重与节流控制刚写完这个原型拿真机一跑立刻发现问题一个红包入口在界面停留期间onAccessibilityEvent会被连续触发每次触发都遍历一次树、点一次按钮。结果就是一个红包被连点三四次。这在红包场景里问题不大但如果是抢课、秒杀场景连续点击反而容易被系统以为是异常操作。解决方案就是加一个“点击记录池”按节点身份识别并去重private val clickedKeys LinkedHashMapString, Long() private fun nodeKey(node: AccessibilityNodeInfo): String { return listOf( node.viewIdResourceName, node.text.toString(), node.boundsInScreen.toString() ).joinToString(|) } private fun canClick(key: String): Boolean { val last clickedKeys[key] ?: return true if (System.currentTimeMillis() - last 5000L) { return false } clickedKeys.remove(key) return true }去重之后同一节点在 5 秒内只会被点击一次。LinkedHashMap用插入序方便之后做容量上限清理防止长时间挂机导致内存膨胀。3.4 悬浮窗观察与运行状态辅助服务没有界面连点状态全写在系统日志里不方便看。我的做法是加一个极简悬浮窗一个半透明圆角小球显示三行字“监控中”“今日已抢N个”“最近操作xx:xx”。开启悬浮窗需要SYSTEM_ALERT_WINDOW权限这一步也必须在系统设置里手动授权。悬浮窗本身不参与点击只做状态展示所以风险极低。如果不想申请悬浮窗也有替代方案用系统通知栏常驻通知展示状态。无障碍服务本身可以发起通知不需要额外权限。对新手来说常驻通知更容易接受因为悬浮窗权限在部分ROM上默认关闭得去应用权限管理里翻半天。3.5 真正跑起来的首轮实测我自己的测试环境是小米 13Android 14目标平台是日常用的支付和生活类应用。第一轮实测结果很典型红包入口出现后从事件回调到按钮被点击平均响应时间在 400 到 800 毫秒之间。其中节点遍历耗时约 200 到 300 毫秒真正点击动作几毫秒其余是系统事件分发延迟。这个速度在和人比拼手速时足够用但算不上极致。想要压到 300 毫秒以下得做两点优化把关键词匹配规则从正则改成 Set 包含匹配并且优先遍历viewIdResourceName带支付包名的节点跳过无关子树。4. 实测里翻车的地方机型适配、文案漂移、风控节奏工具跑通不难跑稳才麻烦。我连续用了几个星期踩过的坑大概能归类成四类按杀伤力排序讲。4.1 品牌ROM不同布局树差异大到离谱同一应用在小米手机上按钮是 FrameLayout到华为上变成 LinearLayout到三星上又套了一层 ConstraintLayout。这不影响事件监听但影响boundsInScreen的取值和isClickable的判断。有些ROM会把整个红包弹层做成一个自定义 ViewviewIdResourceName为空text也拿不到只能靠contentDescription匹配更麻烦的是部分ROM对无障碍事件做了额外延迟明明界面已经切换事件却晚了一两秒才推过来。我的适配办法是在遍历时把“文本匹配”和“结构匹配”分开写两套规则。文本匹配管大部分场景结构匹配专门针对viewIdResourceName里包含redpacket、hb、bonus字样的控件。先跑结构匹配命中就直接点命中不了再走文本匹配。这个优先级顺序在实测中能同时照顾到兼容性和准确率。4.2 文案漂移昨天的关键词今天失效红包页面的按钮文案不是固定的。“开”可能变成“拆”“立即领取”可能变成“我的红包已到账”活动结束后还会变成“再逛逛”。你要是把关键词列表焊死在代码里过两星期大概率就不灵了。我现在的做法是把关键词放到一个可热更新的配置里不写死。最简单的方案是内置一份 JSON每次启动服务时从本地读取再通过一个简易的远程配置来更新。不必引入完整框架用现成的对象存储或云参数服务一行脚本就能推更新。关键词维护频率大概是每周一次每次改动就加两三个词删掉一些过期词。记住一个原则关键词宁多勿少排除关键词宁严勿宽。因为多点击一次的代价远小于漏掉一个入口的代价。4.3 点击节奏触发风控红包金额被降级这是最容易被忽视的问题。我一开始把所有入口的等待时间设成固定 700 毫秒结果连续三天每天前三笔能抢到大额后面就全是几分钱。后来结合了不少同好反馈才意识到问题不是出在工具上而是点击节奏太规律了。固定间隔的自动化行为和人类手速有本质区别。真人抢红包第一下快后续会因为犹豫、切屏、看清楚金额而变慢间隔差异很大。机器固定 700 毫秒一次在服务端的行为模型里就是一个非常典型的“非真人”信号。解决方式很简单做一个不固定延迟区间在 300 到 1800 毫秒之间按随机分布取值偶尔再来一个 2 到 3 秒的长停顿。再配合前面说的去重减掉无效点击行为曲线就会平滑很多。把这个加进去之后红包金额的降级现象明显缓解但不能说完全消失。4.4 无障碍服务被系统回收这是最要命的问题。Android 的原生机制里无障碍服务是用户主动开启的系统服务进程被杀后系统会自动重启它。但国内厂商的ROM会做额外的省电和清理逻辑你挂后台挂久了系统会按“不常用应用”把你清掉。我试过各种保活方案双进程守护、账号同步唤醒、前台服务、通知栏常驻。最终稳定有效的是两个一个是用START_STICKY启动模式让系统在资源充足时自动重建服务另一个是常驻通知并把应用加入系统自启动和后台运行白名单。更激进的保活方式确实存在但涉及很多灰色手段不建议碰。5. 把它调到“能长期用”的状态延迟、存活与自愈这个阶段主要解决三个长期问题行为不被当成异常、服务不被系统杀掉、活动结束后能自己安静下来。5.1 随机延迟背后的思路前面提到了随机延迟区间但真正的工程实现不是随机延时一下就完了。我的策略是每个候选节点在被点击前先检查距离上次点击全局行为的时间差再生成一个服从三角分布的随机值。三角分布的好处是它会偏向中间值不会像均匀分布那样频繁出现极端值行为模型上更像人。具体参数上我设min300ms, max1800ms, mode800ms。在这个区间里绝大多数点击间隔落在 650 到 1100 毫秒之间偶尔来一次快的偶尔来一次磨蹭的。实测下来人工看日志会觉得“像是个手速不错但偶尔分心的人”。5.2 存活与省电的平衡系统回收问题前面说了这里再补充一个细节省电策略和后台运行白名单。不同厂商的管理应用叫法不同小米叫“省电策略”华为叫“启动管理”OPPO 叫“自启动管理”你需要在这个模块里把应用设为“无限制”或“允许自启动”同时打开“允许后台跳转界面”之类的选项。单靠用户手动配置还不够代码层面能做的优化是减少不必要的事件处理。比如红包活动集中在晚间白天大部分时间工具其实在空转。我后来加了一个“活动时段”配置项默认只在设定时段内开启自动点击其他时候只监听事件不执行操作。这样既省电又能降低长期挂机被系统清理的概率。5.3 自愈逻辑活动结束要能自己收手一个容易忽略的场景是红包活动已经结束页面处于倒计时状态或者停留在“活动已结束”的弹窗上。如果没有自愈逻辑工具会一直寻找可点击节点找不到就一直空转看起来好像还活着实际上在做无用功。我在工具里加了三层退避连续 10 次事件处理都没有产生有效点击进入半休眠事件响应频率降为原来的四分之一连续 30 次无有效点击直接暂停自动点击只保留手动唤醒当天累计有效点击次数达到设定阈值自动停用服务并把结果写入统计。这套退避机制看起来简单但它是让工具能持续运行一个月不被拖垮的关键。5.4 数据记录与复盘我每次抢到什么都会记录一条本地日志时间、页面状态、点击节点文本、动作结果。日志会写出一个 CSV 文件每周导出来看一眼。这样做有两个直接好处一是能准确知道哪些关键词在哪些时段有效哪些已经失效二是能定位误点击的来源比如某天发现大量日志指向“签到领金币”页面说明排除关键词漏了一个新入口。复盘数据还有一个价值它告诉你这工具到底值不值得持续维护。我有过连续两周只抢到九块八的记录也有过单日抢到五十多的记录。综合来看平均每晚不到一块钱但省下了每晚守着手机的时间。这个收益本身才是这个工具对我最大的价值。6. 这类工具现在的用法、边界和我踩过的坑最后聊聊更实际的问题这个东西到底能怎么用我叫它“龙虾版”但我不建议真把它当印钞机。先说边界。这类自动化辅助工具的合法性在不同环境、不同平台规则下差别很大。平台的服务条款通常禁止非官方手段干预正常活动流程但这属于平台与用户之间的协议约束而非法律明文禁止。在实际使用中工具本身没有破坏应用、没有篡改数据、没有伪造身份单纯是在用户已授权的前提下辅助触发用户本来可以手动完成的动作。这个边界是我给自己的底线只做“用户可以做但不想做的重复动作”的自动化不碰任何绕过风控验证、修改数据、批量注册之类的操作。然后是风控。从服务端的视角看自动化的点击行为模式是能被识别的识别带来的惩罚不一定是封号更多是降权降额、不给你推送高价值红包、增加验证码频率。我实测下来只要行为保持“人的节奏”惩罚力度就会弱很多。但我也有过一次因为点击过于频繁弹出了滑块验证那一单我直接放弃了没有继续处理。处理滑块验证这类行为极其容易滑向灰色地带我建议你也一样遇到验证就别碰关掉工具手动抢一下保持正常。还有隐私问题。无障碍服务天然能读取界面上的所有文本内容这意味着它理论上能看到你聊天记录、支付金额、验证码。所以这里有一个必须说清楚的合作原则这类工具的代码必须放在自己能看懂的范围内。我的工具从第一行代码到现在全部是本机运行没有接任何云端也没有把节点文本上传收集。你如果要复现建议也这么做。接云端的“智能识别”听着高级但你等于把屏幕内容交给了别人。顺带分享一个我踩过的坑早期版本为了“精准识别”把节点文本全部写入日志文件结果日志文件一天能涨十来兆里面全是各种页面的文本包括验证码消息。后来我把日志系统改成了只记录点击动作、不记录页面原文量级降到了每天几十 KB隐私风险也小得多。现在我使用这套工具的状态是晚上睡前打开设定活动时段第二天早上看一眼统计摘 要有记录就看一眼没记录也不管。它不一定能带来多高的收益但它让我养成了一个好习惯——把手动重复动作用工具替代把注意力放在更重要的事情上。最后说回工具本身。很多人问我这算不算“薅羊毛的尽头”。我的看法是它真正的尽头不是抢到多少钱而是让你理解了操作系统提供无障碍接口的初衷让机器代替人做那些能力之外或意愿之外的动作。这个思路的价值远不止于领个红包它同样适用于帮视力不便的人自动朗读页面帮老人自动点击繁琐的认证按钮帮打工人自动完成重复性的表单填写。方向对了技术不会白学。
返回列表