介绍

什么是无障碍服务

Android 的无障碍辅助功能(Accessibility)是一套专为残障用户或特殊场景设计的核心技术框架,旨在让所有用户都能便捷地操作设备。其功能覆盖视觉、听觉、运动能力和认知障碍支持,同时为开发者提供标准化 API 以实现应用适配。

在 Android 测试中,无障碍服务不仅可以用于辅助功能开发,还可以用于自动化测试、界面分析和监控。通过无障碍服务,开发者可以模拟用户操作、获取界面信息,甚至实现跨应用的自动化测试。

无障碍服务在 Android 测试中的重要性

  • 无需修改被测应用代码:无障碍服务可以直接监控和操作界面,无需对被测应用进行任何修改。所以无障碍服务非常适合做黑盒测试。
  • 支持跨应用操作:无障碍服务可以监控和操作多个应用的界面,适合测试跨应用场景(如从 A 应用跳转到 B 应用)。
  • 强大的界面分析能力:通过 AccessibilityNodeInfo,可以获取界面的层级结构和控件属性,便于分析和验证界面布局。
  • 自动化测试的灵活性:结合无障碍服务,可以编写灵活的自动化测试脚本,模拟用户操作并验证应用行为。
  • 兼容性强:无障碍服务基于 Android 系统框架,兼容大多数 Android 设备和版本。

目前,一些主流的 Android 自动化框架,或多或少都使用到了无障碍服务的功能。比如 Google 官方提供的 Android 测试框架 UI Automator 就是基于无障碍服务实现的。再比如,Google 官方提供的 Android 白盒测试框架,主要依赖于视图层级(View Hierarchy),但在某些场景下会使用无障碍服务来增强功能。

简单案例

下面我们将会编写一个简单的案例,具体认识一下 Android 无障碍服务 (AccessibilityService) 。此案例主要用于获取当前屏幕上的所有文本信息。

步骤 1:实现无障碍服务类

public class ClickAccessibilityService extends AccessibilityService {

    private static final String TAG = BASE_TAG + ".ClickAccessibilityService";
    private static ClickAccessibilityService mService = null;

    public static boolean isConnected() {
        return mService != null;
    }

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        Log.i(TAG, "onServiceConnected");
        mService = this;
    }

    @Override
    public void onInterrupt() {
        Log.i(TAG, "onInterrupt");
        mService = null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy");
        mService = null;
    }


    @Override
    public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        // 打印无障碍服务的事件类型
        int eventType = accessibilityEvent.getEventType();
        Log.i(TAG, "onAccessibilityEvent: " + accessibilityEvent.getText());
        Log.i(TAG, "onAccessibilityEvent: " + eventType);
    }

}

