Android 11无障碍连点器开发实战:从dispatchGesture到防杀保活
如果你手上有一台Android 11的设备应该体会过那种点到手抽筋的场景游戏里反复刷任务、测试应用时不停点同一个按钮、抢个体验资格要在固定位置连戳几十次。市面上的连点器不少最近圈子里讨论最多的要数actus连点器这类工具界面很友好但真到了“需要精确控制坐标、扛住后台清理、适配Android 11无障碍服务”的时候现成方案总会差一口气。所以我自己从头搭了一套名字就叫“连点器11”——既是第11个迭代版本也明确对标Android 11。这篇就把思路、原理和实操过程完整摊开讲适合被重复点击折磨的普通用户也适合想自己写自动点击逻辑的开发者参考。1. 连点器11要解决的重复点击难题1.1 现实中哪些场景需要连点器先说个最直白的场景很多游戏里都有“长按连续领取”“自动抽卡”“反复挑战关卡”这类设计点了第一下之后就知道后续完全是机械动作可手指一旦松开就得重来。再比如做App自动化回归测试同一个按钮一天要点几百次人肉点下去不光费时间还容易因为手抖点偏位置导致测试结果失真。还有填问卷、抢体验名额、批量添加好友这类操作本质都是“固定坐标固定间隔”的重复动作。连点器就是为这些场景设计的。它的核心逻辑非常简单按用户设定的坐标、间隔、次数循环模拟手指点击屏幕。听起来是小东西但真正实现起来如何准确模拟点击、如何保证长时间稳定运行、如何不被系统回收每一个环节都有讲究。1.2 为什么要把版本锁定在Android 11说句实话早期的连点器在Android 9、10上跑得挺欢但到了Android 11API 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:permissionandroid.permission.BIND_ACCESSIBILITY_SERVICE android:exportedtrue intent-filter action android:nameandroid.accessibilityservice.AccessibilityService / /intent-filter meta-data android:nameandroid.accessibilityservice android:resourcexml/accessibility_service_config / /service对应XML配置最关键的三行accessibility-service xmlns:androidhttp://schemas.android.com/apk/res/android android:accessibilityFlagsflagDefault|flagRetrieveInteractiveWindows android:canPerformGesturestrue android:canRetrieveWindowContenttrue android:descriptionstring/accessibility_description /这里最重要的就是canPerformGesturestrue它决定服务是否具备派发手势的权限。没有这一项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是按住时长单位毫秒。真人手指点一下屏幕大约会接触80120ms所以我通常把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推荐范围300500ms。点击时长固定为80ms。为了让行为更像真人还加了随机补偿机制每次点击坐标在基准点的基础上增加±12像素的随机偏移每次点击间隔也在设定值的基础上浮动±10%。比如你设定间隔500ms实际执行会在450550ms之间随机跳动坐标也会小幅抖动。这套逻辑对大多数应用的检测机制很有效。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 11API 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悬浮窗填入X540Y1300间隔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次就被拦截改成480650ms随机间隔后连点200次没有任何提示。5.4 耗电发热问题连点器长时间运行必然耗电。原因不是点击本身而是无障碍服务需要持续监听系统窗口变化加上悬浮窗实时刷新进度。我做了两处优化一是用一个低频Handler定时器代替while循环二是只在状态变化时刷新悬浮窗而不是每帧都刷新。优化之后同样跑1小时耗电从11%降到4%。5.5 问题排查速查表现象可能原因解决方式服务无法开启系统权限限制检查无障碍权限、自启动白名单、电池优化点一次后不再点击上一次手势未回调完成使用互斥标志位onCompleted后再发送下一次点击完全无效缺少canPerformGestures配置在配置XML里加android:canPerformGesturestrue点击位置和预设坐标不同系统导航栏或全屏模式影响改用绝对坐标保存横竖屏两套坐标连点几次后被拦截目标应用风控增加随机间隔、坐标抖动悬浮窗一闪而过未授予SYSTEM_ALERT_WINDOW到系统设置允许显示在其他应用上层连点器明明开着却不执行无障碍服务被后台清理按5.1流程设置系统白名单连点器这个东西听起来门槛极低但要做到在Android 11上稳定跑、不被系统杀、不误触、不被风控识别需要抠的细节非常多。尤其是Android 11以后无障碍服务的权限边界和服务生命周期都被收紧了一套完整的适配方案比单纯“写个循环调用点击”有用得多。连点器11跑到现在我最深刻的体会是真正决定体验的不是点击速度有多快而是这套工具能不能稳定地把该点的位置点准、能不能应付系统变化、能不能让使用者随时看得见执行结果。作为开发者把参数“随机化”和“服务存活保障”这两点做好比加一百个花哨功能都实在。如果你也想做一个自己的连点器工具不必依赖现成代码照着无障碍服务加悬浮窗这套思路从零搭起代码量并不大但踩完这些坑以后你会对Android 11的输入机制、服务生命周期有非常直观的理解。