1. 简介
无障碍服务是 Android 框架的一项功能,旨在代表安装在 Android 设备上的应用向用户提供替代导航反馈。无障碍服务可以代表应用与用户进行通信,例如在用户将光标悬停在屏幕的重要区域时,将文字转换为语音或提供触感反馈。此 Codelab 将介绍如何创建非常简单的无障碍服务。
什么是无障碍服务?
无障碍服务可帮助残障用户使用 Android 设备和应用。它是一项长时间运行的特权服务,可帮助用户处理屏幕上的信息,并让用户与设备进行有意义的互动。
常见无障碍服务示例
- 开关控制:可让有行动障碍的 Android 用户使用一个或多个开关与设备互动。
- 语音操控(Beta 版):可让行动不便的 Android 用户通过语音指令控制设备。
- Talkback:一种屏幕阅读器,通常供视力损害或盲人用户使用。
构建无障碍服务
虽然 Google 为 Android 用户提供了开关控制、语音操控和 TalkBack 等服务,但这些服务无法满足所有残疾用户的需求。由于许多残障人士有独特的需求,因此 Android 用于创建无障碍服务的 API 是开放的,开发者可以自由创建无障碍服务并通过 Play 商店分发这些服务。
构建内容
在此 Codelab 中,您将开发一个使用无障碍 API 执行一些实用操作的简单服务。如果您能编写基本的 Android 应用,就可以开发类似的服务。
无障碍功能 API 非常强大:您将构建的服务的代码仅包含在四个文件中,并且使用了大约 200 行代码!
最终用户
您将为具有以下特征的假设用户构建服务:
- 用户难以触及设备侧面的按钮。
- 用户难以滚动或滑动。
服务详情
您的服务会在屏幕上叠加一个全局操作栏。用户可以点按此栏中的按钮来执行以下操作:
- 在不触碰手机侧面实际电源按钮的情况下关闭设备。
- 无需触摸手机侧面的音量按钮即可调节音量。
- 执行滚动操作,但不实际滚动。
- 执行滑动操作,而无需使用滑动手势。
所需条件
本 Codelab 假定您将使用以下内容:
- 一台运行 Android Studio 的计算机。
- 用于执行简单 shell 命令的终端。
- 一部运行 Android 7.0(Nougat)的设备,该设备已连接到您将用于开发的计算机。
让我们开始吧!
2. 准备工作
使用终端创建一个您将要使用的目录。切换到此目录。
下载代码
您可以克隆包含此 Codelab 代码的代码库:
git clone https://github.com/android/codelab-android-accessibility.git
该代码库包含多个 Android Studio 项目。使用 Android Studio 打开 GlobalActionBarService。
点击 Studio 图标启动 Android Studio:

选择 Import project (Eclipse ADT, Gradle, etc.) 选项:

前往您克隆源代码的位置,然后选择 GlobalActionBarService。
然后,使用终端切换到根目录。
3. 了解起始代码
探索您打开的项目。
我们已为您创建了无障碍服务的精简框架。您在此 Codelab 中编写的所有代码都仅限于以下四个文件:
- app/src/main/AndroidManifest.xml
- app/src/main/res/layout/action_bar.xml
- app/src/main/res/xml/global_action_bar_service.xml
- app/src/main/java/com/example/android/globalactionbarservice/GlobalActionBarService.java
下面将逐步介绍每个文件的内容。
AndroidManifest.xml
无障碍服务的相关信息在清单中声明:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.globalactionbarservice">
<application>
<service
android:name=".GlobalActionBarService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/global_action_bar_service" />
</service>
</application>
</manifest>
AndroidManifest.xml 中声明了以下三个必需项:
- 绑定到无障碍服务的权限:
<service
...
android:permission = "android.permission.BIND_ACCESSIBILITY_SERVICE">
...
</service>
- AccessibilityService intent:
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
- 包含您要创建的服务的元数据的文件位置:
<meta-data
...
android:resource="@xml/global_action_bar_service" />
</service>
global_action_bar_service.xml
此文件包含服务的元数据。
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canPerformGestures="true"
android:canRetrieveWindowContent="true" />
使用 <accessibility-service> 元素定义了以下元数据:
- 此服务的反馈类型(此 Codelab 使用 feedbackGeneric,这是一个不错的默认值)。
- 服务的无障碍标志(此 Codelab 使用默认标志)。
- 服务所需的功能:
- 为了实现滑动操作,android:canPerformGestures 设置为 true。
- 为了检索窗口内容,android:canRetrieveWindowContent 设置为 true。
GlobalActionBarService.java
无障碍服务的大部分代码都位于 GlobalActionBarService.java 中。最初,该文件包含无障碍服务的绝对最低限度代码:
- 扩展 AccessibilityService 的类。
- 一些必需的被替换方法(在此 Codelab 中留空)。
public class GlobalActionBarService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}
@Override
public void onInterrupt() {
}
}
您将在本 Codelab 期间向此文件添加代码。
action_bar.xml
该服务公开了一个包含四个按钮的界面,而 action_bar.xml 布局文件包含用于显示这些按钮的标记:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</LinearLayout>
此文件目前包含一个空的 LinearLayout。在此 Codelab 中,您将为按钮添加标记。
启动应用
请确保已将设备连接到计算机。按屏幕顶部菜单栏中的绿色 Play 按钮
,这应该会启动您正在处理的应用。
前往设置 > 无障碍功能。设备上已安装 Global Action Bar Service。