步骤 2:创建无障碍服务配置文件(res/xml/accessibility_service_config.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:canPerformGestures="true"
    android:canRetrieveWindowContent="true"
    android:description="@string/app_name"
    android:notificationTimeout="100"
    android:packageNames="com.android.systemui" />

上述配置定义了服务的行为和功能,确保服务能够接收特定类型的事件、提供反馈、检索窗口内容等。

  1. android:accessibilityEventTypes :指定无障碍服务需要接收的事件类型。typeAllMask表示接收所有类型的无障碍事件(例如点击、焦点变化、文本变化等)。其他可选值:
  • typeViewClicked:视图点击事件。
  • typeViewFocused:视图焦点变化事件。
  • typeWindowContentChanged:窗口内容变化事件。
  • 其他事件可以参考 API 文档。
  1. android:accessibilityFeedbackType: 指定无障碍服务的反馈类型。feedbackGeneric表示提供通用的反馈(例如语音、震动等)。其他可选值:
  • feedbackSpoken:语音反馈。
  • feedbackHaptic:震动反馈。
  • feedbackAudible:声音反馈。
  • 其他事件可以参考 API 文档。
  1. android:accessibilityFlags: 设置无障碍服务的标志位,用于控制服务的行为。flagDefault表示使用默认标志。其他可选值:
  • flagRequestTouchExplorationMode:请求触摸探索模式。
  • flagRequestFilterKeyEvents:请求过滤按键事件。
  • flagReportViewIds:报告视图的 ID。
  1. android:canPerformGestures: 指定无障碍服务是否能够执行手势操作。true表示服务可以执行手势(例如滑动、点击等)。
  2. android:canRetrieveWindowContent: 指定无障碍服务是否能够检索窗口内容。true表示服务可以获取窗口中的内容(例如文本、视图层次结构等)。
  3. android:description:为无障碍服务提供描述信息,通常显示在系统的无障碍设置界面中。
  4. android:notificationTimeout:设置无障碍事件的通知超时时间(以毫秒为单位)。100表示事件通知的超时时间为 100 毫秒。
  5. android:packageNames:指定无障碍服务只监听特定应用程序包的事件。com.android.systemui表示服务只监听系统 UI 的事件。可以指定多个包名,用逗号分隔。

步骤 3:添加无障碍服务声明(AndroidManifest.xml )

        <service
            android:name=".ClickAccessibilityService"
            android:enabled="true"
            android:exported="true"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
            android:label="@string/accessibility_tip">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessibility_config" />
        </service>

配置信息介绍

在 AndroidManifest.xml 中,无障碍服务(accessibility service)的声明方式与其他服务类似,但它必须完成以下两件事:

  • 指定它处理 “android.accessibilityservice.AccessibilityService” 这个 Intent。
  • 请求 Manifest.permission.BIND_ACCESSIBILITY_SERVICE 权限,以确保只有系统能够绑定到该服务。
    如果缺少其中任何一项,系统将忽略该无障碍服务。

配置无障碍服务只接受特定类型事件

无障碍服务可以根据需求灵活配置,例如只接收特定类型的事件、监听指定的应用、限制事件频率、获取窗口内容,或者设置自定义配置界面等。配置无障碍服务有两种方式:

1、第一种方式是在清单文件中声明服务时,通过添加元数据(meta-data)进行配置。步骤 2 配置方式使用的就是这种配置。
2、另一种配置方式是通过代码动态配置无障碍服务。开发者可以在服务运行时,使用AccessibilityServiceInfo类来设置服务的具体行为,例如事件类型、反馈类型、通知超时等。这种方式更加灵活,允许根据应用的实际需求动态调整配置。

启用无障碍服务ClickAccessibilityService

1、需要在系统设置中启用应用的无障碍服务ClickAccessibilityService

启动无障碍服务

2 通过adb开启

//开启无障碍开关
adb shell settings put secure accessibility_enabled 1
//开启我们的无障碍服务
adb shell settings put secure enabled_accessibility_services com.example.empty/com.example.empty.ClickAccessibilityService

运行效果

开启ClickAccessibilityService无障碍服务

当我们在设置页面开启无障碍服务的一瞬间,会有如下日志打印:

ActivityManager         system_server                        I  Start proc 5304:com.example.empty/u0a152 for service {com.example.empty/com.example.empty.ClickAccessibilityService}
EmptyApp.C...ityService com.example.empty                    I  onServiceConnected

获取SystemUI页面事件

当我们下划划出下拉栏之后。
下拉栏
会有如下日志打印:

2025-06-18 20:33:52.139  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: []
2025-06-18 20:33:52.139  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: 2048
2025-06-18 20:33:52.143  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: []
2025-06-18 20:33:52.143  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: 2048
2025-06-18 20:33:52.157  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: [通知栏。, 互联网, T-Mobile,3G, 蓝牙, 已开启, 手电筒, 相机正在使用中, 勿扰, 已关闭, 没有通知, 12:33, 6月18日周三, T-Mobile, 3G, 100%]
2025-06-18 20:33:52.157  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: 32
2025-06-18 20:33:52.305  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: []
2025-06-18 20:33:52.305  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: 2048
2025-06-18 20:33:52.328  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: []
2025-06-18 20:33:52.328  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: 4096
2025-06-18 20:33:52.365  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: []
2025-06-18 20:33:52.365  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: 2048
2025-06-18 20:33:52.366  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: [通知栏。, 互联网, T-Mobile,3G, 蓝牙, 已开启, 手电筒, 相机正在使用中, 勿扰, 已关闭, 没有通知, 12:33, 6月18日周三, T-Mobile, 3G, 100%]
2025-06-18 20:33:52.366  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: 32
2025-06-18 20:33:52.684  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: []
2025-06-18 20:33:52.684  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: 2048
2025-06-18 20:33:52.800  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: []
2025-06-18 20:33:52.800  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: 2048
2025-06-18 20:33:52.922  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: []
2025-06-18 20:33:52.922  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: 2048
2025-06-18 20:33:53.051  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: []
2025-06-18 20:33:53.051  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: 2048
2025-06-18 20:33:53.153  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: []
2025-06-18 20:33:53.153  5304-5304  EmptyApp.C...ityService com.example.empty                    D  onAccessibilityEvent: 2048

模拟点击

修改代码,模拟打开蓝牙

public class ClickAccessibilityService extends AccessibilityService {
    @Override
    public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        // 打印无障碍服务的事件类型
        int eventType = accessibilityEvent.getEventType();
        Log.i(TAG, "onAccessibilityEvent: " + accessibilityEvent.getText());
        Log.i(TAG, "onAccessibilityEvent: " + eventType);

        if (accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            Log.i(TAG, "Windows state has been changed.");

            AccessibilityNodeInfo rootNode = getRootInActiveWindow();
            if (rootNode == null) {
                Log.i(TAG, "Root node is null.");
                return;
            }
            List<AccessibilityNodeInfo> nodes = rootNode.findAccessibilityNodeInfosByText("蓝牙");
            if (nodes.isEmpty()) {
                Log.i(TAG, "No Bluetooth node found.");
                return;
            }

            for (AccessibilityNodeInfo node : nodes) {
                if (node.getClassName().equals("android.widget.Switch")) {
                    if (node.performAction(AccessibilityNodeInfo.ACTION_CLICK)) {
                        Log.i(TAG, "Bluetooth switch clicked.");
                    } else {
                        Log.i(TAG, "Failed to click Bluetooth switch.");
                    }
                    break;
                }
            }

        }
    }
}

