☰
Android 11无障碍连点器开发实战:从dispatchGesture到防杀保活
2026/10/7 9:49:38 网站建设 项目流程

如果你手上有一台Android 11的设备,应该体会过那种点到手抽筋的场景:游戏里反复刷任务、测试应用时不停点同一个按钮、抢个体验资格要在固定位置连戳几十次。市面上的连点器不少,最近圈子里讨论最多的要数actus连点器这类工具,界面很友好,但真到了“需要精确控制坐标、扛住后台清理、适配Android 11无障碍服务”的时候,现成方案总会差一口气。所以我自己从头搭了一套,名字就叫“连点器11”——既是第11个迭代版本,也明确对标Android 11。这篇就把思路、原理和实操过程完整摊开讲,适合被重复点击折磨的普通用户,也适合想自己写自动点击逻辑的开发者参考。

1. 连点器11要解决的重复点击难题

1.1 现实中哪些场景需要连点器

先说个最直白的场景:很多游戏里都有“长按连续领取”“自动抽卡”“反复挑战关卡”这类设计,点了第一下之后就知道后续完全是机械动作,可手指一旦松开就得重来。再比如做App自动化回归测试,同一个按钮一天要点几百次,人肉点下去不光费时间,还容易因为手抖点偏位置导致测试结果失真。还有填问卷、抢体验名额、批量添加好友这类操作,本质都是“固定坐标+固定间隔”的重复动作。

连点器就是为这些场景设计的。它的核心逻辑非常简单:按用户设定的坐标、间隔、次数,循环模拟手指点击屏幕。听起来是小东西,但真正实现起来,如何准确模拟点击、如何保证长时间稳定运行、如何不被系统回收,每一个环节都有讲究。

1.2 为什么要把版本锁定在Android 11

说句实话,早期的连点器在Android 9、10上跑得挺欢,但到了Android 11(API 30)之后,问题开始集中爆发。主要原因有三个。

第一,Android 11对无障碍服务(AccessibilityService)的约束更严格了。系统在用户开启服务时会明确提示风险,部分国产系统还会在后台权限里把无障碍服务当成“不信任对象”,动不动就把它清掉。你这边的服务一挂,连点器自然停工。

第二,Android 11引入了软件包可见性限制。如果连点器需要读取当前前台应用、判断你有没有停在目标界面,就必须在清单文件里声明查询权限,不然前台包名永远是null。

第三,手势派发精度问题。老一代连点器很多用MotionEvent注入的方式,在Android 11上部分应用会过滤掉非系统来源的事件,导致点了没反应。主流的替代方案是使用AccessibilityService的dispatchGesture手势派发,需要重新适配。

所以“连点器11”这个版本,核心目标不是写一个“能跑就行”的脚本,而是把Android 11下这些坑一个个填平。

2. 自动点击方案选型,哪种更适合自己做

做连点器之前,先别急着写代码。自动点击在Android上有好几种实现路线,选错了后面全白搭。我把常用的四种方案逐个说清楚,再给结论。

2.1 无障碍服务方案

这是我现在推荐的方案,也是连点器11采用的主方案。AccessibilityService本身是系统无障碍框架的一部分,本意是帮助视力障碍用户操作手机,但它额外提供了dispatchGesture()方法——允许服务向系统派发手势事件,包括点击、滑动、长按。因为事件是从系统无障碍通道发出去的,普通应用很难区分这到底是真人手指还是程序模拟。

优点很突出:不需要root,用户在“设置-无障碍”里手动开启一次即可;手势坐标由服务精确控制;支持后台运行,只要服务不被系统杀死,切到后台也能持续点击。缺点也存在:dispatchGesture()依赖系统窗口内容交互,如果目标应用自己做了风控检测,快速点击仍然可能被识别;另外部分深度定制ROM会限制无障碍服务常驻。

2.2 ADB方案

通过数据线把手机连到电脑,执行ADB命令来模拟点击。最简单的一句就是:

adb shell input tap 540 960

这条命令的意思是,在屏幕上坐标(540, 960)的位置模拟一次手指点击。如果想长按,用swipe命令,起点和终点相同、再给一个持续时间:

adb shell input swipe 540 960 540 960 800

这个方案最大的好处是省事,不用写App,只要电脑装了ADB工具就能用。适合临时调试、一次性批量操作。缺点更明显:必须保持手机与电脑连接;每次点击都要经过PC转发,延时不稳定;在息屏场景下有时点不了。所以它只作为开发和调试辅助,不适合作为独立产品。

2.3 root与底层事件注入