点击 Global Action Bar Service 并将其开启。您应该会看到以下权限对话框:

无障碍服务会请求权限来观察用户操作、检索窗口内容并代表用户执行手势!使用第三方无障碍服务时,请务必确保您信任相应来源!
运行该服务不会执行任何操作,因为我们尚未添加任何功能。让我们开始吧。
4. 创建按钮
打开 res/layout 中的 action_bar.xml。在当前为空的 LinearLayout 中添加标记:
<LinearLayout ...>
<Button
android:id="@+id/power"
android:text="@string/power"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/volume_up"
android:text="@string/volume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/scroll"
android:text="@string/scroll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/swipe"
android:text="@string/swipe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
这会创建用户将按下的按钮,以触发设备上的操作。
打开 GlobalActionBarService.java 并添加一个变量来存储操作栏的布局:
public class GlobalActionBarService extends AccessibilityService {
FrameLayout mLayout;
...
}
现在,添加 onServiceStarted() 方法:
public class GlobalActionBarService extends AccessibilityService {
FrameLayout mLayout;
@Override
protected void onServiceConnected() {
// Create an overlay and display the action bar
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
mLayout = new FrameLayout(this);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
lp.format = PixelFormat.TRANSLUCENT;
lp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
lp.gravity = Gravity.TOP;
LayoutInflater inflater = LayoutInflater.from(this);
inflater.inflate(R.layout.action_bar, mLayout);
wm.addView(mLayout, lp);
}
}
该代码会扩充布局,并在屏幕顶部附近添加操作栏。
当服务连接时,系统会运行 onServiceConnected() 方法。此时,无障碍服务已获得正常运行所需的所有权限。您将在此处使用的关键权限是 WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY 权限。此权限可让您直接在屏幕上现有内容之上绘制内容,而无需经历复杂的权限流程。
无障碍服务生命周期
无障碍服务的生命周期完全由系统管理,并遵循既定的服务生命周期。
- 当用户在设备设置中明确开启无障碍服务时,该服务会启动。
- 系统绑定到服务后,会调用 onServiceConnected()。想要执行绑定后设置的服务可以替换此方法。
- 无障碍服务会在以下情况下停止:用户在设备设置中将其关闭,或者该服务调用 disableSelf()。
运行服务
在您使用 Android Studio 启动服务之前,需要确保运行设置已正确配置。
修改您的运行配置(使用顶部菜单中的“运行”,然后前往“修改配置”)。然后,使用下拉菜单将“启动选项”从“默认 activity”更改为“无”。

现在,您应该可以使用 Android Studio 启动服务了。
按屏幕顶部菜单栏中的绿色 Play 按钮
,然后,依次前往设置 > 无障碍功能,然后开启全局操作栏服务。
您应该会看到四个按钮,它们构成了服务界面,叠加在屏幕上显示的内容之上。

