可见即可说


1.android 辅助服务

参考:https://blog.csdn.net/abc6368765/article/details/148281328

介绍

什么是无障碍服务

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

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

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

无需修改被测应用代码:无障碍服务可以直接监控和操作界面,无需对被测应用进行任何修改。所以无障碍服务非常适合做黑盒测试。

支持跨应用操作:无障碍服务可以监控和操作多个应用的界面,适合测试跨应用场景(如从 A 应用跳转到 B 应用)。

强大的界面分析能力:通过 AccessibilityNodeInfo,可以获取界面的层级结构和控件属性,便于分析和验证界面布局。

自动化测试的灵活性:结合无障碍服务,可以编写灵活的自动化测试脚本,模拟用户操作并验证应用行为。

兼容性强:无障碍服务基于 Android 系统框架,兼容大多数 Android 设备和版本。

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

简单案例

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

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" />

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

android:accessibilityEventTypes :指定无障碍服务需要接收的事件类型。typeAllMask表示接收所有类型的无障碍事件(例如点击、焦点变化、文本变化等)。其他可选值:

typeViewClicked:视图点击事件。

typeViewFocused:视图焦点变化事件。

typeWindowContentChanged:窗口内容变化事件。

其他事件可以参考 API 文档。

android:accessibilityFeedbackType: 指定无障碍服务的反馈类型。feedbackGeneric表示提供通用的反馈(例如语音、震动等)。其他可选值:

feedbackSpoken:语音反馈。

feedbackHaptic:震动反馈。

feedbackAudible:声音反馈。

其他事件可以参考 API 文档。

android:accessibilityFlags: 设置无障碍服务的标志位,用于控制服务的行为。flagDefault表示使用默认标志。其他可选值:

flagRequestTouchExplorationMode:请求触摸探索模式。

flagRequestFilterKeyEvents:请求过滤按键事件。

flagReportViewIds:报告视图的 ID。

android:canPerformGestures: 指定无障碍服务是否能够执行手势操作。true表示服务可以执行手势(例如滑动、点击等)。

android:canRetrieveWindowContent: 指定无障碍服务是否能够检索窗口内容。true表示服务可以获取窗口中的内容(例如文本、视图层次结构等)。

android:description:为无障碍服务提供描述信息,通常显示在系统的无障碍设置界面中。

android:notificationTimeout:设置无障碍事件的通知超时时间(以毫秒为单位)。100表示事件通知的超时时间为 100 毫秒。

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

需要在系统设置中启用应用的无障碍服务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

1

2

获取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 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: []