1. 簡介
無障礙服務是 Android 架構的一項功能,可代表 Android 裝置上安裝的應用程式,向使用者提供替代導覽回饋。無障礙服務可代表應用程式與使用者溝通,例如在使用者將游標懸停在螢幕的重要區域時,將文字轉換為語音或提供觸覺回饋。本程式碼研究室會說明如何建立非常簡單的無障礙服務。
什麼是無障礙服務?
無障礙服務可協助身心障礙使用者操作 Android 裝置和應用程式。這項長期執行的特殊權限服務可協助使用者處理螢幕上的資訊,並與裝置進行有意義的互動。
常見的無障礙服務示例
- 切換控制功能:行動不便的 Android 使用者可以透過一或多個外接切換裝置操作裝置。
- Voice Access (Beta 版):讓行動不便的 Android 使用者透過語音指令控制裝置。
- TalkBack:視障或失明使用者常用的螢幕閱讀器。
建構無障礙服務
Google 為 Android 使用者提供切換控制、語音存取和 TalkBack 等服務,但這些服務無法滿足所有身心障礙使用者的需求。由於許多身心障礙使用者有獨特需求,Android 建立無障礙服務的 API 是開放的,開發人員可以自由建立無障礙服務,並透過 Play 商店發布。
建構項目
在本程式碼研究室中,您將開發簡單的服務,使用無障礙 API 執行幾項實用功能。如果您會編寫基本的 Android 應用程式,就能開發類似的服務。
無障礙 API 功能強大:您要建構的服務程式碼只包含四個檔案,且使用約 200 行程式碼!
使用者
您將為假設使用者建構服務,該使用者具有下列特徵:
- 使用者難以觸及裝置側邊按鈕。
- 使用者難以捲動或滑動畫面。
服務詳細資料
服務會在畫面上疊加顯示全域動作列。使用者可以輕觸這個工具列上的按鈕,執行下列動作:
- 在不按壓手機側邊電源鍵的情況下關閉裝置。
- 不必觸碰手機側邊的音量鍵,即可調整音量。
- 執行捲動動作,但不會實際捲動。
- 不必使用滑動手勢即可滑動。
軟硬體需求
本程式碼研究室假設您使用下列項目:
- 執行 Android Studio 的電腦。
- 用於執行簡單殼層指令的終端機。
- 搭載 Android 7.0 (Nougat) 的裝置,並連上您要用於開發的電腦。
立即開始!
2. 開始設定
使用終端機建立工作目錄。變更為這個目錄。
下載程式碼
您可以複製包含本程式碼研究室程式碼的存放區:
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. 瞭解範例程式碼
瀏覽開啟的專案。
系統已為您建立無障礙服務的基礎架構。在本程式碼研究室中,您編寫的所有程式碼都僅限於下列四個檔案:
- 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-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> 元素定義下列中繼資料:
- 這項服務的意見回饋類型 (本程式碼研究室使用 feedbackGeneric,這是合適的預設值)。
- 服務的無障礙旗標 (本程式碼研究室使用預設旗標)。
- 服務所需的功能:
- 為了執行滑動操作,android:canPerformGestures 會設為 true。
- 如要擷取視窗內容,請將 android:canRetrieveWindowContent 設為 true。
GlobalActionBarService.java
無障礙服務的大部分程式碼都位於 GlobalActionBarService.java 中。一開始,檔案會包含無障礙服務的絕對最低程式碼:
- 擴充 AccessibilityService 的類別。
- 幾個必要覆寫方法 (在本程式碼研究室中留空)。
public class GlobalActionBarService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}
@Override
public void onInterrupt() {
}
}
您會在程式碼研究室中將程式碼新增至這個檔案。
action_bar.xml
這項服務會顯示含有四個按鈕的 UI,而 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。在本程式碼研究室中,您將新增按鈕的標記。
啟動應用程式
確認裝置已連上電腦。按下畫面頂端選單列的綠色「播放」圖示
。這時應該會啟動您正在使用的應用程式。
依序前往「設定」 >「無障礙設定」。裝置上已安裝 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 啟動服務,請先確認執行設定正確無誤。
編輯執行設定 (使用頂端選單中的「Run」,然後前往「Edit Configurations」。然後使用下拉式選單,將「啟動選項」從「預設活動」變更為「無」。

現在您應該可以透過 Android Studio 啟動服務。
按下畫面頂端選單列的綠色「播放」圖示
。接著依序前往「設定」>「無障礙設定」,然後開啟「全域動作列服務」。
畫面上顯示的內容上方,應該會疊加顯示四個按鈕,組成服務 UI。

現在要為這四個按鈕新增功能,讓使用者輕觸按鈕即可執行實用動作。
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() 會使用 performGlobalAction() 方法,此方法由 AccessibilityService 提供。您剛才新增的程式碼很簡單:點選按鈕會觸發 onClickListener()。這會呼叫 performGlobalAction(GLOBAL_ACTION_POWER_DIALOG),並向使用者顯示電源對話方塊。
請注意,全域動作不會與任何檢視畫面繫結。按下「返回」按鈕、「主畫面」按鈕和「最近使用的應用程式」按鈕,也是全域動作的例子。
現在,請在 onServiceConnected() 方法的結尾新增 configurePowerButton():
@Override
protected void onServiceConnected() {
...
configurePowerButton();
}
按下畫面頂端選單列的綠色「播放」圖示
。然後依序前往「設定」>「無障礙功能」,並啟動「全域動作列服務」。
按下電源鍵,顯示電源對話方塊。
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 調整串流音量。
請注意,任何人都可以控制音量 (不需是無障礙服務)。
現在,請在 onServiceConnected() 方法的結尾新增 configureVolumeButton():
@Override
protected void onServiceConnected() {
...
configureVolumeButton();
}
按下畫面頂端選單列的綠色「播放」圖示
。接著依序前往「設定」>「無障礙功能」,然後啟動「全域動作列服務」。
按下音量鍵即可調整音量。
假設使用者無法觸及裝置側邊的音量控制項,現在可以透過 Global Action Bar Service 變更 (調高) 音量。
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 動作) 的節點),就會傳回該節點,否則會傳回空值。
現在,請將 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();
}
按下畫面頂端選單列的綠色「播放」圖示
。接著依序前往「設定」>「無障礙功能」,然後啟動「全域動作列服務」。
按下返回鍵,依序前往「設定」>「無障礙設定」。無障礙設定活動中的項目可捲動,輕觸「捲動」按鈕即可執行捲動動作。假設使用者無法輕鬆執行捲動動作,現在可以使用「捲動」按鈕捲動項目清單。
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 物件指定要執行的手勢路徑 (本程式碼研究室使用硬式編碼值),然後代表使用者透過 AccessibilityService 的 dispatchGesture() 方法調度滑動手勢。
現在將 configureSwipeButton() 新增至 onServiceConnected():
@Override
protected void onServiceConnected() {
...
configureSwipeButton();
}
按下畫面頂端選單列的綠色「播放」圖示
。接著依序前往「設定」>「無障礙功能」,然後啟動「全域動作列服務」。
如要測試滑動功能,最簡單的方法是開啟手機上安裝的「地圖」應用程式。地圖載入後,輕觸「滑動」按鈕即可向右滑動畫面。
9. 摘要
恭喜!您已建構簡單實用的無障礙服務。
您可以透過多種方式擴充這項服務。例如:
- 讓動作列可移動 (目前只會顯示在畫面頂端)。
- 允許使用者調高及調低音量。
- 允許使用者向左和向右滑動。
- 新增動作列可回應的其他手勢。
本程式碼研究室僅涵蓋無障礙 API 提供的一小部分功能。這項 API 也涵蓋下列項目 (部分清單):
- 支援多個視窗。
- 支援 AccessibilityEvent。當 UI 變更時,無障礙服務會使用 AccessibilityEvent 物件收到這些變更的通知。服務隨後可適當回應 UI 變更。
- 可控制放大倍率。
本程式碼研究室會協助您開始編寫無障礙服務。如果您知道有特定無障礙問題的使用者需要協助,現在可以建構服務來幫助他們。