一、AccessibilityService简介
辅助功能(AccessibilityService)是Android系统提供的一种服务,本身继承Service类。这个服务提供了增强的用户界面,旨在帮助残障人士或者可能暂时无法与设备充分交互的人们。
二、原理实现
1、系统启动时,会启动大量系统服务,其中就包括 AccessibilityManagerService AccessibilityManagerService(这里简称AMS)在创建时,会注册一些系统广播,包括应用状态变化广播 PackageMonitor。
2、PackageMonitor在有应用安装、卸载、更新时都会收到广播,在收到广播后,AMS会获取对应应用中注册的AccessibilityService,并保存该服务的信息,然后如果设置中开启了该服务,AMS中就会bindService方式启动该服务,并返回该服务的代理AccessibilityService.IAccessibilityServiceClientWrapper。通过该代理AMS可以与监听服务所在进程通信。
3、APP进程,UI变化/获取焦点/点击按钮…许多事件都会通过AccessibilityManager发送给AMS。 AccessibilityManager是AMS的代理,系统启动AMS时创建并缓存在ServiceManager.sCache中。 查看源码可知ViewRootImpl中有调用 AccessibilityManager,把UI信息发送给 AMS进程, AMS 进程 拿到事件后,通过 IAccessibilityServiceClientWrapper(模拟点击服务在AMS中的代理).onAccessibilityEvent()把UI信息发送到AccessibilityService模拟点击服务。
4、AccessibilityService 拿到UI信息后,根据信息判断与处理,这个判断和处理是 AccessibilityService模拟点击服务的开发者实现的。AccessibilityService中把对UI的处理封装之后,回调给AMS,AMS再回调给APP进程,在APP进程中根据处理信息对UI做响应操作(点击)。
常见 AccessibilityEvent 事件种类
序号 | 种类名称 | 触发时机 |
---|---|---|
1 | TYPE_VIEW_CLICKED | 可点击的组件被点击 |
2 | TYPE_VIEW_LONG_CLICKED | 可点击的组件被长按 |
3 | TYPE_VIEW_SELECTED | 组件被选中 |
4 | TYPE_VIEW_FOCUSED | 组件获取到了焦点 |
5 | TYPE_VIEW_TEXT_CHANGED | 组件中的文本发生变化 |
6 | TYPE_VIEW_SCROLLED | 组件被滑动 |
7 | TYPE_WINDOW_STATE_CHANGED | dialog等被打开 |
8 | TYPE_NOTIFICATION_STATE_CHANGED | 通知弹出 |
9 | TYPE_WINDOW_CONTENT_CHANGED | 组件树发生了变化 |
所有事件类型网址 developer.android.com/reference/a…
三、应用实践
要使用AccessibilityService实际上非常简单,一般来说,只需要以下三步即可。
继承系统AccessibilityService
public class MyAccessibility extends AccessibilityService {
private static final String TAG = "xys";
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.d(TAG, "onAccessibilityEvent: " + event.toString());
}
@Override
public void onInterrupt() {
}
}
其中有两个必须实现的方法:onAccessibilityEvent和onInterrupt。
在onAccessibilityEvent中,我们可以接收所监听的事件。不熟悉这些事件的话,只需要使用toString把这些信息打出来,自己多看几个Log,就大概能够了解了。
新建配置文件
在资源目录res下新建xml文件夹,新建accessibility.xml文件,写入:
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackSpoken"
android:canRetrieveWindowContent="true"
android:notificationTimeout="1000"/>
里面有一些比较简单的配置。typeAllMask是设置响应事件的类型,feedbackGeneric是设置回馈给用户的方式,有语音播出和振动。
注册
在AndroidMainifest中注册:
<service
android:name=".MyAccessibility"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility"/>
</service>
完成以上步骤后,一个AccessibilityService就可以使用了,你要知道的是,AccessibilityService具有很高的系统权限,所以,系统不会让App直接设置是否启用,需要用户进入设置-辅助功能中去手动启用,这样在一定程度上,保护了用户数据的安全。
应用实践
微信抢红包 保活 钉钉自动打卡 自动跳过开屏广告
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
switch (event.getEventType()) {
//界面变化
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
//根据控件id,点击跳过广告。示例是跳过网易音乐闪屏
event.getSource().findAccessibilityNodeInfosByViewId("com.netease.cloudmusic:id/c3l")
.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK);
//根据文本,点击跳过广告
List<AccessibilityNodeInfo> nodeInfoList = event.getSource().findAccessibilityNodeInfosByText("跳过");
for (AccessibilityNodeInfo info : nodeInfoList) {
CharSequence charSequence = info.getText();
if (charSequence != null) {
String msg = charSequence.toString();
if (msg.contains("跳过")) {
info.performAction(AccessibilityNodeInfo.ACTION_CLICK);
Toast.makeText(this, "跳过广告", Toast.LENGTH_SHORT).show();
}
}
}
//根据位置,点击跳过广告
Path path = new Path();
path.moveTo(1000, 100);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
GestureDescription.Builder builder = new GestureDescription.Builder().addStroke(new GestureDescription.StrokeDescription(path, start_time, duration));
dispatchGesture(builder.build(), null, null);
}
break;
}
}
四、如何防御
1.将控间属性clickable设置为false;
2.重写TextView 的 findViewsWithText方法,屏蔽文案检查;
3.设置无障碍代理,忽略AccessibilityService传过来的点击事件; onTouch替换onClick,屏蔽View的模拟位置点击事件;
//1)设置无障碍代理
adSkipView.setAccessibilityDelegate(object : AccessibilityDelegate() {
override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean
{
//忽略AccessibilityService传过来的点击事件以达到防止模拟点击的目的
return if (action == AccessibilityNodeInfo.ACTION_CLICK || action == AccessibilityNodeInfo.ACTION_LONG_CLICK)
{
true
} else
super.performAccessibilityAction(host, action, args)
}
})
//2)拦截模拟坐标点击
val accessibilityManager: AccessibilityManager = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
adSkipView.setOnTouchListener({ v, event ->
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (null != accessibilityManager && accessibilityManager.isEnabled()) {
//可以直接拦截无障碍的【坐标】点击,不影响用户正常的手动点击
adSkipView.setClickable(false)
}
}
false
})
五、启示
辅助功能(AccessibilityService)的核心就是两点:
1.是感知界面的变化;
2.是模拟点击;
对我们的启示,个人认为有:
1.自动化UI测试;
2.第三方APPorSDK界面事件监测;
3.系统操作的拓展:如监控到用户卸载我们APP进行提示,挽留客户等;
六、参考
作者:xuyisheng
链接:juejin.cn/post/684490…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
作者:Halifax
链接:juejin.cn/post/695393…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
csdn文章参考链接:blog.csdn.net/u010577768/…
gitHub代码可以参考: