本文为稀土技能社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!
0x1、导言
Hi,我是杰哥,忙了好一阵子,总算有点时刻来继续填坑啦~
距离太久没更新,读者估量都忘掉这个专栏了,所以在开端本节前,再重复下这段话:
一切Android主动化结构和东西中 操作Android设备的功用实现 都基于 adb 和 无障碍服务AccessibilityService。
前面所学的 adb
更倾向于 PC端操控Android设备主动化,无论有线衔接仍是无线衔接,你都需求一台PC 来跑脚本。它的 不便利 还体现在:你写了一个很屌的脚本,跟亲朋好友Share,他们还得 安装装备一波运转环境 才能用。
而本节要学的 无障碍服务AccessibilityService
则更倾向于 APP操控Android设备主动化,把编写好的脚本打包成 Android APK安装包,直接把apk发给别人,安装了启动下无障碍服务,直接能用,相比之下便利得不是一星半点。当然,编写脚本需求一点 一点根本的Android开发经验。
AccessibilityService,别看名字长,其实一点都不难,本节学习道路如下:
- 简略了解下AccessibilityService是什么东西;
- AccessibilityService的根本运用,先跑起来再说;
- 掌握一些常用手段;
- 动手写个超简略的事例:主动登录Windows/Mac微信
没有前戏,我直接开端~
0x2、AccessibilityService简介
Android官方文档中有个专题 → 【打造无障碍运用】 其间包含了对 无障碍相关 的一系列解读,在Android开发者的公号里也有两篇解说的文章:
- 《让每一个人获益,收获更大的成功 | 一文了解无障碍体会》
- 《为运用打造更好的无障碍体会》
感兴趣的可移步至相关文章进行阅读,这儿不翻开讲,咱们更重视的是 无障碍服务的运用。点开官方文档:《创立自己的无障碍服务》,这样介绍到:
无障碍服务是一种运用,可供给界面增强功用,来帮忙残障用户或或许暂时无法与设备进行全面互动的用户完成操作。例如,正在开车、照顾孩子或参与喧哗聚会的用户或许需求其他或代替的界面反应办法。Android 供给了标准的无障碍服务(包括 TalkBack),开发者也能够创立和分发自己的服务。
简而言之便是:优化残障人士运用Android设备和运用程序的体会
读者看完这段话,估量是一脸懵逼,落地一下便是:运用这个服务主动操控其它APP的各种操作,如点击、滑动、输入等。然后文档下面有一个 留意:
只能是为了!!!
2333,在国内是不存在的,它的运用场景形形色色,但凡和 主动点 有关的都离不开它,如:灰产微商东西、开屏广告跳过、主动点击器、红包帮手、主动秒杀东西、一键XX、第三方运用监听等等。em…读者暂且把它理解成一个能够拿来帮助咱们主动点点点的东西就好,接着说下怎样用。
0x3、AccessibilityService根本运用
AccessibilityService无障碍服务 说到底,仍是一个 服务,那妥妥滴承继 Service,并具有它的生命周期和一些特性。
用户手动到设置里启动无障碍服务,体系绑定服务后,会回调 onServiceConnected()
,而当用户在设置中手动封闭、杀死进程、或开发者调用 disableSelf()
时,服务会被封闭毁掉。
关于它的根本用法十分简略,四步走~
① 自定义AccessibilityService
承继 AccessibilityService
,重写 onInterrupt()
和 onAccessibilityEvent()
办法,示例代码如下:
import android.accessibilityservice.AccessibilityService
import android.util.Log
import android.view.accessibility.AccessibilityEvent
class JumpAdAccessibilityService: AccessibilityService() {
val TAG = javaClass.simpleName
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
Log.d(TAG, "onAccessibilityEvent:$event")
}
override fun onInterrupt() {
Log.d(TAG, "onInterrupt")
}
}
上述两个办法是 必须重写 的:
-
onInterrupt()
→ 服务中断时回调; -
onAccessibilityEvent()
→ 接收到体系发送AccessibilityEvent时回调,如:顶部Notification,界面更新,内容改动等,咱们能够筛选特定的事情类型,履行不同的响应。比方:顶部呈现WX加好友的Notification Event,跳转到加好友页主动经过。
具体的Event类型可拜见文尾附录,另外两个 可选 的重写办法:
-
onServiceConnected()
→ 当体系成功衔接无障碍服务时回调,可在此调用 setServiceInfo() 对服务进行装备调整 -
onUnbind()
→ 体系将要封闭无障碍服务时回调,可在此进行一些封闭流程,如撤销分配的音频管理器
② Service注册
上面说了AccessbilityService实质仍是Service,所以需求在 AndroidManifest.xml
中进行注册:
<service
android:name=".JumpAdAccessibilityService"
android:exported="false"
android:label="跳过广告哈哈哈哈"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
</service>
Tips:设置 android:permission=”android.permission.BIND_ACCESSIBILITY_SERVICE” 是为了确保只要体系能够绑定此服务。而 android:label 是设置在无障碍服务那里文案,其它照抄。
③ 监听相关装备
便是监听什么类型的Event,监听什么app等的装备,装备办法有两种,二选一 即可~
动态装备
重写 onServiceConnected()
,装备代码示例如下:
override fun onServiceConnected() {
val serviceInfo = AccessibilityServiceInfo().apply {
eventTypes = AccessibilityEvent.TYPES_ALL_MASK
feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC
flags = AccessibilityServiceInfo.DEFAULT
packageNames = arrayOf("com.tencent.mm") //监听的运用包名,支撑多个
notificationTimeout = 10
}
setServiceInfo(serviceInfo)
}
特点与可选值详解可见文尾附录,接着说另一种装备办法~
静态装备
Android 4.0 后,能够在AndroidManifest.xml中添加一个引用装备文件的<meta-data>元素:
<service android:name=".JumpAdAccessibilityService"
...
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessible_service_config_jump_ad" />
能够看到resource特点会引用了一个xml文件,咱们来创立这个文件:
在 res 文件夹下 新建xml文件夹 (有的话不必建),然后 新建一个装备xml文件 (名字自己定),如:
内容如下:
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_desc"
android:notificationTimeout="100"
android:packageNames="com.tencent.mm"
android:settingsActivity="cn.coderpig.jumpad.MainActivity" />
特点与可选值详解可见文尾附录,说下两种装备办法的优缺点:
静态装备可装备特点更多,合适参数不需求动态改动的场景,动态装备特点有限,但灵活性较高,可按需修正参数,能够搭配运用。
④ 启用无障碍服务
二选一装备完毕后,运转APP,然后顺次翻开手机 (不同手机体系会有些许差异):设置 → 无障碍 → 找到咱们的APP → 显现封闭阐明无障碍服务没起来,点开:
开关翻开后,会弹出授权窗口,点击答应:
上面咱们设置监听的包名是com.tencent.mm,翻开微信,也能够看到操控台陆续输出一些日志信息:
能够,虽然没具体干点啥,但服务算是支棱起来了!!!
0x3、一些常用手段
无障碍服务的常用手段有这四个:判别无障碍服务是否敞开、结点查找、结点交互、大局交互。接着逐个解说:
① 判别无障碍服务是否翻开
这个没啥好讲的,直接上东西代码:
fun Context.isAccessibilitySettingsOn(clazz: Class<out AccessibilityService?>): Boolean {
var accessibilityEnabled = false // 判别设备的无障碍功用是否可用
try {
accessibilityEnabled = Settings.Secure.getInt(
applicationContext.contentResolver,
Settings.Secure.ACCESSIBILITY_ENABLED
) == 1
} catch (e: Settings.SettingNotFoundException) {
e.printStackTrace()
}
val mStringColonSplitter = SimpleStringSplitter(':')
if (accessibilityEnabled) {
// 获取启用的无障碍服务
val settingValue: String? = Settings.Secure.getString(
applicationContext.contentResolver,
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
)
if (settingValue != null) {
// 遍历判别是否包含咱们的服务
mStringColonSplitter.setString(settingValue)
while (mStringColonSplitter.hasNext()) {
val accessibilityService = mStringColonSplitter.next()
if (accessibilityService.equals(
"${packageName}/${clazz.canonicalName}",
ignoreCase = true
)
) return true
}
}
}
return false
}
每次翻开咱们的APP都调用下这个办法判别无障碍服务是否翻开,没有弹窗或许给提示,引导用户去 无障碍设置页设置下,跳转代码如下:
startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
设置完回来APP,再获取一次服务状况,所以主张在 onResume() 中调用,并做一些对应的UI更新操作。
② 节点查找
比方我要点击某个按钮,我需求先查找到节点,然后再触发点击交互,所以得先定位到节点。下述两个办法能够 获取当时页面节点信息AccessibilityNodeInfo:
AccessibilityEvent.getSource()
AccessibilityService.getRootInActiveWindow()
但要留意两个节点个数不一定相等,而获取到 AccessibilityNodeInfo 实例后能够经过下述办法定位结点(或许匹配到多个,所以回来类型是List<AccessibilityNodeInfo>):
-
AccessibilityNodeInfo.findAccessibilityNodeInfosByText()
→ 经过Text查找; -
AccessibilityNodeInfo.findAccessibilityNodeInfosByViewId()
→ 经过节点ID查找
依据文本匹配就不必说了,留意它是contain()包含匹配,不是equals()的办法就好,这儿首要说下如何获取 节点ID,需求用到一些东西,前三个是最常见的东西,从旧到新顺次是:
1、HierarchyView
老牌剖析东西,前期Android SDK有快捷办法,新版找不到了,得自己点击:android-sdk目录下的tools\monitor.bat 启动 Android Device Monitor:
然后点击生成节点数,会dump出节点树,点击相应节点获取所需数据:
直接生成当时页面节点树,便利易用,而且不止布局剖析,还有办法调用跟踪、文件管理器等,百宝箱啊,不过小卡,用的时候鼠标一向显现Loading。
2、UI Automator Viewer
比HierarchyView更纯粹,只要生成当时页面节点树的功用,新版相同找不到快捷办法了,得点击 android-sdk目录下的 tools\bin\uiautomatorviewer.bat 启动:
用法也相同简略,而且支撑保存节点树,不卡~
3、LayoutInspector
AS 3.0后撤销了老旧的DDMS后供给的界面更友好的全新东西,顺次点击:Tools → Layout Inspector 翻开:
然后挑选要监听的进程:
挑选完或许会一向转加载不出来,由于默许勾选了 Enable Live Layout Inspector 它会实时加载布局内容,关掉它。
顺次点击:File → Settings → Experimental → 找到Layout Inspector → 撤销勾选
确认后,此刻进口变成了这个:
挑选要查看的进程,OK,有多个Windows还能够自行挑选:
这儿笔者试了几次没load出微信的布局,不知道电脑太辣鸡仍是手机问题:
试了一个简略页面倒能够:
还有一点,选进程只能选可debug的进程,所以想调一切进程的话,要么虚拟机,要么Root了的真机,2333,虽然高档,可是用起来没前两个顺手。
4、其它东西
除上面三个之外其它都是一些小众东西了,如 autojs,划出左边面板 → 翻开悬浮框 → 点击悬浮图标翻开扇形菜单 → 点击蓝色的 → 挑选布局范围剖析 → 点击需求取得结点信息的区域。具体步骤如下图所示:
开发者帮手等东西获取办法也是类型。这儿顺带安利一波笔者在《学亿点有备无患的”姿态”》 写的东西代码 → 获取当时页面一切控件信息,直接调用下办法:
解析好的节点树直接dump出来,获取id便是这么so easy~
③ 节点交互
除了依据ID或文本定位到节点的办法外,还能够调用下述办法进行循环迭代:
- getParent() → 获取父节点;
- getChild() → 获取子节点;
- getChildCount() → 获取节点的子节点数;
获取节点后,能够调用 performAction() 办法对节点履行一个动作,如点击、长按、滑动等,直接上东西代码:
// 点击
fun AccessibilityNodeInfo.click() = performAction(AccessibilityNodeInfo.ACTION_CLICK)
// 长按
fun AccessibilityNodeInfo.longClick() =
performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK)
// 向下滑动一下
fun AccessibilityNodeInfo.scrollForward() =
performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)
// 向上滑动一下
fun AccessibilityNodeInfo.scrollBackward() =
performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD)
// 填充文本
fun AccessibilityNodeInfo.input(content: String) = performAction(
AccessibilityNodeInfo.ACTION_SET_TEXT, Bundle().apply {
putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, content)
}
)
④ 大局交互
除了控件触发事情外,AccessibilityService供给了一个 performGlobalAction()
来履行一些通用交互,示例如下:
performGlobalAction(GLOBAL_ACTION_BACK) // 回来键
performGlobalAction(GLOBAL_ACTION_HOME) // Home键
关于AccessibilityService常用手段就这些,接着写个超简略的例子来练练手~
0x4、超简略事例:主动登录Windows/Mac微信
登录过微信的PC,下次登录需求在手机上点击确认登录:
我有强迫症,每次下班都会退掉PC的微信,上班再重新登,每次都要点一下,不得不说有点蠢。
彻底能够用本节学的姿态写一个主动登录的小jio本啊,简略,也好演示脚本开发的根本流程~
① 判别无障碍服务是否敞开
直接在《AccessibilityService根本运用》的代码根底上进行开发,先撸出一个骚气的设置页:
接着是控件初始化,事情设置的一些简略逻辑:
运转下看看效果:
② 看下需求监听什么类型的Event
先把无障碍装备文件里的 android:accessibilityEventTypes
设置为 typeAllMask
,即监听一切类型的Event。接着直接把 onAccessibilityEvent()
的参数 event
打印出来:
运转后,敞开无障碍服务,接着点击登录/或许扫二维码,微信弹出登录页面,能够看到下述日志:
即翻开登录页会触发 TYPE_WINDOW_STATE_CHANGED
类型的 Event,且页面为 ExtDeviceWXLoginUI
。
行吧,那就只重视这类型的Event,把 android:accessibilityFeedbackType
设置为 typeWindowStateChanged
,改下 onAccessibilityEvent()
里的处理逻辑:
③ 找到登录按钮并触发点击
懒得用东西扣,直接用adb的脚本打印出节点树,直接就定位要找的节点了:
行吧,能够依据文本查找,也能够依据id查找,前者是contain()的办法匹配,包含登录文本的节点都会被选中:
而这儿的id是仅有的,所以直接依据id进行查找,找到后触发点击:
运转下看看效果:
脚本检测到登录页面,主动点击登录按钮,迅雷不及掩耳之势页面就关了~
0x5、小结
本节过了一下 AccessibilityService无障碍服务 的根底姿态,并写了一个超简略的微信主动登录事例演示脚本编写的大约过程,信任读者学完能够动手尝试编写一些简略的脚本。而在实践开发中还会遇到一些问题,如:获取到控件,但无法点击,在后续实战环节中会逐个涉猎,剧透下,下一节会带着大家来开发一个:微信僵尸好友检测东西,敬请期待~
参考文献
- 官方文档:《创立自己的无障碍服务》
- AccessibilityService运用入门
- (AccessibilityService) Android 辅佐功用笔记
- 辅佐功用 AccessibilityService笔记(2)
附录:特点、参数、可选值详解
Tips:下述内容或许过时,或许有部分不精确,主张以官方文档和源码为准
android:accessibilityEventTypes → AccessibilityServiceInfo.eventTypes
服务监听的事情类型,可选值有这些,支撑多个,特点值用|分隔,代码设置值用or分隔
描绘 | xml特点值 | 代码设置值 |
---|---|---|
一切类型的事情 | typeAllMask | xxx |
一个运用产生一个告诉事情 | typeAnnouncement | TYPE_ANNOUNCEMENT |
辅佐用户读取当时屏幕事情 | typeAssistReadingContext | TYPE_ASSIST_READING_CONTEXT |
view中上下文点击事情 | typeContextClicked | TYPE_VIEW_CONTEXT_CLICKED |
监测到的手势事情完成 | typeGestureDetectionEnd | TYPE_GESTURE_DETECTION_END |
开端手势监测事情 | typeGestureDetectionStart | TYPE_GESTURE_DETECTION_START |
Notification改动事情 | typeNotificationStateChanged | TYPE_NOTIFICATION_STATE_CHANGED |
接触阅读事情完成 | typeTouchExplorationGestureEnd | TYPE_TOUCH_EXPLORATION_GESTURE_END |
接触阅读事情开端 | typeTouchExplorationGestureStart | TYPE_TOUCH_EXPLORATION_GESTURE_START |
用户触屏事情结束 | typeTouchInteractionEnd | TYPE_TOUCH_INTERACTION_END |
接触屏幕事情开端 | typeTouchInteractionStart | TYPE_TOUCH_INTERACTION_START |
无障碍焦点事情清除 | typeViewAccessibilityFocusCleared | TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED |
取得无障碍的焦点事情 | typeViewAccessibilityFocused | TYPE_VIEW_ACCESSIBILITY_FOCUSED |
View被点击 | typeViewClicked | TYPE_VIEW_CLICKED |
View被长按 | typeViewLongClicked | TYPE_VIEW_LONG_CLICKED |
View被选中 | typeViewSelected | TYPE_VIEW_SELECTED |
View取得焦点 | typeViewFocused | TYPE_VIEW_FOCUSED |
一个View进入悬停 | typeViewHoverEnter | TYPE_VIEW_HOVER_ENTER |
一个View退出悬停 | typeViewHoverExit | TYPE_VIEW_HOVER_EXIT |
View翻滚 | typeViewScrolled | TYPE_VIEW_SCROLLED |
View文本改动 | typeViewTextChanged | TYPE_VIEW_TEXT_CHANGED |
View文字选中产生改动事情 | typeViewTextSelectionChanged | TYPE_VIEW_TEXT_SELECTION_CHANGED |
窗口的内容产生改动,或子树根布局产生改动 | typeWindowContentChanged | TYPE_WINDOW_CONTENT_CHANGE |
新的弹出层导致的窗口改动(dialog、menu、popupwindow) | typeWindowStateChanged | TYPE_WINDOW_STATE_CHANGED |
屏幕上的窗口改动事情,需求API 21+ | typeWindowsChanged | TYPE_WINDOWS_CHANGED |
UIanimator中在一个视图文本中进行遍历会产生这个事情,多个粒度遍历文本。一般用于语音阅读context | typeViewTextTraversedAtMovementGranularity | TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY |
android:accessibilityFeedbackType → AccessibilityServiceInfo.feedbackType
操作相关按钮后,服务给用户的反应类型,可选值如下:
描绘 | xml特点值 | 代码设置值 |
---|---|---|
撤销一切的反应办法,一般用这个 | feedbackAllMask | FEEDBACK_ALL_MASK |
可听见的(非语音反应) | feedbackAudible | FEEDBACK_AUDIBLE |
通用反应 | feedbackGeneric | FEEDBACK_GENERIC |
触觉反应(震动) | feedbackHaptic | FEEDBACK_HAPTIC |
语音反应 | feedbackSpoken | FEEDBACK_SPOKEN |
视觉反应 | feedbackVisual | FEEDBACK_VISUAL |
盲文反应 | 不支撑 | FEEDBACK_BRAILLE |
android:accessibilityFlags → AccessibilityServiceInfo.flags
辅佐功用附加的标志,可选值有这些,支撑多个,特点值用|分隔,代码设置值用or分隔:
描绘 | xml特点值 | 代码设置值 |
---|---|---|
默许装备 | flagDefault | DEFAULT |
为WebView中呈现的内容供给更好的辅佐功用支撑 | flagRequestEnhancedWebAccessibility | FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY |
运用该flag表明可获取到view的ID | flagReportViewIds | FLAG_REPORT_VIEW_IDS |
获取到一些被表明为辅佐功用无权获取到的view | flagIncludeNotImportantViews | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS |
监听体系的物理按键 | flagRequestFilterKeyEvents | FLAG_REQUEST_FILTER_KEY_EVENTS |
监听体系的指纹手势 API 26+ | flagRequestFingerprintGestures | FLAG_REQUEST_FINGERPRINT_GESTURES |
体系进入触控探索形式,呈现一个鼠标在用户的界面 | flagRequestTouchExplorationMode | FLAG_REQUEST_TOUCH_EXPLORATION_MODE |
如果辅佐功用可用,供给一个辅佐功用按钮在体系的导航栏 API 26+ | flagRequestAccessibilityButton | FLAG_REQUEST_ACCESSIBILITY_BUTTON |
要拜访一切交互式窗口内容的体系,这个标志没有被设置时,服务不会收到TYPE_WINDOWS_CHANGE事情 | flagRetrieveInteractiveWindows | FLAG_RETRIEVE_INTERACTIVE_WINDOWS |
体系内一切的音频通道,运用由STREAM_ACCESSIBILTY音量操控USAGE_ASSISTANCE_ACCESSIBILITY | flagEnableAccessibilityVolume | FLAG_ENABLE_ACCESSIBILITY_VOLUME |
android:canRetrieveWindowContent
服务是否能取回活动窗口内容的特点,与flagRetrieveInteractiveWindows搭配运用,无法在运转时更改此装备。
android:notificationTimeout → AccessibilityServiceInfo.notificationTimeout
同一类型的两个辅佐功用事情发送到服务的最短距离(毫秒,两个辅佐功用事情之间的最小周期)
android:packageNames → AccessibilityServiceInfo.packageNames
监听的运用包名,多个用逗号(,)离隔,两种办法设置监听一切运用的事情:
- 不设置此特点;
- 赋值null → android:packageNames=”@null”
网上一堆说空字符串的,都是没经过验证的,用空字符串你啥都捕获不到!!!
android:settingsActivity → AccessibilityServiceInfo.settingsActivityName
答应修正辅佐功用的activity类名,便是你自己的无障碍服务的设置页。
android:description
该服务的简略阐明,会显现在无障碍服务阐明页:
android:canPerformGestures
是否能够履行手势,API 24新增