TalkBack 原始碼分析之TalkBackService

語言: CN / TW / HK

「這是我參與11月更文挑戰的第21天,活動詳情檢視:2021最後一次更文挑戰

TalkBack 原始碼分析之TalkBackService

該篇文章比較小眾,無障礙服務對大部分開發者來說是接觸不到的,但如果你剛好需要,或者想要了解它,就接著往下看,這篇文章是對TalkBack的原始碼的初步講解。

TalkBack 基本操作

為了防止大家開啟TalkBack之後無法關閉,請先看下它的基本操作。 前面說過,當TalkBack功能開啟之後,手機的操作習慣會改變。 滑動操作: 必須雙指滑動 點選:雙擊 長按: 選中元件後,雙擊後按住 點按兩次以啟用某項內容,雙指滑動以瀏覽螢幕。 在開啟TalkBack的時候,在主頁也會看到操作提示,在TalkBack設定->教程和幫助中會有詳細的解釋。

1.jpg

TalkBack專案入口

TalkBack的專案有多個module,我們需要找到主入口,我們知道實現無障礙服務需要在xml中去配置無障礙服務的配置資訊,我們很輕鬆的在xml中找到了accessibilityservice,然後在AndroidManifest.xml中去找使用它的地方。

```

<service android:icon="@drawable/icon" android:name="com.google.android.marvin.talkback.TalkBackService"

```

所以我們斷定talkback.TalkBackServic就是專案的主要入口。

TalkBackService

找了專案的入口,就跟著入口服務去分析。 無障礙服務有幾個關鍵的回撥方法。
onCreate - 服務建立成功時,和普通Service的onCreate一樣。
onServiceConnected - 服務開啟成功,在該方法中適合做一些初始化操作
onAccessibilityEvent - 事件處理方法的回撥,主要的操作的需要在該方法執行,所有的操作都會觸發事件。
onConfigurationChanged - 配置發生變化時,比如探索模式的開啟和關閉
getRootInActiveWindow - 獲取螢幕的所有元件

  1. onCreate

服務建立成功。

```

@Override

public void onCreate() {

super.onCreate();

this.setTheme(R.style.BaseTheme);

instance = this; setServiceState(ServiceStateListener.SERVICE_STATE_INACTIVE);

systemUeh = Thread.getDefaultUncaughtExceptionHandler();

Thread.setDefaultUncaughtExceptionHandler(this);

}

```

設定主題,設定狀態,設定一個setDefaultUncaughtExceptionHandler,相當於全域性的catch,用於捕獲程式未捕獲的異常。

  1. onServiceConnected

無障礙服務繫結成功。於OnCreate方法不同的是有些方法必須要在無障礙服務繫結成功之後才可以呼叫。

@Override protected void onServiceConnected() { 效能統計... SP 初始化... 日誌初始化... 一些幫助類... }

  1. onAccessibilityEvent() 該回調方法是很重要的,可以監聽所有的螢幕響應事件。

```

@Override public void onAccessibilityEvent(AccessibilityEvent event) { Performance perf = Performance.getInstance(); EventId eventId = perf.onEventReceived(event); accessibilityEventProcessor.onAccessibilityEvent(event, eventId); perf.onHandlerDone(eventId); } ```

Performance 是事件統計的方法類。

accessibilityEventProcessor.onAccessibilityEvent

``` public void onAccessibilityEvent(AccessibilityEvent event, EventId eventId) { if (testingListener != null) { testingListener.onAccessibilityEvent(event); } if ((dumpEventMask & event.getEventType()) != 0) { LogUtils.v(TAG, DUMP_EVENT_LOG_FORMAT, event); } if (shouldDropRefocusEvent(event)) { return; } if (shouldDropEvent(event)) { return; } if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { lastWindowStateChanged = SystemClock.uptimeMillis(); } if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED || event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED || event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED) { service.setRootDirty(true); }

// We need to save the last focused event so that we can filter out related selected events. if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) { if (lastFocusedEvent != null) { lastFocusedEvent.recycle(); } lastFocusedEvent = AccessibilityEvent.obtain(event); } if (AccessibilityEventUtils.eventMatchesAnyType(event, MASK_DELAYED_EVENT_TYPES)) { handler.postProcessEvent(event, eventId); } else { processEvent(event, eventId); } if (testingListener != null) { testingListener.afterAccessibilityEvent(event); } }

```

這裡設定了一個快取,如果監聽到TYPE_WINDOW_CONTENT_CHANGED,TYPE_WINDOW_STATE_CHANGED,TYPE_WINDOWS_CHANGED 事件,就清楚mRootNode的快取,下次呼叫獲取mRootNode(getRootInActiveWindow()) 時,重新獲取 mRootNode。

lastFocusedEvent 代表只記錄最近的一個聚焦事件。