場景
自己想寫一個搶紅包工具,于是需要了解AccessibilityService,在此記錄一下學習使用的過程。目錄如下:
1.什么是AccessibilityService
2.AccessibilityService的基本使用
2.1 創建服務類
2.2 聲明服務
2.3 服務參數設置
2.3.1 通過<meta-data>進行配置
2.3.2 通過setServiceInfo()方法進行配置
2.4 啟動服務
2.5 獲取事件信息
3.AccessibilityEvent類詳解
1.什么是AccessibilityService
Google
為了讓Android系統更實用,為用戶提供了無障礙輔助服務AccessibilityService
AccessibilityService運行在后臺,并且能夠收到由系統發出的一些事件(即:AccessibilityEvent事件
,這些事件表示用戶界面一系列的狀態變化),比如焦點改變、輸入內容變化、按鈕被點擊了等等,該種服務能夠請求獲取當前活動窗口并查找其中的內容。換言之,界面中產生的任何變化都會產生一個AccessibilityEvent事件
,并由系統通知給AccessibilityService
。這就像監視器監視著界面的一舉一動,一旦界面發生變化,立刻發出通知:大家請注意,界面發生了變化。
2.AccessibilityService
的基本使用
2.1 創建服務類
編寫自己的服務類,需要繼承AccessibilityService
類.其中要實現onAccessibilityEvent(AccessibilityEvent event)
及onInterruput()
兩個重要的方法:
/**
* 搶紅包服務
*/
public class LuckyMoneyService extends AccessibilityService {
/**
* 當服務連上時的回調
*/
@Override
protected void onServiceConnected() {
super.onServiceConnected();
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}
}
方法名 | 含義解析 |
---|---|
onServiceConnected() | 系統成功綁定該服務時被觸發,也就是當你在設置中開啟相應的服務,系統成功的綁定了該服務時會觸發,通常我們可以在這里做一些初始化操作 |
onAccessibilityEvent(AccessibilityEvent event) | 有關AccessibilityEvent事件的回調函數.系統通過sendAccessibiliyEvent()不斷的發送AccessibilityEvent到此處 |
disableSelf() | 禁用當前服務,也就是在服務可以通過該方法停止運行 |
findFoucs(int falg) | 查找擁有特定焦點類型的控件 |
getRootInActiveWindow() | 如果配置能夠獲取窗口內容,則會返回當前活動窗口的根結點 |
getSeviceInfo() | 獲取當前服務的配置信息 |
performGlobalAction(int action) | 執行全局操作,比如返回,回到主頁,打開最近等操作 |
setServiceInfo(AccessibilityServiceInfo info) | 設置當前服務的配置信息 |
getSystemService(String name) | 獲取系統服務 |
onKeyEvent(KeyEvent event) | 如果允許服務監聽按鍵操作,該方法是按鍵事件的回調,需要注意,這個過程發生了系統處理按鍵事件之前 |
2.2 聲明服務
像其他Service
服務一樣,需要在AndroidManifest.xml
中聲明該服務.除此之外,該服務還必須配置以下兩項:
配置<intent-filter>
,其name
為固定的:
android.accessibilityservice.AccessibilityService
聲明BIND_ACCESSIBILITY_SERVICE
權限,以便系統能夠綁定該服務(4.0版本后要求)
注意:任何一點配置錯誤,系統都無反應,因此其固定配置如下:
<service
android:name=". LuckyMoneyService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
</service>
2.3 服務參數設置
在AndroidManifest.xml
聲明了該服務之后,接下來就是需要對該服務進行一些參數設置了.該服務能夠被配置用來接受指定類型的事件,監聽指定package
,檢索窗口內容,獲取事件類型的時間等等.目前有兩種配置方法:
方法一:4.0之后提供了可以通過<meta-data>
標簽進行配置
方法二:通過setServiceInfo()
方法進行配置
2.3.1 通過<meta-data>進行配置
在AndroidManifest.xml
聲明的service
中,其中:
accessibility_service_label
是服務的名稱,這些會在設置里面的輔助服務中顯示出來,用戶需要在里面開啟服務才能使用。
accessibility_service_description
是有關這個服務的功能說明,也可以在meta-data
指定的配置文件中聲明,效果同:android:description="搶紅包服務,請開啟"
permission和intent-filter
是固定寫法。
提供一個meta-data
標簽,meta-data
主要用于對服務進行一些配置,配置的具體內容通過android:resource
指定相應的配置文件:
<service
android:name=".new_service.LuckyMoneyService"
android:enabled="true"
android:exported="true"
android:label="搶紅包服務"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/lucky_money_accessibility_service_config" />
</service>
接下來我們來看lucky_money_accessibility_service_config
的相關配置。
提示:該配置文件是在res
目錄下創建xml
文件,并在其中創建配置文件lucky_money_accessibility_service_config.xml
,該文件名可自定義。
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:description="搶紅包服務,請開啟"
android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged"
android:packageNames="com.tencent.mm"
android:accessibilityFeedbackType="feedbackGeneric"
android:notificationTimeout="100"
android:accessibilityFlags="flagDefault"
android:canRetrieveWindowContent="true"/>
現在我們對配置中的重要屬性進行說明:
description="搶紅包服務,請開啟"
是有關這個服務的功能說明。
accessibilityEventTypes
:服務要監控的事件類型,如通知、窗口改變、點擊、焦點改變等等,如果有多個可以用 | 連起來,具體的值可以在AccessibilityEvent
類中查到,如typeAllMask
表示接受所有的事件通知。
android:packageNames
服務要監控的應用的包名,如果有多個則用逗號連起來,空著表示監聽所有的應用。
android:accessibilityFeedbackType
服務反饋的方式,如語音、震動等等,feedbackAllMask
代表所有類型。
notificationTimeout
:接受事件的時間間隔,通常將其設置為100即可.
canRetrieveWindowContent
:服務能否獲取窗口里面的內容。也就是如果你希望在服務中獲取窗體內容的化,則需要設置其值為true.
這些配置除了在xml
里面寫之外,還可以在代碼中建立一個AccessibilityServiceInfo
對象,然后通過setServiceInfo()
來設置。見下方2.3.2:
2.3.2 通過setServiceInfo()
方法進行配置
也可以通過setServiceInfo(AccessibilityServiceInfo)
為其配置信息,除此之外,通過該方法可以在運行期間動態修改服務配置.需要注意,該方法只能用來配置動態屬性:eventTypes,feedbackType,flags,notificaionTimeout及packageNames
.
通常是在onServiceConnected()
進行配置,如下代碼:
@Override
protected void onServiceConnected() {
AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo();
serviceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
serviceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
serviceInfo.packageNames = new String[]{"com.tencent.mm"};
serviceInfo.notificationTimeout=100;
setServiceInfo(serviceInfo);
}
在這里涉及到了AccessibilityServiceInfo
類,做個說明:
AccessibilityServiceInfo
該類被用于配置AccessibilityService
信息,該類中包含了大量用于配置的常量字段及用來xml
屬性,比如常見的:
android:accessibilityEventTypes
、android:canRequestFilterKeyEvents
、android:packageNames
等等。
2.4 啟動服務
當我們做完以上操作,便可將app安裝到手機.安裝成功后,在設置->輔助功能中便可以找到我們的服務.該服務默認處在關閉狀態,需要手動開啟。代碼方式打開如下:
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
context.startActivity(intent);
2.5 獲取事件信息
上面我們說到,onAccessibilityEvent(AccessibilityEvent event)
是該服務的核心方法,其中參數event
封裝來自界面相關事件的信息,比如我們可以獲得該事件的事件類型,進而根據起類型選擇不同的處理方式:
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}
這里我們先對AccessibilityEvent
進行簡單的說明:
當用戶發生發生變化時,系統會發送一系列的AccessibilityEvent
事件,比如按鈕被點擊時會發送TYPE_VIEW_CLICKED
類型的事件.
下面會對AccessibilityEvent類詳細介紹。
3.AccessibilityEvent類詳解
AccessibilityEvent
是輔助功能服務中一個非常重要的類,它主要用于提供有關用戶界面交互的信息。當用戶界面中發生了服務需要關注的事件時系統就會發送AccessibilityEvent
事件,并傳遞到onAccessibilityEvent方法
。通常,用得比較多的是event.getEventType()
和event.getClassName()
,分別用于獲取當前事件的類型和發生該事件的類名,通過這兩個的判斷可以過濾想要處理的事件,并進行操作。例如,當點擊一個按鈕時,會發送一個type
為TYPE_VIEW_CLICKED
,className
為android.widget.Button的
事件,如果我們需要在某個按鈕被點擊時做一些操作,就可以在onAccessibilityEvent
中對event
進行判斷。
AccessibilityEvent
的Type
包括:
TYPE_VIEW_CLICKED
TYPE_VIEW_LONG_CLICKED
TYPE_VIEW_FOCUSED
TYPE_VIEW_SELECTED
TYPE_VIEW_TEXT_CHANGED
TYPE_WINDOW_STATE_CHANGED
TYPE_NOTIFICATION_STATE_CHANGED
TYPE_TOUCH_EXPLORATION_GESTURE_START
TYPE_TOUCH_EXPLORATION_GESTURE_END
TYPE_VIEW_HOVER_ENTER
TYPE_VIEW_HOVER_EXIT
TYPE_VIEW_SCROLLED
TYPE_VIEW_TEXT_SELECTION_CHANGED
TYPE_WINDOW_CONTENT_CHANGED
TYPE_ANNOUNCEMENT
TYPE_GESTURE_DETECTION_START
TYPE_GESTURE_DETECTION_END
TYPE_TOUCH_INTERACTION_START
TYPE_TOUCH_INTERACTION_END
TYPE_VIEW_ACCESSIBILITY_FOCUSED
TYPE_WINDOWS_CHANGED
TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
方法 | 說明 |
---|---|
getEventType() | 事件類型,如上面列舉 |
getSource() | 獲取事件源對應的結點信息 |
getClassName() | 獲取事件源對應類的類型,比如點擊事件是有某個Button產生的,那么此時獲取的就是Button的完整類名: android.widget.Button |
getText() | 獲取事件源的文本信息,比如事件是有TextView發出的,此時獲取的就是TextView的text屬性.如果該事件源是樹結構,那么此時獲取的是這個樹上所有具有text屬性的值的集合 |
isEnabled() | 事件源(對應的界面控件)是否處在可用狀態 |
getItemCount() | 如果事件源是樹結構,將返回該樹根節點下子節點的數量 |
特別注意??:
系統不斷的產生各種事件,有些是界面控件產生的,有些是系統產生的。對于由界面控件的產生的事件,通常我們將該控件稱之為事件源。但并不是所有的事件都能通過getSource()
方法獲取到事件源,比如像通知消息類型的事件TYPE_NOTIFICATION_STATE_CHANGED
。
另外,AccessibilityService中還有一個performGlobalAction()方法,用于執行一些通用的事件,主要包括:
GLOBAL_ACTION_BACK 點擊返回按鈕
GLOBAL_ACTION_HOME 點擊home
GLOBAL_ACTION_NOTIFICATIONS 打開通知
GLOBAL_ACTION_RECENTS 打開最近應用
GLOBAL_ACTION_QUICK_SETTINGS 打開快速設置
GLOBAL_ACTION_POWER_DIALOG 打開長按電源鍵的彈框
4.1 獲取窗口內容
僅僅知道事件的信息是不夠的,我們還希望通過事件來獲取發出該事件(事件源)的信息,比如Button按鈕被點擊時它的text.一個服務可以配置為允許服務檢索窗口內容,即獲取窗口內容。
整個窗口內容本質上是關于AccessibilityWindowInfo
和AccessibilityNodeInfo
的樹結構,我們可以稱之為內容樹.(類似View Tree,但由不完全相同)
需要注意,該服務可能配置了只檢測了部分事件,而不是全部事件,這就意味著,當內容樹發生變化后,該服務可能并不知道。即該服務無法及時的了解當前的內容樹是否發生了變化。比如說,你的服務只檢測了點擊事件,但是此時界面的輸入焦點已經變化,這樣整個結點樹也發生了變化,但是你的服務卻不知道,此時你在結點中拿到的窗口內容可能已經不是最新的了。因此,如果你想及時的獲知當前窗口的內容,那么就在配置的時候,設置監聽全部事件。
正如上面所提到的,要想獲取窗口內容,在配置AccessibilityService
時需設置canRetrieveWindowContent為true
之后,便可以通過一下方法獲取窗口內容:AccessibilityEvent.getSource()
、findFocus(int)
、getWindow()
或者getRootInActiveWindow()
。
4.2 服務的生命周期
要理解該中服務的生命周期只需要記住一下三點即可:
a:該種服務完全由系統管理,并遵循已有的服務周期.
b:開啟一個服務只能由用戶在設置中打開,而關閉則只能由用戶在設置中關閉或者服務本身通過diableSelf()方法關閉(當然,現在有些第三放軟件也可以強制關閉該類型服務)
c:系統綁定該服務之后,會調用onServiceConnected()方法,這個方法可以被重寫,在其中,你可以做一些初始化的操作.
4.3 檢測服務是否開啟
介紹了一些AccessibilityService
的基礎知識之后,再補充一點關于檢測某個服務是否開啟的知識.通常來說大體有一下兩種方法來檢測服務是否啟用:
方法一:借助服務管理器AccessibilityManager
來判斷,但是該方法不能檢測app本身開啟的服務.
private boolean enabled(String name) {
AccessibilityManager am = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
List<AccessibilityServiceInfo> serviceInfos = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);
List<AccessibilityServiceInfo> installedAccessibilityServiceList = am.getInstalledAccessibilityServiceList();
for (AccessibilityServiceInfo info : installedAccessibilityServiceList) {
Log.d("MainActivity", "AccessibilityServiceInfo -->" + info.getId());
if (name.equals(info.getId())) {
return true;
}
}
return false;
}
既然談到了AccessibilityManager,那么在這里我們就做個簡單的介紹:
AccessibilityManager是系統級別的服務,用來管理AccessibilityService服務,比如分發事件,查詢系統中服務的狀態等等,更多信息參考官方文檔,常見方法如下:
方法 | 說明 |
---|---|
getAccessibilityServiceList() | 獲取服務列表(api 14之后廢棄,用下面的方法代替) |
getInstalledAccessibilityServiceList() | 獲取已安裝到系統的服務列表 |
getEnabledAccessibilityServiceList(int feedbackTypeFlags) | 獲取已啟用的服務列表 |
isEnabled() | 判斷服務是否啟用 |
sendAccessibilityEvent(AccessibilityEvent event) | 發送事件 |
方法二:我們知道大部分的系統屬性都在settings中進行設置,比如wifi,藍牙狀態等,而這些信息主要是存儲在settings對應的的數據庫中(system表和serure表),這就意味我們可以通過直接讀取setting設置來判斷相關服務是否開啟:
private boolean checkStealFeature1(String service) {
int ok = 0;
try {
ok = Settings.Secure.getInt(getApplicationContext().getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED);
} catch (Settings.SettingNotFoundException e) {
}
TextUtils.SimpleStringSplitter ms = new TextUtils.SimpleStringSplitter(':');
if (ok == 1) {
String settingValue = Settings.Secure.getString(getApplicationContext().getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
ms.setString(settingValue);
while (ms.hasNext()) {
String accessibilityService = ms.next();
if (accessibilityService.equalsIgnoreCase(service)) {
return true;
}
}
}