对ClickAccessibilityService服务进行上述修改,然后重新安装应用,并在设置页面开启无障碍服务。

运行效果

当我们再次下划划出下拉栏之后。
请添加图片描述
可以发现蓝牙的状态开关被点了一下,状态进行了切换,并会产生如下日志打印。

2025-06-18 20:41:56.593  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: [通知栏。, 互联网, T-Mobile,3G, 蓝牙, 已开启, 手电筒, 相机正在使用中, 勿扰, 已关闭, 没有通知, 12:41, 6月18日周三, T-Mobile, 3G, 100%]
2025-06-18 20:41:56.593  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: 32
2025-06-18 20:41:56.593  5695-5695  EmptyApp.C...ityService com.example.empty                    I  Windows state has been changed.
2025-06-18 20:41:56.692  5695-5695  EmptyApp.C...ityService com.example.empty                    I  Bluetooth switch clicked.
2025-06-18 20:41:56.704  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: []
2025-06-18 20:41:56.705  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: 2048
2025-06-18 20:41:56.760  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: []
2025-06-18 20:41:56.760  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: 2048
2025-06-18 20:41:56.765  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: []
2025-06-18 20:41:56.765  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: 4096
2025-06-18 20:41:56.766  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: [通知栏。, 互联网, T-Mobile,3G, 蓝牙, 已开启, 手电筒, 相机正在使用中, 勿扰, 已关闭, 没有通知, 12:41, 6月18日周三, T-Mobile, 3G, 100%]
2025-06-18 20:41:56.766  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: 32
2025-06-18 20:41:56.767  5695-5695  EmptyApp.C...ityService com.example.empty                    I  Windows state has been changed.
2025-06-18 20:41:56.836  5695-5695  EmptyApp.C...ityService com.example.empty                    I  Bluetooth switch clicked.
2025-06-18 20:41:56.840  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: []
2025-06-18 20:41:56.840  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: 2048
2025-06-18 20:41:56.855  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: [蓝牙, 已开启]
2025-06-18 20:41:56.855  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: 1
2025-06-18 20:41:56.916  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: []
2025-06-18 20:41:56.916  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: 2048
2025-06-18 20:41:56.940  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: []
2025-06-18 20:41:56.941  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: 2048
2025-06-18 20:41:56.941  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: [蓝牙, 已开启]
2025-06-18 20:41:56.941  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: 1
2025-06-18 20:41:57.052  5695-5695  EmptyApp.C...ityService com.example.empty                    I  onAccessibilityEvent: []

更多推荐