網絡熱傳App鑑定 |「李跳跳」裏用到的無障礙權限是什麼?

語言: CN / TW / HK

theme: channing-cyan

「李跳跳」是什麼?

一款用於跳過開屏廣告的工具類應用。

其核心的原理,是藉助Android系統上提供的無障礙功能,檢測出當前界面上跳過按鈕的位置,並自動幫我們完成點擊的動作。

「無障礙」功能是什麼?

解釋這個概念之前,我們先來看幾張圖:

無障礙電梯

無障礙洗手間

無障礙坡道

以上這些都是在公園、地鐵等的公共場所裏常見的設施,目的是為了保障殘疾人、老年人及其他行動不便者能安全、方便地通行,體現的是對於社會少數羣體的關懷和尊重。

無障礙功能的設計初衷也是如此,同樣是為了照顧到部分視力/聽力受損、有認知障礙或無法完成精細動作的殘障人士,使他們也能夠和其他正常人一樣,無障礙地使用手機應用來完成日常的溝通、學習和工作

除此以外,無障礙功能還可以彌補應用在某些使用場景上的限制,比如在烹飪的同時使用菜譜類的應用,有了無障礙功能的輔助,我們就可以使用語音指令而非觸控手勢來操控應用了。

無障礙功能

Android系統上提供的無障礙功能,實際可分為兩個不同層面上的內容:

設計層面上,它要求應用界面上的元素: - 控件尺寸儘可能大,以方便用户點按

  • 色彩對比度儘可能高,以方便用户查看

低於建議的色彩對比度(左圖)和足夠高的色彩對比度(右圖)

  • 儘可能包含能描述控件實際用途的説明文字

僅使用顏色創建界面元素的示例(左圖),以及使用顏色、形狀和文字創建界面元素的示例(右圖)

操作層面上,它要求應用內提供的服務:

  • 儘可能為用户的每個操作提供及時、有效的反饋

  • 儘可能簡化操作的步驟,或協助用户完成複雜操作

後者主要依賴平台級的「無障礙服務」(AccessibilityService)來實現。

「無障礙服務」能做什麼?

Android系統提供了許多標準的無障礙服務實現,比如TalkBack這項服務,其提供了與屏幕交互時的實時語音反饋,對於盲人和視力低弱人士非常實用。

TalkBack

同時Android也允許開發者創建和分發自己的服務,以增強與用户的互動效果,打造運用範圍更廣的功能。

具體展開講的話,自定義的無障礙服務通常包含對以下幾個事項的處理:

收集信息

這裏收集的主要是與界面交互相關的信息,包括所操作對象的類型、其包含的描述性文字以及其他詳細信息。

我們知道,Android應用所採用的界面佈局,是基於ViewViewGroup對象、以樹狀結構來進行構建的視圖層級。

視圖層次結構的圖示,它定義了一個界面佈局

Android系統正是基於此視圖層級結構編寫的無障礙事件,也即,它不僅會返回用户當前所交互的控件本身,還會返回包含此控件的父視圖,以及此控件所包含的一系列子視圖,這部分額外的信息統稱為控件的上下文信息

上下文信息對於用户理解其所交互控件的含義至關重要。

以一個簡單的複選框為例,對於一個有視力障礙的用户來説,如果無障礙服務僅僅反饋“是否選中”,而未能説明“選中的是什麼內容”,用户就會感到很困惑。

有了完整的上下文信息,無障礙服務就能夠為用户提供更加實用和有效的反饋。

對事件做出響應

這部分比較簡單,通常就是確定無障礙事件的類型,並從觸發此事件的視圖節點中提取有用的文字描述,以文字轉語音或振動的形式反饋給用户。

為用户執行操作

無障礙服務可以代替用户執行的操作,經過數個系統版本的迭代擴展,目前已經支持多達數十種,包括但不限於點擊、長按、複製、粘貼、滾動、翻頁等。

可以説,基本上,只要是Android平台上的標準UI控件(如TextView、RecyclerView、ViewPager等)內可支持的操作,都有對應的無障礙操作類型提供。

並且,這類操作不僅可以在應用內進行,還可以在系統全局進行,比如回到主屏幕、按“返回”按鈕,以及打開最近使用的應用列表等,因此可以極大地簡化用户與應用、系統之間的互動。

所以,説了這麼多,「李跳跳」的「跳過開屏廣告」功能到底是怎麼實現的呢?

跳過開屏廣告是怎麼實現的?

其實,如果你能看到這裏,此刻你的腦海裏應該就已經有了大致的思路,下面,就讓我們用代碼來實際還原一下:

創建無障礙服務

首先我們要做的,就是創建一個擴展自AccessibilityService的類。

``` class MyAccessibilityService : AccessibilityService() { ... }

```

由於無障礙服務本質上就是一個服務(Service),因此必須在AndroidManife應用清單文件中包含特定的聲明。

在service元素的聲明中,我們還必須添加一個intent過濾器,告訴系統這是一個無障礙服務。

此外,我們還需要添加 BIND_ACCESSIBILITY_SERVICE 權限,以確保只有系統才可以綁定到它。

<application> <service android:name=".MyAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" android:label="@string/accessibility_service_label"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> </service> </application>

配置無障礙服務

配置的目的是為了告訴系統,我們的無障礙服務運行的方式和時機、響應的事件類型、是針對特定的應用還是所有的應用以及採用的反饋類型等。

