模拟点击品类分析
相关软件:
按键小精灵免root版本(已分析)
total control
游戏蜂窝(已下线)
hamibot
auto.js
目标功能分析
hamibot
关键权限:无障碍服务
官网:https://hamibot.com/ , 能用来群控。
QQ音乐签到脚本为例子;
hamibot 可以实现,打开QQ音乐, 点击”我的“,点击”每日签到“,完成签到后,上划调出任务列表,划掉 QQ音乐。
关键点:
启动目标应用
目标点击位置确定手段
点击操作
auto.js
https://pro.autojs.org/docs/zh/guide/
关键权限:无障碍服务
思路类似, 分为开源版本和商用版本,原理应该是类似的,先看开源版本研究原理,再从商用版本验证。
关键点:
点击操作
Total Control
群控软件, http://www.sigma-rt.com/en/tc/script/js-api.php?version=9.0.u12&code=JS.API.List&verify=11372-3.4.0
看接口可能使用input event来实现
关键点:
点击操作
按键小精灵免root版本
关键权限:申请录制屏幕权限
申请录制权限,可能用于识图。
关键点:
启动应用
点击
从四个软件分析, 实现模拟点击分为3种情况
root权限
无障碍服务
免root,不使用无障碍
先从按键小精灵免root开始。
逆向分析
按键小精灵免root版本
先看logcat
发现关键类没有执行,发现了其他进程(能不能通过代码辅助定位到其他进程呢 , 这里有运气成分,猜出来了)。
悬浮窗进程在com.xxx.xxx:enginfloat , 悬浮窗的类com.cyjh.elfin.floatingwindowprocess.floatview.ElfinFloatView
点击开始时的日志
{"#":"com.cyjh.elfin.floatingwindowprocess.floatview.ElfinFloatView.b","args":[],"returns":{
"str":null}}
{"#":"com.cyjh.common.h.ad.c","args":[{"i":0,"o":"ElfinFloatView","s":"ElfinFloatView"},{"i":1,"o":"runScriptOperate
--> 1 isPreventRepeatedClick=false","s":"runScriptOperate --> 1 isPreventRepeatedClick=false"}],"returns":{"str":nu
ll}}
{"#":"com.cyjh.common.h.ad.c","args":[{"i":0,"o":"o","s":"o"},{"i":1,"o":"isValidAppId --> scriptAppId=257335","s":"
isValidAppId --> scriptAppId=257335"}],"returns":{"str":null}}
{"#":"com.cyjh.common.h.ad.c","args":[{"i":0,"o":"ElfinFloatView","s":"ElfinFloatView"},{"i":1,"o":"runScriptOperate
--> 3","s":"runScriptOperate --> 3"}],"returns":{"str":null}}
{"#":"com.cyjh.common.h.ad.b","args":[{"i":0,"o":"saveVersion ","s":"saveVersion "},{"i":1,"o":"ownVersion:167599595
8 saveVersion:1675995958 get appVersion:1675995958","s":"ownVersion:1675995958 saveVersion:1675995958 get appVersi
on:1675995958"}],"returns":{"str":null}}
{"#":"com.cyjh.common.h.ad.c","args":[{"i":0,"o":"ScriptRunHelper","s":"ScriptRunHelper"},{"i":1,"o":"updateVersionR
equest --> 1 ScriptId=1297cf77-4cfa-40fc-a757-d2fe4b374ec9,DeviceName=google938e21c5 AppVersion:1675995958","s":"upd
ateVersionRequest --> 1 ScriptId=1297cf77-4cfa-40fc-a757-d2fe4b374ec9,DeviceName=google938e21c5 AppVersion:167599595
8"}],"returns":{"str":null}}
8 saveVersion:1675995958 get appVersion:1675995958","s":"ownVersion:1675995958 saveVersion:1675995958 get appVersi
on:1675995958"}],"returns":{"str":null}}
{"#":"com.cyjh.elfin.floatingwindowprocess.floatview.ElfinFloatView.k","args":[],"returns":{"str":null}}
{"#":"com.cyjh.elfin.floatingwindowprocess.floatview.ElfinFloatView.onClick","args":[{"i":0,"o":"<instance: android.
view.View, $className: android.widget.LinearLayout>","s":"android.widget.LinearLayout{367d3c7 VFE...C.. ...P.... 0,0
-97,140 #7f1001b9 app:id/floatview_linearlayout_run}"}],"returns":{"str":null}}
{"#":"com.cyjh.common.h.ad.c","args":[{"i":0,"o":"ScriptRunHelper","s":"ScriptRunHelper"},{"i":1,"o":"onUpdateOtherI
nfo","s":"onUpdateOtherInfo"}],"returns":{"str":null}}
{"#":"com.cyjh.common.h.ad.c","args":[{"i":0,"o":"ScriptRunHelper","s":"ScriptRunHelper"},{"i":1,"o":"onUpdateHas","
s":"onUpdateHas"}],"returns":{"str":null}}
{"#":"com.cyjh.common.h.ad.b","args":[{"i":0,"o":"saveVersion ","s":"saveVersion "},{"i":1,"o":"ownVersion:167599595
8 saveVersion:1675995958 get appVersion:1675995958","s":"ownVersion:1675995958 saveVersion:1675995958 get appVersi
on:1675995958"}],"returns":{"str":null}}
{"#":"com.cyjh.common.h.ad.c","args":[{"i":0,"o":"ScriptRunHelper","s":"ScriptRunHelper"},{"i":1,"o":"runScriptOpera
te --> onCallbackSuc","s":"runScriptOperate --> onCallbackSuc"}],"returns":{"str":null}}
{"#":"com.cyjh.common.h.ae.b","args":[{"i":0,"o":"<instance: android.content.Context, $className: com.cyjh.elfin.bas
e.AppContext>","s":"com.cyjh.elfin.base.AppContext@b48138d"},{"i":1,"o":"runScriptOperate --> 准备启动引擎","s":"run
ScriptOperate --> 准备启动引擎"}],"returns":{"str":null}}
{"#":"com.cyjh.elfin.floatingwindowprocess.floatview.ElfinFloatView.e","args":[{"i":0,"o":"<instance: com.cyjh.elfin
.floatingwindowprocess.floatview.ElfinFloatView>","s":"com.cyjh.elfin.floatingwindowprocess.floatview.ElfinFloatView
{89561e2 V.E...... ........ 0,0-630,142}"}],"returns":{"val":false,"str":null}}
{"#":"com.cyjh.common.h.ad.c","args":[{"i":0,"o":"ScriptRunnerManager","s":"ScriptRunnerManager"},{"i":1,"o":"onStar
tScript","s":"onStartScript"}],"returns":{"str":null}}
{"#":"com.cyjh.common.h.ad.b","args":[{"i":0,"o":"saveVersion ","s":"saveVersion "},{"i":1,"o":"ownVersion:167599595
8 saveVersion:1675995958 get appVersion:1675995958","s":"ownVersion:1675995958 saveVersion:1675995958 get appVersi
on:1675995958"}],"returns":{"str":null}}
{"#":"com.cyjh.elfin.floatingwindowprocess.floatview.ElfinFloatView.a","args":[],"returns":{"str":null}}
{"#":"com.cyjh.elfin.floatingwindowprocess.floatview.ElfinFloatView.b","args":[],"returns":{"str":null}}
{"#":"com.cyjh.common.h.ad.c","args":[{"i":0,"o":"ScriptRunnerManager","s":"ScriptRunnerManager"},{"i":1,"o":"ddySta
ticsJudgeOperate --> 1","s":"ddyStaticsJudgeOperate --> 1"}],"returns":{"str":null}}
{"#":"com.cyjh.common.h.ae.a","args":[{"i":0,"o":"<instance: android.content.Context, $className: com.cyjh.elfin.bas
e.AppContext>","s":"com.cyjh.elfin.base.AppContext@b48138d"},{"i":1,"o":"是否是多多云环境:false","s":"是否是多多云
环境:false"}],"returns":{"str":null}}
{"#":"com.cyjh.common.h.ad.c","args":[{"i":0,"o":"ScriptRunnerManager","s":"ScriptRunnerManager"},{"i":1,"o":"ddySta
ticsJudgeOperate --> 2 isDdyEnvironment=false","s":"ddyStaticsJudgeOperate --> 2 isDdyEnvironment=false"}],"returns"
:{"str":null}}
启动应用
有点类似于VA ,按键小精灵自身来加载apk ,但是兼容性做的不好,实际无法自动打开app ,app会直接白屏。
执行脚本的日志。进程对象com.xxx.xxx:enginfloat
2023-02-10 21:32:53.634 10660-10705 AbstractHttpPresenter com.xxx.xxxx I decodeRes:{"SignContent":"{\u0022Code\u0022:200,\u0022Message\u0022:\u0022请求成功!\u0022,\u0022Data\u0022:{\u0022UpdateType\u0022:0,\u0022AppName\u0022:null,\u0022UpdateContent\u0022:null,\u0022UpdateUrl\u0022:null,\u0022PackageName\u0022:null,\u0022PackageSize\u0022:0,\u0022AppUpdateTime\u0022:0,\u0022StudioProjectKey\u0022:null,\u0022DeviceName\u0022:\u0022google0bf0f756\u0022,\u0022InstanceDataUploadInterval\u0022:0,\u0022UpgradeMode\u0022:0,\u0022AppVersion\u0022:0,\u0022ClientTimestamp\u0022:1676035973,\u0022ServerTimestamp\u0022:1676035585}}","Signature":"OEeQVSCwwdflhMgX/kgwnjGddqe0N0/fQ1BqT3ssHp52bXoF/KfrqxJTSiRfvT3IrdPIXQhcPqKPunQvKORBZ5r9q9Fo2y4bnrJ8ox4nuzxbvDOtaBEPWPsJfcvNW5ErCN2erssSCCyLrPgngWSQd2u6Yff5W/s9SPEzIutrFX0="}
2023-02-10 21:32:53.640 10660-10660 zzz com.xxx.xxxx D com.cyjh.share.AppStatisticsPresenter--loadScriptStartRunStatistics:ScriptStartRunParams{TemplateFileId=547, TemplateVersion='6.0.6', AppId=257335, AppVersion='1675995958', DeviceId='ef4cb63ba9cd42aae6fd6622b1303e75', ClientTimestamp=1676035973, IsRedFinger=0, DeviceCode='unknown', AppInfo='COBC41DkB9tAzYyE7OnKqfHuHjfB9QyEK6cQSTfTbbZZ6yMI4XQ/Jvdll5w5Ed04e/JnVcOeO4VZDIdm148Zz44o1bZX+rzu', V='81058250b7deb83d0', RegCode='', ScriptId='1297cf77-4cfa-40fc-a757-d2fe4b374ec9'}
2023-02-10 21:32:53.659 10660-10660 zzz com.xxx.xxxx D com.cyjh.share.AppStatisticsPresenter--loadScriptStartRunStatistics:http://auth2.mobileanjian.com/Script/PreStart
2023-02-10 21:32:53.738 10660-10706 AbstractHttpPresenter com.xxx.xxxx I decodeRes:{"SignContent":"{\u0022Code\u0022:200,\u0022Message\u0022:\u0022请求成功!\u0022,\u0022Data\u0022:{\u0022ExpireTime\u0022:253402272000,\u0022DailyTryTimes\u0022:0,\u0022OnceTryMinute\u0022:0,\u0022RunGuid\u0022:null,\u0022ScriptEncryptKey\u0022:\u0022c042549d079b4fe8811eb15220f74fbc\u0022,\u0022ClientTimestamp\u0022:1676035973,\u0022ServerTimestamp\u0022:1676035585}}","Signature":"nrwvto565D75j/DMyhXJS1TcmsfPCQubOGo5SmUEfDrtdJAcx5C6w5dA2jKH2QH5XlxLjjrLBssQCE0d2uEyzfnP4n7UM7QsyNrQrvIQ8wcptcoLWkAHRbOIMxtuQ2Zo0wJPUDLu1pMUWHj/ts0ZjzRUzxSR/BeFJyTVo5lAHG4="}
2023-02-10 21:32:53.744 10660-10660 zzz com.xxx.xxxx E com.cyjh.share.AppStatisticsPresenter--uiDataSuccess:{"Code":200,"Message":"请求成功!","Data":{"ExpireTime":253402272000,"DailyTryTimes":0,"OnceTryMinute":0,"RunGuid":null,"ScriptEncryptKey":"c042549d079b4fe8811eb15220f74fbc","ClientTimestamp":1676035973,"ServerTimestamp":1676035585}}
2023-02-10 21:32:53.750 10660-10660 EnginSdk com.xxx.xxxx D setScriptInfo lcPath:/data/user/0/com.xxx.xxxx/files/script.lc
2023-02-10 21:32:53.750 10660-10660 EnginSdk com.xxx.xxxx D setScriptInfo atcPath:/data/user/0/com.xxx.xxxx/files/script.atc
2023-02-10 21:32:53.750 10660-10660 EnginSdk com.xxx.xxxx D setScriptInfo uiCfgPath:/data/user/0/com.xxx.xxxx/files/script.cfg
2023-02-10 21:32:53.750 10660-10660 EnginSdk D setScriptInfo scripyEncryptKey:c042549d079b4fe8811eb15220f74fbc
2023-02-10 21:32:53.750 10660-10660 EnginRunnerManager com.xxx.xxxx D engin start
2023-02-10 21:32:53.752 10660-10660 EnginRunnerManager com.xxx.test D OnScriptListener onStartScript
2023-02-10 21:32:53.753 10660-10660 System.err com.xxx.xxxx W java.lang.NullPointerException: Attempt to invoke virtual method 'void android.os.Messenger.send(android.os.Message)' on a null object reference
2023-02-10 21:32:53.753 10660-10660 System.err com.xxx.xxxx W at com.cyjh.elfin.floatingwindowprocess.service.a.a(Unknown Source:19)
2023-02-10 21:32:53.753 10660-10660 System.err com.xxx.xxxx W at com.cyjh.elfin.floatingwindowprocess.d.b$2.b(Unknown Source:14)
2023-02-10 21:32:53.753 10660-10660 System.err com.xxx.xxxx W at com.elfin.engin.c$4.onStartScript(Unknown Source:21)
2023-02-10 21:32:53.753 10660-10660 System.err com.xxx.xxxx W at com.cyjh.mobileanjian.ipc.a.b$1.handleMessage(Unknown Source:55)
2023-02-10 21:32:53.753 10660-10660 System.err com.xxx.xxxx W at android.os.Handler.dispatchMessage(Handler.java:106)
2023-02-10 21:32:53.753 10660-10660 System.err com.xxx.xxxx W at android.os.Looper.loopOnce(Looper.java:201)
2023-02-10 21:32:53.753 10660-10660 System.err com.xxx.xxxx W at android.os.Looper.loop(Looper.java:288)
2023-02-10 21:32:53.753 10660-10660 System.err com.xxx.xxxx W at android.app.ActivityThread.main(ActivityThread.java:7842)
2023-02-10 21:32:53.753 10660-10660 System.err com.xxx.xxxx W at java.lang.reflect.Method.invoke(Native Method)
2023-02-10 21:32:53.754 10660-10660 System.err com.xxx.xxxx W at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
2023-02-10 21:32:53.754 10660-10660 System.err com.xxx.xxxx W at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
2023-02-10 21:32:53.767 10660-13676 Injector com.xxx.xxxx E GetFloatEvent:
2023-02-10 21:32:53.770 10660-10660 zzz com.xxx.xxxx D com.cyjh.share.AppStatisticsPresenter--loadScriptStartRunStatistics:http://auth2.mobileanjian.com/Script/Start
2023-02-10 21:32:53.817 10660-10660 SYS com.xxx.xxxx I command finished.
设计架构和按键精灵一致 ,悬浮窗或者服务,发送消息给其他进程完成操作
找到对应的handler处理函数
运行的实例
核心找f的实例,调用f实例的b方法。
OK , com.cyjh.mq.sdk.MqRunnerLite , 定位到最内部的一层
com.elfin.engin.c$4
这个方法是个接口 ,需要找到其实现。
com.cyjh.elfin.floatingwindowprocess.d.b$2
追踪不到谁处理了这个消息
根据关键日志定位到关键类:
能对应上功能和脚本,
继续往下分析,利用Instrumentation实现按键的模拟点击。怪不得需要实现应用内部打开app。
hamibot
使用自动化脚本, 是一个QQ音乐简单的签到脚本。记录日志
2023-02-16 14:18:40.704 22638-26280 RhinoJavaScriptEngine com.hamibot.hamibot D forceStop: interrupt Thread: Thread[ScriptThread-1[63e343f0fcb80131270dd286.js],5,]
2023-02-16 14:18:40.707 22638-26281 ContextFactory com.hamibot.hamibot D onContextCreated: count = 1
2023-02-16 14:18:40.816 22638-26281 GlobalConsole com.hamibot.hamibot D 14:18:40.816/V: 开始运行「63e343f0fcb80131270dd286.js」
2023-02-16 14:18:40.823 22638-26281 GlobalConsole com.hamibot.hamibot D 14:18:40.823/D: 无需解锁
2023-02-16 14:18:40.836 22638-22638 NotificationObserver com.hamibot.hamibot D onNotification: [无需解锁]; EventType: TYPE_NOTIFICATION_STATE_CHANGED; EventTime: 1571052171; PackageName: com.hamibot.hamibot; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.Toast; Text: [无需解锁]; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; Enabled: false; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: 0; ScrollY: 0; MaxScrollX: 0; MaxScrollY: 0; ScrollDeltaX: -1; ScrollDeltaY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
2023-02-16 14:18:41.917 30130-32660 ActivityTaskManager system_process W Background activity start for com.hamibot.hamibot allowed because SYSTEM_ALERT_WINDOW permission is granted.
2023-02-16 14:18:41.928 22638-26281 GlobalConsole com.hamibot.hamibot D 14:18:41.928/D: 等待QQ音乐启动,因为有开屏广告,要多等一下下
2023-02-16 14:18:42.032 22638-22638 ActivityInfoProvider com.hamibot.hamibot D setLatestComponent: com.tencent.qqmusic/com.tencent.qqmusic.activity.AppStarterActivity com.tencent.qqmusic/com.tencent.qqmusic.activity.AppStarterActivity
2023-02-16 14:18:42.067 22638-22638 ActivityInfoProvider com.hamibot.hamibot D setLatestComponent: com.tencent.qqmusic/com.tencent.qqmusic.activity.AppStarterActivity com.tencent.qqmusic/com.tencent.qqmusic.activity.AppStarterActivity
2023-02-16 14:18:42.838 30130-32647 NotificationService system_process W Toast already killed. pkg=com.hamibot.hamibot token=android.os.BinderProxy@12fd020
2023-02-16 14:18:42.860 22638-22638 NotificationObserver com.hamibot.hamibot D onNotification: [等待QQ音乐启动,因为有开屏广告,要多等一下下]; EventType: TYPE_NOTIFICATION_STATE_CHANGED; EventTime: 1571054183; PackageName: com.hamibot.hamibot; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.Toast; Text: [等待QQ音乐启动,因为有开屏广告,要多等一下下]; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; Enabled: false; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: 0; ScrollY: 0; MaxScrollX: 0; MaxScrollY: 0; ScrollDeltaX: -1; ScrollDeltaY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
2023-02-16 14:18:44.844 30130-32216 NotificationService system_process W Toast already killed. pkg=com.hamibot.hamibot token=android.os.BinderProxy@54fee81
2023-02-16 14:18:47.226 22638-26281 GlobalConsole com.hamibot.hamibot D 14:18:47.225/D: 跳转我的信息页面
2023-02-16 14:18:52.016 22638-22638 ActivityInfoProvider com.hamibot.hamibot D setLatestComponent: com.android.systemui/android.view.View com.android.systemui/android.view.View
2023-02-16 14:18:54.127 22638-22638 ActivityInfoProvider com.hamibot.hamibot D setLatestComponent: com.android.systemui/android.view.View com.android.systemui/android.view.View
2023-02-16 14:18:56.002 22638-26281 GlobalConsole com.hamibot.hamibot D 14:18:56.001/D: 今日已签到
2023-02-16 14:18:56.003 22638-26281 GlobalConsole com.hamibot.hamibot D 14:18:56.003/D: 退出程序
2023-02-16 14:18:56.060 22638-22638 NotificationObserver com.hamibot.hamibot D onNotification: [今日已签到]; EventType: TYPE_NOTIFICATION_STATE_CHANGED; EventTime: 1571067380; PackageName: com.hamibot.hamibot; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.Toast; Text: [今日已签到]; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; Enabled: false; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: 0; ScrollY: 0; MaxScrollX: 0; MaxScrollY: 0; ScrollDeltaX: -1; ScrollDeltaY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
2023-02-16 14:18:56.060 22638-22638 ActivityInfoProvider com.hamibot.hamibot D setLatestComponent: com.google.android.googlequicksearchbox/android.widget.FrameLayout com.google.android.googlequicksearchbox/android.widget.FrameLayout
2023-02-16 14:18:57.810 22638-26281 RhinoJavaScriptEngine com.hamibot.hamibot D forceStop: interrupt Thread: Thread[ScriptThread-2[63e343f0fcb80131270dd286.js],5,main]
2023-02-16 14:18:57.818 22638-26281 RunnableJSExecution com.hamibot.hamibot W onException: engine = ScriptEngine@8d7b97d{id=2,source='63e343f0fcb80131270dd286.js',cwd='/storage/emulated/0/Hamibot'}
2023-02-16 14:18:57.826 22638-26281 GlobalConsole com.hamibot.hamibot D 14:18:57.826/V: 「63e343f0fcb80131270dd286.js」运行结束,用时17.009000秒
2023-02-16 14:18:57.826 22638-26281 RunnableJSExecution com.hamibot.hamibot D Engine destroy
2023-02-16 14:18:57.827 22638-26281 ScriptRuntime com.hamibot.hamibot D on exit
2023-02-16 14:18:57.828 22638-26281 RhinoJavaScriptEngine com.hamibot.hamibot D on destroy
2023-02-16 14:18:57.828 22638-26281 ContextFactory com.hamibot.hamibot D onContextReleased: count = 0
通过日志,判断客户端也是使用了js解释器,执行js代码。 打开的android 的触摸轨迹追踪 , 发现只有最后划掉应用时,出现了轨迹。
可以猜测打开应用实现的点击操作可能是解析目标view ,调用其onClick方法实现。
尝试获取js代码。
如何打开目标应用
hook 下无障碍获取view的方法
hook 下无障碍实现点击的方法
分析到了Script的对象, 直接获取到了js代码
dump 完整的js,看下js的主要代码逻辑
不出所料是解析的view,再调用click方法。
https://developer.android.com/guide/topics/ui/accessibility/service?hl=zh-cn#event-details 查看官网无障碍的功能说明和实现功能的接口。
解析view,并获取目标view。
通过无障碍服务dispatchGesture 方法传递按键手势。
auto.js
hamibot就是封装的auto.js, 分析结果与hamibot相同。
Total Control
windows端 ,会在设备安装apk ,实际卸载也不影响功能。。。暂不分析(投屏手段)。
分析结果
抛开windows 端 , 在android端实现的模拟点击手段有:
root权限权限下对/dev/input/* 的事件做监控获取到按键行为或对文件写入事件实现模拟点击的行为。(按键XXROOT版本)
非ROOT权限下,实现应用内打开目标app ,使用Instrumentation来模拟按键行为,开发难度主要在兼容性上(按键XXX免ROOT版本)
非ROOT权限下,使用无障碍服务,解析view实现控件的点击,和使用无障碍服务接口实现屏幕手势,开发简单。(hamibot、auto.js)
[培训]《安卓高级研修班(网课)》月薪三万计划