AccessibilityService原理及实践

3,513 阅读4分钟

一、AccessibilityService简介

辅助功能(AccessibilityService)是Android系统提供的一种服务,本身继承Service类。这个服务提供了增强的用户界面,旨在帮助残障人士或者可能暂时无法与设备充分交互的人们。

二、原理实现

1.png

1、系统启动时,会启动大量系统服务,其中就包括 AccessibilityManagerService AccessibilityManagerService(这里简称AMS)在创建时,会注册一些系统广播,包括应用状态变化广播 PackageMonitor。

2、PackageMonitor在有应用安装、卸载、更新时都会收到广播,在收到广播后,AMS会获取对应应用中注册的AccessibilityService,并保存该服务的信息,然后如果设置中开启了该服务,AMS中就会bindService方式启动该服务,并返回该服务的代理AccessibilityService.IAccessibilityServiceClientWrapper。通过该代理AMS可以与监听服务所在进程通信。

2.png

image.png

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 事件种类

序号种类名称触发时机
1TYPE_VIEW_CLICKED可点击的组件被点击
2TYPE_VIEW_LONG_CLICKED可点击的组件被长按
3TYPE_VIEW_SELECTED组件被选中
4TYPE_VIEW_FOCUSED组件获取到了焦点
5TYPE_VIEW_TEXT_CHANGED组件中的文本发生变化
6TYPE_VIEW_SCROLLED组件被滑动
7TYPE_WINDOW_STATE_CHANGEDdialog等被打开
8TYPE_NOTIFICATION_STATE_CHANGED通知弹出
9TYPE_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代码可以参考:

github.com/xuyisheng/A…

github.com/zfdang/Andr…