如果手机已经root,可以往内核输入设备节点直接写入触摸事件,也就是sendevent方式。这种方式产生的触摸事件和硬件上报的事件几乎一致,连连锁检测都很难识别。但它对设备和内核版本的依赖非常强,不同手机的事件节点路径不一样,同一款设备不同系统版本也可能变化。再加上root本身的安全风险,普通用户基本不会走这条路。我在连点器11里完全放弃了它。

2.4 方案对比与最终选择

方案是否需要root是否需要连接电脑点击真实度实现难度后台稳定性
无障碍服务dispatchGesture否否较高中等较好
ADB input否是中低一般
sendevent是否高高好
悬浮窗+无障碍组合否否较高中高较好

最终结论是:普通用户和开发者最合适的组合是“悬浮窗控制层 + 无障碍服务执行层”,也就是用悬浮窗承载开始、停止、参数设置这些控制入口,用无障碍服务里的dispatchGesture完成实际点击。actus这类工具在界面上做得很成熟,但自己实现时,核心就是这套架构。

3. 连点器11的核心实现拆解

3.1 工程结构和模块划分

连点器11的工程结构划分得很清晰,整体分成三层。最上层是控制层,MainActivity负责首次授权引导和参数设置;中间是悬浮窗服务,承载一个全局悬浮窗,无论你切到哪个应用都能看到控制面板;最底层才是AutoClickService,也就是无障碍服务,它接收悬浮窗传过来的坐标和延时参数,调用dispatchGesture执行手势。

这种分层最大的好处是职责单一。悬浮窗就算因为权限问题没能显示,无障碍服务依然能通过前台通知栏的快捷开关来触发连点。也就是说,操作入口和动作执行是解耦的,不会因为某一块挂掉就全军覆没。

3.2 无障碍服务注册与配置

要让系统认识你的无障碍服务,必须做两件事:在Manifest里注册、在res/xml目录下提供配置。Manifest里注册是这样写的:

<service android:name=".AutoClickService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" android:exported="true"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service>

对应XML配置最关键的三行:

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows" android:canPerformGestures="true" android:canRetrieveWindowContent="true" android:description="@string/accessibility_description" />

这里最重要的就是canPerformGestures="true",它决定服务是否具备派发手势的权限。没有这一项,dispatchGesture怎么调都会回调onCancelled。我第一版就卡在这里,服务一切正常但点击永远失败,排查了半天才发现配置里漏了它。flagRetrieveInteractiveWindows允许你获取交互窗口的内容,如果你想做“读取控件id然后点击”,需要用到。

3.3 手势派发:dispatchGesture的真相

Android的输入事件要走完“内核事件上报—InputManager接收—窗口分发”这条链路,普通App想要模拟点击,只能靠注入。而dispatchGesture的底层是直接通过AccessibilityService向InputManager提交手势描述,由系统负责把这段手势“翻译”成触摸事件流。这意味着点击的起点、路径、时长都由你决定,但系统是否毫秒不差地执行,还要看当前系统负载。

一个最简单的点击手势的代码是这样的:

private fun performClick(x: Int, y: Int) { val path = Path().apply { moveTo(x.toFloat(), y.toFloat()) } val stroke = GestureDescription.StrokeDescription(path, 0, 50) val gesture = GestureDescription.Builder().addStroke(stroke).build() dispatchGesture(gesture, object : AccessibilityService.GestureResultCallback() { override fun onCompleted(gestureDescription: GestureDescription?) { Log.i("AutoClick", "点击完成: $x, $y") } override fun onCancelled(gestureDescription: GestureDescription?) { Log.w("AutoClick", "点击被取消: $x, $y") } }, null) }

这里的50是按住时长,单位毫秒。真人手指点一下屏幕,大约会接触80~120ms,所以我通常把50改成80会更接近真实操作。StrokeDescription三个参数分别是运动轨迹、起始时间、持续时间。startTime设为0表示立即开始,持续时间决定手指在屏幕上停留多久。

这里踩过一个坑:dispatchGesture必须在主线程调用,同时在Android 11上,同一时间只能有一个未完成的GestureDescription。如果你用循环连点,上一轮手势还没回调onCompleted下一轮就开始,系统会直接取消前者。解决方式就是做一个互斥标志位,只有onCompleted才允许发送下一个点击。

3.4 坐标获取与分辨率适配

连点器连的是坐标,坐标从哪来?最直观的办法是打开开发者选项里的“显示指针位置”,手指按到目标按钮上,屏幕顶部就会显示当前的X和Y坐标。比如我在练习App里看到“立即领取”按钮中心坐标是(540, 1300),直接填进连点器就行。

