網路熱傳App鑑定 |「李跳跳」裡用到的無障礙許可權是什麼?
theme: channing-cyan
「李跳跳」是什麼?
一款用於跳過開屏廣告的工具類應用。
其核心的原理,是藉助Android系統上提供的無障礙功能,檢測出當前介面上跳過按鈕的位置,並自動幫我們完成點選的動作。
「無障礙」功能是什麼?
解釋這個概念之前,我們先來看幾張圖:
以上這些都是在公園、地鐵等的公共場所裡常見的設施,目的是為了保障殘疾人、老年人及其他行動不便者能安全、方便地通行,體現的是對於社會少數群體的關懷和尊重。
無障礙功能的設計初衷也是如此,同樣是為了照顧到部分視力/聽力受損、有認知障礙或無法完成精細動作的殘障人士,使他們也能夠和其他正常人一樣,無障礙地使用手機應用來完成日常的溝通、學習和工作。
除此以外,無障礙功能還可以彌補應用在某些使用場景上的限制,比如在烹飪的同時使用菜譜類的應用,有了無障礙功能的輔助,我們就可以使用語音指令而非觸控手勢來操控應用了。
Android系統上提供的無障礙功能,實際可分為兩個不同層面上的內容:
設計層面上,它要求應用介面上的元素: - 控制元件尺寸儘可能大,以方便使用者點按;
- 色彩對比度儘可能高,以方便使用者檢視;
- 儘可能包含能描述控制元件實際用途的說明文字。
操作層面上,它要求應用內提供的服務:
-
儘可能為使用者的每個操作提供及時、有效的反饋
-
儘可能簡化操作的步驟,或協助使用者完成複雜操作
後者主要依賴平臺級的「無障礙服務」(AccessibilityService
)來實現。
「無障礙服務」能做什麼?
Android系統提供了許多標準的無障礙服務實現,比如TalkBack這項服務,其提供了與螢幕互動時的實時語音反饋,對於盲人和視力低弱人士非常實用。
同時Android也允許開發者建立和分發自己的服務,以增強與使用者的互動效果,打造運用範圍更廣的功能。
具體展開講的話,自定義的無障礙服務通常包含對以下幾個事項的處理:
收集資訊
這裡收集的主要是與介面互動相關的資訊,包括所操作物件的型別、其包含的描述性文字以及其他詳細資訊。
我們知道,Android應用所採用的介面佈局,是基於View
和ViewGroup
物件、以樹狀結構來進行構建的檢視層級。
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為例,以上程式碼所跳轉到的介面,對應的操作路徑是「設定-輔助功能-無障礙」,隨後,我們就可以從已安裝的服務裡找到我們自己的服務,然後點選開啟按鈕,我們的服務就會執行起來。
過載無障礙服務方法
除了常規的Service類方法外,無障礙服務還額外提供了2個必須過載的方法,分別是:
-
onAccessibilityEvent()
:當系統檢測到與我們前面的配置選項相匹配的事件時,就會回撥此方法,事件將以AccessibilityEvent
類物件的形式提供。隨後我們就可以解析該物件,並執行相應的處理。這個方法可能會在服務的整個生命週期內被呼叫多次。 -
onInterrupt()
:當系統要中斷我們的服務正在提供的反饋時(通常是在控制元件焦點轉移時),就會回撥此方法。
獲取事件詳細資訊
開頭我們就講了,「李跳跳」跳過開屏廣告實際上就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開發團隊或許也從未想到,無障礙功能居然還可以用來做這麼多事情。實踐之後,我也不得不對李跳跳開發者的創意拍手叫絕。
但另一方面,我也衍生出一種擔憂,至於原因,顯而易見,無障礙功能在解鎖了相應的許可權之後,其所能掌握的對於手機的控制權是相當可怕的。
能讀取螢幕內容,就可能竊取隱私;能操控使用者行為,就可能違法犯罪。何況服務本身的特點就是在後臺長時間運作,而不需要提供介面的。如果還允許這類應用在後臺持續保活的話,真不敢想象它會在你意想不到的時候做出什麼出格的事情。
當然我相信「李跳跳」的開發者的初心是純粹的,畢竟多省下幾秒看無聊廣告的時間,就多幾秒可以去做更有意義的事情,我只是表達對這種過度權力本身的擔憂,畢竟就像羅翔老師說的:
同作為開發者,我佩服「李跳跳」的創意,但我敬畏這種權力。