配置的方式有代碼形式和XML文件形式兩種,這裏我們主要介紹後者。

首先,我們需要在res/xml目錄下創建一個名為serviceconfig(命名可隨意)的XML文件,其中的每一個鍵值對即表示一個配置選項,具體每一個配置選項的含義我們留到後面再介紹:

```

```

隨後,為了確保引用到該文件,我們需要在上面的服務聲明中添加一個指向該XML文件的標記:

<service android:name=".MyAccessibilityService"> ... <meta-data android:name="android.accessibilityservice" android:resource="@xml/serviceconfig" /> </service>

註冊無障礙事件

這一步的作用就是確立我們的無障礙服務要處理的事件來自哪個應用的哪些事件類型,與此關聯的幾個配置選項就是:

  • android:packageNames(軟件包名稱):指定可接收的無障礙事件的應用來源。如果有多個應用需要指定,可用逗號進行分隔。而如果未指定,則默認接收來自所有應用的無障礙事件。

  • android:accessibilityEventTypes(事件類型):指定可接收的無障礙事件的事件類型。例如,accessibilityEventTypes="typeViewClicked|typeViewFocused"就表示可接收點擊和獲取焦點這兩種事件類型,多個事件類型之間使用|進行分隔,typeAllMask則表示接收所有類型的事件。

此外,為了保證能獲取無障礙事件來源控件的視圖層次結構(即獲取其父視圖和子視圖),我們還需添加canRetrieveWindowContent屬性,並將其設為true,以請求相應的訪問權限。

申請無障礙服務權限

只有當系統綁定到我們定義的無障礙服務,我們的服務才能正常運行起來,為此,我們需要跳轉到系統的無障礙設置界面,開啟我們安裝的無障礙服務,代碼如下:

startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))

以華為P30 pro為例,以上代碼所跳轉到的界面,對應的操作路徑是「設置-輔助功能-無障礙」,隨後,我們就可以從已安裝的服務裏找到我們自己的服務,然後點擊開啟按鈕,我們的服務就會運行起來。

設置-輔助功能-無障礙.png

重載無障礙服務方法

除了常規的Service類方法外,無障礙服務還額外提供了2個必須重載的方法,分別是:

  • onAccessibilityEvent():當系統檢測到與我們前面的配置選項相匹配的事件時,就會回調此方法,事件將以AccessibilityEvent類對象的形式提供。隨後我們就可以解析該對象,並執行相應的處理。這個方法可能會在服務的整個生命週期內被調用多次。

  • onInterrupt():當系統要中斷我們的服務正在提供的反饋時(通常是在控件焦點轉移時),就會回調此方法。

獲取事件詳細信息

開頭我們就講了,「李跳跳」跳過開屏廣告實際上就2步實現:

  1. 檢測「跳過」按鈕的位置
  2. 自動完成「點擊」的動作

關於步驟1,我們要做的事情就是,在接收到無障礙事件後,使用getSource()從事件中檢索出AccessibilityNodeInfo對象, 通過AccessibilityNodeInfo對象,我們可以瀏覽當前界面的視圖層級結構,找到包含“跳過”字樣文本的事件來源節點。

至於步驟2就很簡單了,直接調用performAction(ACTION_CLICK)為用户執行點擊操作即可。

完整代碼如下:

``` class MyAccessibilityService : AccessibilityService() {

override fun onAccessibilityEvent(event: AccessibilityEvent) {
    val source = event.source ?: return
    for (i in 0 until source.childCount) {
        if (source.getChild(i)?.text?.contains("跳過") == true) {
            source.getChild(i).performAction(ACTION_CLICK)
        }
    }
    source.recycle()
}

override fun onInterrupt() {
}

} ```

效果演示

可以看到,對比前一個未啟用跳過開屏廣告功能的網易雲版本,後一個版本會在開屏廣告顯示的瞬間,就幫我們自動點擊了跳過按鈕,以達到跳過開屏廣告的效果。

還想説點什麼

好了,到這裏我們就完成了一個“簡易版”的李跳跳了,之所以説是“簡易版”,是因為現在的李跳跳早已不限於跳過廣告這一主要功能了,但大體上還是基於無障礙功能進行擴展的。

這也讓我不得不慨歎,能力是有限的,但創意可以是無限的。Android開發團隊或許也從未想到,無障礙功能居然還可以用來做這麼多事情。實踐之後,我也不得不對李跳跳開發者的創意拍手叫絕。

但另一方面,我也衍生出一種擔憂,至於原因,顯而易見,無障礙功能在解鎖了相應的權限之後,其所能掌握的對於手機的控制權是相當可怕的

能讀取屏幕內容,就可能竊取隱私;能操控用户行為,就可能違法犯罪。何況服務本身的特點就是在後台長時間運作,而不需要提供界面的。如果還允許這類應用在後台持續保活的話,真不敢想象它會在你意想不到的時候做出什麼出格的事情。

當然我相信「李跳跳」的開發者的初心是純粹的,畢竟多省下幾秒看無聊廣告的時間,就多幾秒可以去做更有意義的事情,我只是表達對這種過度權力本身的擔憂,畢竟就像羅翔老師説的:

同作為開發者,我佩服「李跳跳」的創意,但我敬畏這種權力。