真正麻烦的是分辨率适配。连点器记录的是绝对像素坐标,同样一个按钮,在一台1080x2400的手机上和在另一台1440x3200的手机上,坐标完全不同。我在连点器11里加了一个“坐标校准”流程:记录一个基准点,换算成以左上角为原点的比例坐标,换设备后自动按新屏幕尺寸等比缩放。当然,如果你只是在自己手机上用,直接写死坐标也没问题。

如果你想要更精准的定位,可以利用AccessibilityNodeInfo读取当前窗口里的控件。先拿到当前前台窗口的根节点,然后递归查找文本或者内容描述匹配的节点,读取它的getBoundsInScreen()得到控件在屏幕上的绝对坐标。这种做法适合“不管按钮挪到哪里都能找到”的场景,代价是需要canRetrieveWindowContent权限,并且每次点击前都要做一次节点查找,性能消耗高一些。

3.5 参数设计:点击间隔、时长与随机补偿

连点器能配置的无非是这四项:点击坐标、点击时长(stroke duration)、点击间隔(两次点击之间的时间差)、循环次数。其中点击间隔最要命。真人再快,1秒钟点5次也到头了,所以如果连点器把间隔设成50ms,目标应用只要稍微有点防连点逻辑,立刻能识别出这是脚本。

我的默认策略是间隔不低于200ms,推荐范围300~500ms。点击时长固定为80ms。为了让行为更像真人,还加了随机补偿机制:每次点击坐标在基准点的基础上增加±1~2像素的随机偏移,每次点击间隔也在设定值的基础上浮动±10%。比如你设定间隔500ms,实际执行会在450~550ms之间随机跳动,坐标也会小幅抖动。这套逻辑对大多数应用的检测机制很有效。

3.6 悬浮窗控制面板实现

悬浮窗的作用是让你在任何界面都能看到“开始”“停止”“当前次数”等状态。Android 11里悬浮窗类型必须使用TYPE_APPLICATION_OVERLAY,并且需要Manifest里声明SYSTEM_ALERT_WINDOW权限。启动代码如下:

val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager val params = WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT )

悬浮窗默认置顶,不能抢占焦点,否则你手指去点应用里的按钮时,点到的就是悬浮窗了。拖拽位置保存到SharedPreferences里,下次启动时按上次位置恢复。用户点悬浮窗里的“设置”,再呼出完整面板调整坐标和间隔。

4. 完整实操流程:从装机到跑通一次连点

4.1 环境准备与编译

我用的开发环境是Android Studio Koala版本,SDK平台选Android 11(API 30),编译版本和targetSdk都设成30或以上。编译连点器11之前需要先把Manifest里的权限补全,除了无障碍服务相关配置,还需要SYSTEM_ALERT_WINDOW和FOREGROUND_SERVICE。如果你要读取当前前台应用名称,还要申请PACKAGE_USAGE_STATS特殊权限。

准备一台Android 11的真机,建议先把“开发者选项—显示指针位置”打开,方便读取坐标。如果你要用ADB辅助调试,再打开“USB调试”。

4.2 权限配置与无障碍服务启用

安装APK后,先去设置里允许悬浮窗权限,路径一般是“设置—应用—连点器11—显示在其他应用上层”。紧接着去“设置—无障碍—已安装的服务”里找到连点器11,手动开启开关。这一步系统会弹窗提示“此服务可能会影响你使用其他应用”,点击确定。

注意,Android 11对无障碍服务的开启做了额外保护,有些第三方系统(比如ColorOS、MIUI)还会在系统设置里藏一个“省电管理”,即使你在这里开启了,后台清理白名单没设置好,服务照样会被杀掉。最佳实践是装完就往系统“自启动管理”里把连点器11加入白名单,并把电池策略设为“无限制”。

4.3 一个实际的连点任务配置

我用一个具体任务演示:某个练习App的“领取奖励”按钮坐标在(540, 1300),需要连续点击30次,每次间隔400ms。打开连点器11悬浮窗,填入X=540,Y=1300,间隔=400,次数=30,然后点“开始”。悬浮窗上会显示当前执行进度,比如“5/30”,上面再放一个“停止”按钮,随时中断。

代码里对应的循环逻辑大致是这样的:

for (count in 1..totalCount) { if (!isRunning) break performClick(x, y) Thread.sleep(intervalMillis) }

但实际上不能直接在主线程sleep,否则ANR。我用的方式是在子线程里循环,每次执行完点击之后,通过Handler把下一次点击post回主线程,同时延迟intervalMillis毫秒。

4.4 运行检查与日志分析