现在,您将为这四个按钮添加功能,以便用户可以通过触摸它们来执行有用的操作。
5. 配置电源按钮
将 configurePowerButton() 方法添加到 GlobalActionBarService.java:
private void configurePowerButton() {
Button powerButton = (Button) mLayout.findViewById(R.id.power);
powerButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
performGlobalAction(GLOBAL_ACTION_POWER_DIALOG);
}
});
}
为了访问电源按钮菜单,configurePowerButton() 使用了 AccessibilityService 提供的 performGlobalAction() 方法。您刚刚添加的代码很简单:点击按钮会触发 onClickListener()。这会调用 performGlobalAction(GLOBAL_ACTION_POWER_DIALOG) 并向用户显示电源对话框。
请注意,全局操作不与任何视图相关联。按“返回”按钮、“主屏幕”按钮、“最近使用的应用”按钮是全局操作的其他示例。
现在,将 configurePowerButton() 添加到 onServiceConnected() 方法的末尾:
@Override
protected void onServiceConnected() {
...
configurePowerButton();
}
按屏幕顶部菜单栏中的绿色 Play 按钮
,然后,依次前往设置 > 无障碍功能,然后启动全局操作栏服务。
按电源按钮以显示电源对话框。
6. 配置音量按钮
将 configureVolumeButton() 方法添加到 GlobalActionBarService.java:
private void configureVolumeButton() {
Button volumeUpButton = (Button) mLayout.findViewById(R.id.volume_up);
volumeUpButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI);
}
});
}
configureVolumeButton() 方法添加了一个 onClickListener(),当用户按下音量按钮时,该监听器会被触发。在此监听器内,configureVolumeButton() 使用 AudioManager 调整音频流音量。
请注意,任何人都可以控制音量(您无需是无障碍服务即可执行此操作)。
现在,将 configureVolumeButton() 添加到 onServiceConnected() 方法的末尾:
@Override
protected void onServiceConnected() {
...
configureVolumeButton();
}
按屏幕顶部菜单栏中的绿色 Play 按钮
,然后,前往“设置”>“无障碍功能”,并启动全局操作栏服务。
按音量按钮可调节音量。
假设用户无法触及设备侧面的音量控件,现在可以使用全局操作栏服务来更改(调高)音量。
7. 配置滚动按钮
此部分涉及编写两种方法的代码。第一个方法用于查找可滚动节点,第二个方法用于代表用户执行滚动操作。
将 findScrollableNode 方法添加到 GlobalActionBarService.java:
private AccessibilityNodeInfo findScrollableNode(AccessibilityNodeInfo root) {
Deque<AccessibilityNodeInfo> deque = new ArrayDeque<>();
deque.add(root);
while (!deque.isEmpty()) {
AccessibilityNodeInfo node = deque.removeFirst();
if (node.getActionList().contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD)) {
return node;
}
for (int i = 0; i < node.getChildCount(); i++) {
deque.addLast(node.getChild(i));
}
}
return null;
}
无障碍服务无法访问屏幕上的实际视图。相反,它会看到屏幕上内容的反映,以由 AccessibilityNodeInfo 对象组成的树的形式呈现。这些对象包含有关其所代表的视图的信息(视图的位置、与视图关联的任何文本、为无障碍功能添加的元数据、视图支持的操作等)。findScrollableNode() 方法从根节点开始,对此树执行广度优先遍历。如果找到可滚动的节点(即支持 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD 操作)的节点),则返回该节点,否则返回 null。
现在,将 configureScrollButton() 方法添加到 GlobalActionBarService.java:
private void configureScrollButton() {
Button scrollButton = (Button) mLayout.findViewById(R.id.scroll);
scrollButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AccessibilityNodeInfo scrollable = findScrollableNode(getRootInActiveWindow());
if (scrollable != null) {
scrollable.performAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId());
}
}
});
}
此方法会创建一个 onClickListener(),当点击滚动按钮时,该监听器会触发。它会尝试查找可滚动节点,如果成功找到,则执行滚动操作。
现在,将 configureScrollButton() 添加到 onServiceConnected():
@Override
protected void onServiceConnected() {
...
configureScrollButton();
}
按屏幕顶部菜单栏中的绿色 Play 按钮
,然后,前往“设置”>“无障碍功能”,并启动全局操作栏服务。
按返回按钮,前往设置 > 无障碍功能。无障碍设置 activity 中的项可滚动,触摸“滚动”按钮会执行滚动操作。假设用户无法轻松执行滚动操作,现在可以使用“滚动”按钮滚动浏览商品列表。
8. 配置滑动按钮
将 configureSwipeButton() 方法添加到 GlobalActionBarService.java:
private void configureSwipeButton() {
Button swipeButton = (Button) mLayout.findViewById(R.id.swipe);
swipeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Path swipePath = new Path();
swipePath.moveTo(1000, 1000);
swipePath.lineTo(100, 1000);
GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
gestureBuilder.addStroke(new GestureDescription.StrokeDescription(swipePath, 0, 500));
dispatchGesture(gestureBuilder.build(), null, null);
}
});
}
configureSwipeButton() 方法使用 N 中新增的 API 来代表用户执行手势。该代码使用 GestureDescription 对象指定要执行的手势的路径(此 Codelab 中使用了硬编码的值),然后使用 AccessibilityService 的 dispatchGesture() 方法代表用户调度滑动操作。
现在,将 configureSwipeButton() 添加到 onServiceConnected():
@Override
protected void onServiceConnected() {
...
configureSwipeButton();
}
按屏幕顶部菜单栏中的绿色 Play 按钮
,然后,前往“设置”>“无障碍功能”,并启动全局操作栏服务。
测试滑动功能的最简单方法是打开手机上安装的 Google 地图应用。地图加载完毕后,触摸“滑动”按钮会将屏幕向右滑动。
9. 总结
恭喜!您已构建了一个简单实用的无障碍服务。
您可以通过多种方式扩展此服务。例如:
- 使操作栏可移动(目前它只是位于屏幕顶部)。
- 允许用户调高和调低音量。
- 允许用户向左和向右滑动。
- 添加了对操作栏可响应的其他手势的支持。
此 Codelab 仅涵盖无障碍功能 API 提供的一小部分功能。该 API 还涵盖以下内容(部分列表):
- 支持多个窗口。
- 支持 AccessibilityEvent。当界面发生变化时,系统会使用 AccessibilityEvent 对象将这些变化通知给无障碍服务。然后,服务可以根据界面变化做出适当的响应。
- 能够控制放大倍数。
本 Codelab 将帮助您开始编写无障碍服务。如果您知道有用户存在特定的无障碍问题,并且您想解决这些问题,现在可以构建一项服务来帮助该用户。