Android 無障礙服務(wù)AccessibilityService

前言

谷歌提供了一種用于與app自動交互的手動無障礙服務(wù)罚勾,只要像正常的Android Service一樣寫一個繼承AccessibilityService的類即可創(chuàng)建一個無障礙服務(wù)晨缴。當(dāng)app有操作時系統(tǒng)就會通知你開啟的服務(wù),有時候遇到困難無障礙服務(wù)也是一個不得已的方案笔诵。

具體可以參照谷歌文檔:https://developer.android.com/guide/topics/ui/accessibility/service

創(chuàng)建服務(wù)類

先創(chuàng)建一個app,實現(xiàn)一個繼承AccessibilityService的Service

public class AccessibilitySampleService extends AccessibilityService {
    public static String TAG = "SharkChilli";

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        // 此方法是在主線程中回調(diào)過來的屯掖,所以消息是阻塞執(zhí)行的
 
    }

    @Override
    public void onInterrupt() {
    }
}

onServiceConnected方法中可以來配置服務(wù)的一些設(shè)置瓢谢,比如監(jiān)控哪些app的哪些動作之類的兼呵。
onAccessibilityEvent方法就是app發(fā)送交互后會調(diào)用的方法兔辅,相關(guān)的交互信息都在AccessibilityEvent 中。
谷歌官網(wǎng)的具體解釋如下

  • onServiceConnected() -(可選)當(dāng)系統(tǒng)成功連接到無障礙服務(wù)時击喂,會調(diào)用此方法维苔。使用此方法可為服務(wù)執(zhí)行任何一次性設(shè)置步驟,包括連接到用戶反饋系統(tǒng)服務(wù)懂昂,如音頻管理器或設(shè)備振動器介时。如果您要在運行時設(shè)置服務(wù)的配置或做出一次性調(diào)整,從此處調(diào)用 setServiceInfo() 非常方便凌彬。
  • onAccessibilityEvent() -(必需)當(dāng)系統(tǒng)檢測到與無障礙服務(wù)指定的事件過濾參數(shù)匹配的 `AccessibilityEvent 時沸柔,會回調(diào)此方法。例如铲敛,當(dāng)用戶點擊某個按鈕或?qū)⒔裹c置于應(yīng)用中的某個界面控件上褐澎,而無障礙服務(wù)正在為其提供反饋時。出現(xiàn)這種情況時伐蒋,系統(tǒng)會調(diào)用此方法工三,并傳遞關(guān)聯(lián)的 AccessibilityEvent,服務(wù)隨后可以對其進行解讀并用其向用戶提供反饋先鱼。此方法可能會在服務(wù)的整個生命周期內(nèi)被調(diào)用多次俭正。
  • onInterrupt() -(必需)當(dāng)系統(tǒng)要中斷服務(wù)正在提供的反饋(通常是為了響應(yīng)將焦點移到其他控件等用戶操作)時,會調(diào)用此方法焙畔。此方法可能會在服務(wù)的整個生命周期內(nèi)被調(diào)用多次掸读。
  • onUnbind() -(可選)當(dāng)系統(tǒng)將要關(guān)閉無障礙服務(wù)時,會調(diào)用此方法。使用此方法可執(zhí)行任何一次性關(guān)閉流程寺枉,包括取消分配用戶反饋系統(tǒng)服務(wù)抑淫,如音頻管理器或設(shè)備振動器。

配置AndroidManifest.xml

和正常的Service一樣需要到AndroidManifest.xml中配置我們實現(xiàn)的Service姥闪。

<service
            android:name="com.shark.service.AccessibilitySampleService"
            android:exported="true"
            android:label="@string/accessibility_tip"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
            android:process=":BackgroundService">
            <!--為了被視為無障礙服務(wù)始苇,您必須在清單的 application 元素中添加一個 service 元素(而非 activity 元素)。
            此外筐喳,在 service 元素中催式,您還必須添加一個無障礙服務(wù) intent 過濾器。為了與 Android 4.1 及更高版本兼容避归,
            清單還必須保護該服務(wù)荣月,方法是添加 BIND_ACCESSIBILITY_SERVICE 權(quán)限以確保只有系統(tǒng)可以綁定到它 -->
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
            <!--從 Android 4.0 開始,您可以在清單中添加一個引用配置文件的 <meta-data> 元素梳毙,這樣可讓您為無障礙服務(wù)設(shè)置所有選項-->
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessibility_config" />
 </service>

這里基本是固定的在meta-data信息中配置的android:resource="@xml/accessibility_config"是指定我們服務(wù)的配置信息哺窄,這個配置工作也可以在上面的onServiceConnected方法通過AccessibilityServiceInfo配置。

我們看看這兩種的配置方式
xml創(chuàng)建好相關(guān)的文件后配置如下

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:canRetrieveWindowContent="true"
    android:description="@string/accessibility_desc"
    android:packageNames="com.shark.nougat"
    android:notificationTimeout="100" />

onServiceConnected方法中配置

protected void onServiceConnected() {
        super.onServiceConnected();
        AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
        accessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPE_WINDOWS_CHANGED
                | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
                | AccessibilityEvent.TYPE_VIEW_CLICKED
                | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
                | AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED;
        accessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
        accessibilityServiceInfo.notificationTimeout = 0;
        accessibilityServiceInfo.flags = AccessibilityServiceInfo.DEFAULT;
        accessibilityServiceInfo.packageNames = new String[]{"com.shark.nougat"};
        setServiceInfo(accessibilityServiceInfo);
    }

如需詳細了解可在無障礙服務(wù)配置文件中使用的 XML 屬性账锹,請點擊以下鏈接轉(zhuǎn)到相應(yīng)的參考文檔:

如需詳細了解可在運行時動態(tài)設(shè)置的配置設(shè)置萌业,請參閱 AccessibilityServiceInfo 參考文檔


開啟無障礙服務(wù)

將app安裝后,到設(shè)置中開啟服務(wù)


image.png

這個名稱就是我們前面設(shè)置的lable


image.png

image.png

AccessibilityEvent的操作

開啟后app只要有互動奸柬,系統(tǒng)就會將這次互動的相關(guān)信息封裝到AccessibilityEvent中并調(diào)用onAccessibilityEvent方法生年,這里就介紹一下基本操作

判斷操作的類型

public void onAccessibilityEvent(AccessibilityEvent event) {
        // 此方法是在主線程中回調(diào)過來的,所以消息是阻塞執(zhí)行的
        switch (event.getEventType()) {
            /**
             * AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED廓奕,
             * 而會調(diào)用AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED抱婉,
             * 而AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED只要內(nèi)容改變后都會調(diào)用,
             * 所以一般是使用AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED來作為監(jiān)測事件的
             */
            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
                Log.i(TAG, "TYPE_WINDOW_STATE_CHANGED 界面改變");
                break;
            case AccessibilityEvent.TYPE_VIEW_CLICKED:
                Log.i(TAG, "TYPE_VIEW_CLICKED view被點擊");
                break;
        }
    }

通過AccessibilityEvent.getEventType()可以獲得這次互動的類型桌粉。更多類型可以參考官網(wǎng)文檔

獲得操作的app包名和Activity類名

String pkgName = event.getPackageName().toString();
String className = event.getClassName().toString();

AccessibilityNodeInfo操作

在無障礙服務(wù)中控件節(jié)點會包裝為一個AccessibilityNodeInfo蒸绩,通過這個類我們可以在代碼中與之交互

獲取root節(jié)點類型

private AccessibilityNodeInfo getRootNodeInfo() {
        AccessibilityEvent curEvent = mAccessibilityEvent;
        AccessibilityNodeInfo nodeInfo = null;
        if (Build.VERSION.SDK_INT >= 16) {
            // 建議使用getRootInActiveWindow,這樣不依賴當(dāng)前的事件類型
            if (mAccessibilityService != null) {
                nodeInfo = mAccessibilityService.getRootInActiveWindow();
                Log.d(TAG, "getRootNodeInfo: " + nodeInfo);
            }
            // 下面這個必須依賴當(dāng)前的AccessibilityEvent
//            nodeInfo = curEvent.getSource();
        } else {
            nodeInfo = curEvent.getSource();
        }
        return nodeInfo;
    }

獲取了根節(jié)點就可以向下去查找其他節(jié)點了

通過ID查找節(jié)點

nodeInfo.findAccessibilityNodeInfosByViewId(viewId);

id:"com.shark.nougat:id/sample_text"

通過Text查找節(jié)點

nodeInfo.findAccessibilityNodeInfosByText(text);

點擊節(jié)點

node.performAction(AccessibilityNodeInfo.ACTION_CLICK);

修改文本

node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);

點擊全局返回铃肯、Home

performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME);

uiautomatorviewer

我們可以使用sdk中自帶的uiautomatorviewer查看app id等信息


image.png

引用

Github對AccessibilitySample封裝
android 輔助功能(無障礙) AccessibilityService 實戰(zhàn)入門詳解
Android輔助功能(Accessibility)簡介
Android輔助功能原理與基本使用詳解-AccessibilityService

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侵贵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子缘薛,更是在濱河造成了極大的恐慌窍育,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宴胧,死亡現(xiàn)場離奇詭異漱抓,居然都是意外死亡,警方通過查閱死者的電腦和手機恕齐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門乞娄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事仪或∪纺鳎” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵范删,是天一觀的道長蕾域。 經(jīng)常有香客問我,道長到旦,這世上最難降的妖魔是什么旨巷? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮添忘,結(jié)果婚禮上采呐,老公的妹妹穿的比我還像新娘。我一直安慰自己搁骑,他們只是感情好斧吐,可當(dāng)我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著仲器,像睡著了一般煤率。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上娄周,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天涕侈,我揣著相機與錄音沪停,去河邊找鬼煤辨。 笑死,一個胖子當(dāng)著我的面吹牛木张,可吹牛的內(nèi)容都是我干的众辨。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼舷礼,長吁一口氣:“原來是場噩夢啊……” “哼鹃彻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起妻献,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蛛株,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后育拨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谨履,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年熬丧,在試婚紗的時候發(fā)現(xiàn)自己被綠了笋粟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖害捕,靈堂內(nèi)的尸體忽然破棺而出绿淋,到底是詐尸還是另有隱情,我是刑警寧澤尝盼,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布吞滞,位于F島的核電站,受9級特大地震影響东涡,放射性物質(zhì)發(fā)生泄漏冯吓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一疮跑、第九天 我趴在偏房一處隱蔽的房頂上張望组贺。 院中可真熱鬧,春花似錦祖娘、人聲如沸失尖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掀潮。三九已至,卻和暖如春琼富,著一層夾襖步出監(jiān)牢的瞬間仪吧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工鞠眉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留薯鼠,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓械蹋,卻偏偏與公主長得像出皇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子哗戈,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容