跑起来之后,用Logcat确认服务状态。过滤关键词“AutoClick”,正常会看到连贯的“点击完成: x, y”日志。如果看到“点击被取消”,多半是上一次手势还没结束就派发了新的,或者手势器件的路径没有被正确创建。如果完全没有日志,检查服务是否真的绑定了:直接在悬浮窗设一个开关,操作时看它有没有状态变化。

跑完30次后到应用里看实际结果。拿练习App举例,奖励数量应该刚好增加30次。如果发现中间有几次没点到,把间隔调大一点,同时检查点击坐标是否被系统手势导航区域遮盖。Android 11全面屏设备底部有一段“手势条”,如果坐标设得太靠下,点击会被系统拦截。

5. 常见问题与排查技巧实录

5.1 服务被杀与无法启动

连点器11最常见的故障就是服务莫名其妙停了。症状是悬浮窗明明还在,但实际点击已经失效,去看系统无障碍设置,开关处于关闭状态。

排查顺序是:第一步,进系统“电池优化”页面,把连点器11设为“不优化”;第二步,在多任务界面把App卡片下拉锁定;第三步,在系统的“自启动管理”里允许应用自启动;第四步,如果还是被杀,检查是不是系统内存不足。我在这台测试机上反复实验后确认,国产ROM里前三步缺一不可,只做其中一步会复发。

还有一个坑:Android 11上如果用户手动在设置里关闭了无障碍服务,悬浮窗并不会收到任何回调通知。所以我在悬浮窗里加了服务存活探测,每秒检查AccessibilityService.isServiceEnabled(),一旦发现被关闭,就弹窗引导用户重新打开。

5.2 坐标偏移与点击无效

坐标偏移多半是分辨率变化引起的。横屏和竖屏的分辨率是不同的,如果连点器只在竖屏下校准过,切到横屏后就全偏了。最简单的解决方式是在连点器11里保存两套坐标,一套横屏一套竖屏,根据系统当前屏幕朝向自动切换。

更大的坑是导航栏。某些全屏应用会隐藏底部导航栏,坐标计算基础值就会变化,应用退出全屏模式后点击位置整体上移。这种情况建议以“显示屏绝对坐标”为基准,不要用相对应用窗口的坐标。

5.3 连点过快被风控或卡顿

很多应用对高频点击做了防护。表现是前几次点有效,第10次开始全部失效,或者直接弹“操作过于频繁”。这不是连点器的问题,是目标应用的风控策略。

解决办法就是把间隔调大。但更大价值的一招是使用随机化:把固定间隔改成随机间隔,点击坐标也加入微小抖动,前文已经说过具体实现。实测中,500ms固定间隔在某个抢单App里第7次就被拦截,改成480~650ms随机间隔后连点200次没有任何提示。

5.4 耗电发热问题

连点器长时间运行必然耗电。原因不是点击本身,而是无障碍服务需要持续监听系统窗口变化,加上悬浮窗实时刷新进度。我做了两处优化:一是用一个低频Handler定时器代替while循环,二是只在状态变化时刷新悬浮窗,而不是每帧都刷新。优化之后同样跑1小时,耗电从11%降到4%。

5.5 问题排查速查表

现象可能原因解决方式
服务无法开启系统权限限制检查无障碍权限、自启动白名单、电池优化
点一次后不再点击上一次手势未回调完成使用互斥标志位,onCompleted后再发送下一次
点击完全无效缺少canPerformGestures配置在配置XML里加android:canPerformGestures="true"
点击位置和预设坐标不同系统导航栏或全屏模式影响改用绝对坐标,保存横竖屏两套坐标
连点几次后被拦截目标应用风控增加随机间隔、坐标抖动
悬浮窗一闪而过未授予SYSTEM_ALERT_WINDOW到系统设置允许显示在其他应用上层
连点器明明开着却不执行无障碍服务被后台清理按5.1流程设置系统白名单

连点器这个东西听起来门槛极低,但要做到在Android 11上稳定跑、不被系统杀、不误触、不被风控识别,需要抠的细节非常多。尤其是Android 11以后,无障碍服务的权限边界和服务生命周期都被收紧了,一套完整的适配方案比单纯“写个循环调用点击”有用得多。

连点器11跑到现在,我最深刻的体会是:真正决定体验的不是点击速度有多快,而是这套工具能不能稳定地把该点的位置点准、能不能应付系统变化、能不能让使用者随时看得见执行结果。作为开发者,把参数“随机化”和“服务存活保障”这两点做好,比加一百个花哨功能都实在。如果你也想做一个自己的连点器工具,不必依赖现成代码,照着无障碍服务加悬浮窗这套思路从零搭起,代码量并不大,但踩完这些坑以后,你会对Android 11的输入机制、服务生命周期有非常直观的理解。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询