Accessibility Service介绍与使用


Android系统提供的辅助功能,旨在帮助身体不便或操作不灵活的人辅助使用手机应用
一般在手机中 叫 无障碍模式
当然 也可以用于一些其他事情 如 自动抢红包 静默安装apk 自动点击弹框等

需要用户主动打开无障碍模式,并且打开所需的特定的无障碍服务 该服务才会生效


生命周期:

该服务由系统管理生命周期
(1)onServiceConnected()
当用户开启辅助功能会回调该函数

(2)onAccessibilityEvent()
当收到通过配置过滤的相关事件

(3)onInterrupt()
服务断开时回调

断开服务:
Android7.0以上调用 disableSelf() 或者 用户关闭了Accessibility Service


实例:点击系统弹框: 接受/拒绝 基本使用流程:

1 创建MyAccessibilityService类 继承AccessibilityService 并在Manifest中注册

<application>
  <service android:name=".MyAccessibilityService"
      android:label="@string/accessibility_label"
      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_config" />
  </service>
</application>

除了 android:name 写自己的类名 其他照着写 不能漏了
android:label 无障碍界面你开启的服务的名称 如设置为应用名
accessibility_service_config 创建res/xml/accessibility_service_config文件 通过静态方式进行服务的配置


2 accessibility_service_config.xml

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/accessibility_description"
     android:packageNames="com.x.xx.xxx"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFlags="flagDefault"
    android:accessibilityFeedbackType="feedbackSpoken"
    android:notificationTimeout="100"
    android:canRetrieveWindowContent="true"

android:description无障碍界面你设置的服务的描述字段
android:packageNames 要过滤的包名
若有多个包名 则通过,分隔
如果不过滤任何package则不设置
或者动态方式:
accessibilityServiceInfo.packageNames = null;

android:accessibilityEventTypes 要响应的事件类型 如
typeAllMask 所有类型
typeWindowStateChanged 窗口变化
typeWindowContentChanged 窗口内容变化 等等
android:accessibilityFlags 辅助功能额外信息
flagReportViewIds 可以携带view 的id信息
一般设置为flagDefault
android:accessibilityFeedbackType 操作相应事件后的反馈
feedbackSpoken 发声
feedbackVisual 视觉反馈
feedbackHaptic 震动反馈
android:notificationTimeout 两个相同事件发送的间隔(ms)
android:canRetrieveWindowContent=“true” 允许辅助功能获得窗口的节点信息 要设置为true

可以通过下面的方式进行动态设置


3 MyAccessibilityService类

(1)onServiceConnected() //当用户开启辅助功能会回调该函数
在该函数中可以通过AccessibilityServiceInfo类 动态的方式去 设置配置服务操作
如 eventTypes packageNames等 但不能动态设置canRetrieveWindowContent(不能生效)
得通过上方静态设置

(2)onAccessibilityEvent()
当收到通过配置过滤的相关事件
通过event.getEventType() 获取相应的事件类型
即 android:accessibilityEventTypes 的类型
然后进行相关操作

(3)onInterrupt()
服务断开时回调

(4) isAccessibilitySettingsOn 判断服务是否开启

private boolean isAccessibilitySettingsOn(String accessibilityServiceName, Context context) {
        int accessibilityEnable = 0;
        String serviceName = context.getPackageName() + "/" + accessibilityServiceName;
        try {
            accessibilityEnable = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, 0);
        } catch (Exception e) {
            Logger.t(TAG).e("get accessibility enable failed, the err:" + e.getMessage());
        }
        if (1 == accessibilityEnable) {
            TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
            String settingValue = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
            if (null != settingValue) {
                mStringColonSplitter.setString(settingValue);
                while (mStringColonSplitter.hasNext()) {
                    String accessibilityService = mStringColonSplitter.next();
                    if (accessibilityService.equalsIgnoreCase(serviceName)) {
                        Logger.t(TAG).d("accessibility service:" + serviceName + " is on.");
                        return true;
                    }
                }
            }
        } else {
            Logger.t(TAG).d("accessibility service disable.");
        }
        return false;
    }

(5) 服务开启
1 无root或者系统签名:
跳转到无障碍界面:

val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
startActivity(intent)

注意:有的系统可能并不是该ACTION 可能会crash

2 应用具有系统签名 自动去打开无障碍服务

先申请权限:

<uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />

因为系统的安全配置文件是在Settings.Secure中 直接去操作该文件即可

private void enableAccessibility(Context context) {
        try {
            Settings.Secure.putString(context.getContentResolver(),
                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                    "com.xxx.xx你的应用包名/com.xx.xx.service.MyAccessibilityService你应用的AccessibilityService服务的完整路径");
            Settings.Secure.putString(context.getContentResolver(),
                    Settings.Secure.ACCESSIBILITY_ENABLED, "1");
            Logger.t(TAG).d("enableAccessibility.");
        } catch (Exception e) {
            Logger.t(TAG).e("Exception:" + e.toString());
        }
    }

(6) onAccessibilityEvent当收取到某个事件后:

AccessibilityNodeInfo nodeInfo = findViewByText("接受", true);
            if (nodeInfo != null) {
                performViewClick(nodeInfo);
            } else {
                Logger.t(TAG).d("nodeInfo is null.");
            }

通过 text去进行查找:

private AccessibilityNodeInfo findViewByText(String text, boolean clickable) {
        AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
        if (accessibilityNodeInfo == null) {
            Logger.t(TAG).d("accessibilityNodeInfo is null");
            return null;
        }

        List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text);
        if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
            for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
                if (nodeInfo != null && (nodeInfo.isClickable() == clickable)) {
                    return nodeInfo;
                }
            }
        }
        return null;
    }

也可以通过Id去查找

点击操作:

private void performViewClick(AccessibilityNodeInfo nodeInfo) {
        if (nodeInfo == null) {
            return;
        }
        Logger.t(TAG).d("performViewClick start.");
        while (nodeInfo != null) {
            if (nodeInfo.isClickable()) {
                nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                Logger.t(TAG).d("has clicked the view");
               nodeInfo.recycle(); //  当点击完后回收
                break;
            }
            nodeInfo = nodeInfo.getParent();
        }
    }

4 关闭服务
Android7.0以上调用 disableSelf() 或者 用户关闭了Accessibility Service

对于root的手机

private void disableAccessibility(Context context) {
        try {
            Settings.Secure.putInt(context.getContentResolver(),
                    Settings.Secure.ACCESSIBILITY_ENABLED, 0);
            Logger.t(TAG).d("Accessibility disabled");
        } catch (Exception e) {
            Logger.t(TAG).e("Exception:" + e.toString());
        }
    }

似乎以上两个方法都关闭不成功


问题:

1 accessibilityNodeInfo is null

2 getRootInActiveWindow return null when the window changed
没有配置
android:canRetrieveWindowContent=“true” 允许辅助功能获得窗口的节点信息 要设置为true

覆盖安装apk 就不回调onConnected??