最近因為項目的需要(在某個界面需要自動的點擊某個按鈕),所以接觸了關于輔助功能的開發。剛開始的時候根本沒有想到可以用輔助功能來幫助實現這個需求,一直在看關于屏幕監聽和模擬點擊的一些資料。后來通過有經驗的同事的介紹,發現可以使用輔助功能來實現我這一需求。話不多說,開始介紹這個輔助功能(AccessibilityService):
1. 創建自己的輔助功能類:
編寫自己的服務類,需要繼承AccessibilityService類。
對于一些需要重載的方法的介紹:
onServiceConnected();
系統會在成功連接上你的服務的時候調用這個方法,在這個方法里你可以做一下初始化工作,例如設備的聲音震動管理,也可以調用setServiceInfo()進行配置工作。
onAccessibilityEvent();
通過這個函數可以接收系統發送來的AccessibilityEvent,接收來的AccessibilityEvent是經過過濾的,過濾是在配置工作時設置的。
onInterrupt();
這個在系統想要中斷AccessibilityService返給的響應時會調用。在整個生命周期里會被調用多次。
onUnbind(Intent intent);
在系統將要關閉這個AccessibilityService會被調用。在這個方法中進行一些釋放資源的工作。
1.1 配置:
對于輔助功能類的配置有兩種方式:
(1)onServiceConnected()中初始化的方式:
(2)使用在manifest中添加meta-data的方式(Android 4.0 開始 ——可以參見我在注冊中的代碼片段)
在value文件夾中,添加一個xml文件夾(名字應該是可以自定義的,個人沒有試過),并在其中添加一個accessibilityservice.xml或者任何你喜歡的名字。可參見下圖:
建議將一些不可動態更改的服務配置寫到xml文件中。
可以看到上邊使用了部分的參數,都是比較常用的一些參數配置,下面來介紹下這些參數的作用:
Event types(android:accessibilityEventTypes ?/ ?info.eventTypes)
簡單說兩個常用的:(具體的可以參照API真的太多了)
typeAllMask ?/ ?AccessibilityEvent.TYPES_ALL_MASK:全局事件響應
typeViewClicked ?/ ?AccessibilityEvent.TYPE_VIEW_CLICKED :點擊事件
Feedback Types(android:accessibilityFeedbackType ?/ ?info.feedbackType )
feedbackGeneric ?/ ?AccessibilityServiceInfo.FEEDBACK_GENERIC : 通用的反饋
feedbackAudible ?/ ?AccessibilityServiceInfo.FEEDBACK_AUDIBLE : 聲音反饋
feedbackSpoken ?/? AccessibilityServiceInfo.FEEDBACK_SPOKEN ?: ?語音反饋
CanRetrieveWindow? (android:canRetrieveWindowContent? / info.getCanRetrieveWindowContent())
從一個AccessibilityEvent中調查完全視圖層級的能力隱式地暴露私有用戶信息給你的無障礙服務。出于這個原因,你的服務必須通過無障礙服務配置XML文件請求這個級別的訪問權,通過包含canRetrieveWindowContent屬性和設置它為true。如果你不在你的服務配置xml文件中包含這個設置,那么對getSource()的調用會失敗。
PackageNames (android:packageNames=? /? info.packageNames)
需要服務監聽的包名,中間可以用","分隔開。
notificationTimeout(android:notificationTimeout? /? info.notificationTimeout)
響應時間的設置
1.2 Service類中對事件的攔截處理
這個部分主要在onAccessibilityEvent()中處理回調返回的AccessibilityEvent。
主要需要了解的有:
AccessibilityNodeInfo(findAccessibilityNodeInfosByViewId,findAccessibilityNodeInfosByText)
AccessibilityEvent(eventType)
(1)AccessibilityEvent:
首先介紹AccessibilityEvent:
This class represents accessibility events that are sent by the system when something notable happens in the user interface.(在用戶交互使用時系統返回的event事件)
方法:
getSource() :
Gets the AccessibiltyNodeInfo?of the event source.
當前event的節點信息
順便說下getRootInActiveWindow()
Gets the root node in the currently active window if this service can retrieve window content.
中文的翻譯應該是獲取到當前活躍中本服務的可檢索到窗口的根節點
getSource()
?約等于??AccessibilityService.getRootInActiveWindow();(我輸出過兩個NodeInfo的child個數,不太一致,有個數差。)
It is a client responsibility to recycle the received info by calling AccessibilityNodeInfo.recycle()?to avoid creating of multiple instances.
為避免創建重復的實例通過recycle方法回收掉nodeInfo(我們自己手動去回收)
eventType :(常用)
TYPE_NOTIFICATION_STATE_CHANGED
represents the event of change in the content of a window. This change can be adding/removing view, changing a view size, etc.
基本窗口view的變化都可以使用這個type來監聽
TYPE_WINDOW_STATE_CHANGED
Represents the event of opening a Pop,Menu,Dialog?etc.
打開popupwindow,菜單,對話框時候會觸發
TYPE_WINDOW_CONTENT_CHANGED
Represents the event of changing the content of a window and more specifically the sub-tree rooted at the event's source.
更加精確的代表了基于當前event.source中的子view的內容變化
TYPE_WINDOWS_CHANGED
Represents the event change in the windows shown on the screen.
窗口的變化
(2)AccessibilityNodeInfo:
我覺得是輔助功能中最重要的的一個內容,也是最坑的地方到來了。
關于節點這個問題應該是沒有太多的可說的地方,但是呢,出問題出的最多的地方也是這里。
先看下怎么獲取NodeInfo:
AccessibilityNodeInfo mNodeInfo = getRootInActiveWindow();
AccessibilityNodeInfo mNodeInfo= event.getSource();
兩種獲取方式,之前我也提過,有時候兩種方式獲取的childNode個數不一致,挨了個球,我都不知道是腫么回事,有了解原理的大大請給我解解惑吧。
好的接下來我們來查找我們需要做操作的view,在NodeInfo中,默認提供了兩個方法來查找我們需要操作的對象:(開始重頭戲)
findAccessibilityNodeInfosByViewId(String str)
findAccessibilityNodeInfosByText(String str)
(1)先說上一個方法:findAccessibilityNodeInfosByViewId(String id)
List <AccessibilityNodeInfo> listNodes = mNodeInfo.findAccessibilityNodeInfosByViewId("id");
好的,關于這個ID我真的想說說事,每個API不同,同一個對象的ID可能不同:So,做國際化的時候,噩夢來了,為什么在大Samsung上邊能夠找到并操作這個node對象,在LG上就不可以捏....
OK,這里就來到了我們的關于node對象的id問題了,放心,Google會讓你有解決方法的。(不過就是要累死你,累死你累死你)隆重的來到Hierarchy View。(這里可以百度,大概就是在Android Device Monitor中打開這個View,查看對應界面的view的ID信息)。
關于真機不能連接Hierarchy View,可以看下我另一篇文章《AccessibilityService 獲取View的Id》
好了,好多同學以為知道id就完了,呵呵,too young to naive。
id的格式大概是這樣的"com.android.settings:id/force_stop_button"(這里以設置界面的強制停止按鈕作為范例),請注意加粗的部分,WTF我怎么知道這個前邊是神馬...
不要驚慌,注意看這里:
聰明的小朋友了解了吧。哈哈不多說,沒懂就多看看圖。
(2)說完了findById,我們接著說findAccessibilityNodeInfosByText(String text)
List listNodes = mNodeInfo.findAccessibilityNodeInfosByText("text");
看了上邊的findById之后,這個方法也簡單多了吧。沒有那么坑,只是做中文系統的時候,直接就可以看圖寫關鍵詞。所以就不多說了。
(3)關于node的使用:
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
點擊事件簡單至極,一看就明白了。
需要其他的操作,只需要看看API,換換ACTION啦。
(4)全局按鈕的操作
performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
同上。全局的操作是在service做出的action,和node沒有太大的關系。
2. 注冊:
AccessibilityService,從名字上來看,有經驗的小朋友就不難看出來,他是一個service,(內心獨白:屁話,是個猿都能看出來,鄙視...)那對于他的注冊,就不需要多說了,這里簡要的將manifest中service注冊的一些參數做出說明:
name:對應的是自定義service的包名
label:對應了在系統輔助功能開關界面中,你的service的名字(例如:手機管家等)
description :則是點擊對應的服務進入開關界面后,該服務的簡介
permission:應該都不陌生,對應的權限
(亦可在service中單獨寫出來
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE">)
intent-filter:指定了執行的組件為輔助功能類
3. 其他的一些輔助判斷的方法:
3.1是否開啟輔助功能的判斷
3.2 跳轉到輔助功能界面,開啟輔助功能
總結:
在輔助功能開發中,初次開發的朋友可能會遇到的一些難點(我覺得我第一次開發的時候最難得就是找viewId)
1、獲取需要操作的viewId
之前也提到過可以使用hierarchy View來查找對應的viewId,但是在實際開發中,很多手機是沒有辦法連接server進行dump view的,在這個時候,我們其實可以在AccessibiltiyService的配置中添加一個flag。對應的屬性是:android:accessibilityFlags="flagReportViewIds"。在代碼中我們就可以通過node節點來getViewIdResourceName()獲取對應的節點的id。
2、獲取到id后,查找到需要操作的node
根據id查找節點的方法上邊也有介紹過,需要注意的是,id的格式,對于packageName我們可以通過getPackageName()方法獲取。
最后,祝大家都能夠愉快的進行輔助功能的開發工作。
PS.在網上搜索輔助功能的話,都會出現一些關于微信搶紅包的插件。沒錯,微信搶紅包的插件就是使用了我們今天要介紹的輔助功能來開發的。