從搶紅包插件說AccessibilityService

微信紅包自打出世以來就極其受歡迎,搶紅包插件可謂紅極一時.今天,我們重新談?wù)創(chuàng)尲t包插件的哪些事兒.本質(zhì)上,搶紅包插件的原理不難理解,其過程就是在收到紅包時,自動模擬點擊.做過自動化UI測試的童鞋應(yīng)該非常熟悉了.

那么問題來了,我們怎么知道有沒有紅包,又怎么模擬點擊操作呢?在PC端我們有按鍵精靈,那么在Android設(shè)備上呢?話說也偶然,Google為了讓Android系統(tǒng)更實用,為用戶提供了無障礙輔助服務(wù)---AccessibilityService.

AccessibilityService運行在后臺,并且能夠收到由系統(tǒng)發(fā)出的一些事件(AccessibilityEvent,這些事件表示用戶界面一系列的狀態(tài)變化),比如焦點改變,輸入內(nèi)容變化,按鈕被點擊了等等,該種服務(wù)能夠請求獲取當(dāng)前活動窗口并查找其中的內(nèi)容.

換言之,界面中產(chǎn)生的任何變化都會產(chǎn)生一個時間,并由系統(tǒng)通知給AccessibilityService.這就像監(jiān)視器監(jiān)視著界面的一舉一動,一旦界面發(fā)生變化,立刻發(fā)出警報.

現(xiàn)在讓我們來看看如何AccessibilityService的基本使用.

基礎(chǔ)使用

開發(fā)這種服務(wù)需要繼承AccessibilityService,并實現(xiàn)其中的方法.

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

編寫自己的服務(wù)類,需要繼承AccessibilityService類,如下:

public class RobService extends AccessibilityService {

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        //handle 
    }

    @Override
    public void onInterrupt() {

    }

    @Override
    protected void onServiceConnected() {
       
    }


這里我們簡單的介紹一下該類常用的方法,更詳細(xì)的內(nèi)容參見官方文檔

方法 作用
disableSelf() 禁用當(dāng)前服務(wù),也就是在服務(wù)可以通過該方法停止運行
findFoucs(int falg) 查找擁有特定焦點類型的控件
getRootInActiveWindow() 如果配置能夠獲取窗口內(nèi)容,則會返回當(dāng)前活動窗口的根結(jié)點
getSeviceInfo() 獲取當(dāng)前服務(wù)的配置信息
onAccessibilityEvent(AccessibilityEvent event) 有關(guān)AccessibilityEvent事件的回調(diào)函數(shù).系統(tǒng)通過sendAccessibiliyEvent()不斷的發(fā)送AccessibilityEvent到此處
performGlobalAction(int action) 執(zhí)行全局操作,比如返回,回到主頁,打開最近等操作
setServiceInfo(AccessibilityServiceInfo info) 設(shè)置當(dāng)前服務(wù)的配置信息
getSystemService(String name) 獲取系統(tǒng)服務(wù)
onKeyEvent(KeyEvent event) 如果允許服務(wù)監(jiān)聽按鍵操作,該方法是按鍵事件的回調(diào),需要注意,這個過程發(fā)生了系統(tǒng)處理按鍵事件之前
onServiceConnected() 系統(tǒng)成功綁定該服務(wù)時被觸發(fā),也就是當(dāng)你在設(shè)置中開啟相應(yīng)的服務(wù),系統(tǒng)成功的綁定了該服務(wù)時會觸發(fā),通常我們可以在這里做一些初始化操作
  1. 聲明服務(wù)

像其他Service服務(wù)一樣,需要在AndroidManifest.xml中聲明.除此之外,該服務(wù)還必須配置以下兩項:

  • 配置<intent-filter>,其name為固定的android.accessibilityservice.AccessibilityService
  • 聲明BIND_ACCESSIBILITY_SERVICE權(quán)限,以便系統(tǒng)能夠綁定該服務(wù)(4.1版本后要求)

注意:任何一點配置錯誤,系統(tǒng)都無反應(yīng),因此其基本配置如下:

 <service
        android:name=".RobService"
        android:enabled="true"
        android:exported="true"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService"/>
    </intent-filter>
</service>

  1. 配置服務(wù)類

在AndroidManifest.xml聲明了該服務(wù)之后,接下來就是需要對該服務(wù)進行一些參數(shù)設(shè)置了.
該服務(wù)能夠被配置用來接受指定類型的事件,監(jiān)聽指定package,檢索窗口內(nèi)容,獲取事件類型的時間等等.目前有兩種配置方法:

  • 方法一:4.0之后提供了可以通過<meta-data>標(biāo)簽進行配置
  • 方法二:通過setServiceInfo()進行配置

1. 通過<meta-data>進行配置

在manifest生命的servce中提供一個meta-data標(biāo)簽,然后通過android:resource指定相應(yīng)的配置文件(在res目錄下創(chuàng)建xml文件,并在其中創(chuàng)建配置文件accessibility.xml):

 <service
    android:name=".RobService"
    android:enabled="true"
    android:exported="true"
    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/accessibility"/>
    </service>

接下來我們來看accessibility.xml的相關(guān)配置:

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
               
     android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="true"
    android:notificationTimeout="100"
    android:packageNames="com.tencent.mm" />

后面,我們在只需要仿照該配置文件根據(jù)自己的需求進行修改即可.

2. 通過setServiceInfo(AccessibilityServiceInfo info)

也可以通過setServiceInfo(AccessibilityServiceInfo)為其配置信息,除此之外,通過該方法可以在運行期間動態(tài)修改服務(wù)配置.需要注意,該方法只能用來配置動態(tài)屬性:
eventTypes,feedbackType,flags,notificaionTimeout及packageNames.

這里我們簡單的示例一下:

 @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該類被用于配置AccessibilityService信息,該類中包含了大量用于配置的常量字段及用來xml 屬性,比如常見的:
android:accessibilityEventTypes,android:canRequestFilterKeyEvents,android:packageNames等等,更多信息參見官方文檔

這里簡單的對重要屬性進行說明:

  1. accessibilityEventTypes:表示該服務(wù)對界面中的哪些變化感興趣,即哪些事件通知,比如窗口打開,滑動,焦點變化,長按等.具體的值可以在AccessibilityEvent類中查到,如typeAllMask表示接受所有的事件通知.
  1. accessibilityFeedbackType:表示反饋方式,比如是語音播放,還是震動
  2. canRetrieveWindowContent:表示該服務(wù)能否訪問活動窗口中的內(nèi)容.也就是如果你希望在服務(wù)中獲取窗體內(nèi)容的化,則需要設(shè)置其值為true.
  3. notificationTimeout:接受事件的時間間隔,通常將其設(shè)置為100即可.
  4. packageNames:表示對該服務(wù)是用來監(jiān)聽哪個包的產(chǎn)生的事件
  1. 啟動服務(wù)

當(dāng)我們做完以上操作,便可將app安裝到手機.安裝成功后,在設(shè)置->輔助功能中便可以找到我們的服務(wù).該服務(wù)默認(rèn)處在關(guān)閉狀態(tài),需要手動開啟.

  1. 獲取事件信息

上面我們說道,onAccessibilityEvent(AccessibilityEvent event)是該服務(wù)的核心方法,其中參數(shù)event封裝來自界面相關(guān)事件的信息,比如我們可以獲得該事件的事件類型,進而根據(jù)起類型選擇不同的處理方式:

 public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        switch (eventType) {
            case AccessibilityEvent.TYPE_VIEW_CLICKED:
                //界面點擊
                break;
            case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
                //界面文字改動
                break;
        }
    }

這里我們對AccessibilityEvent進行簡單的說明:
當(dāng)用戶發(fā)生發(fā)生變化時,系統(tǒng)會發(fā)送一系列的AccessibilityEvent事件,比如按鈕被電擊時會發(fā)送TYPE_VIEW_CLICKED類型的事件.

方法 說明
getEventType() 事件類型
getSource() 獲取事件源對應(yīng)的結(jié)點信息
getClassName() 獲取事件源對應(yīng)類的類型,比如點擊事件是有某個Button產(chǎn)生的,那么此時獲取的就是Button的完整類名
getText() 獲取事件源的文本信息,比如事件是有TextView發(fā)出的,此時獲取的就是TextView的text屬性.如果該事件源是樹結(jié)構(gòu),那么此時獲取的是這個樹上所有具有text屬性的值的集合
isEnabled() 事件源(對應(yīng)的界面控件)是否處在可用狀態(tài)
getItemCount() 如果事件源是樹結(jié)構(gòu),將返回該樹根節(jié)點下子節(jié)點的數(shù)量

簡單的介紹一下事件源

系統(tǒng)不斷的產(chǎn)生各種事件,有些是界面控件產(chǎn)生的,有些是系統(tǒng)產(chǎn)生的.對于由界面控件的產(chǎn)生的事件,通常我們將該控件稱之為事件源.并不是所有的事件都能通過getSource()方法獲取到事件源,比如像通知消息類型的事件(TYPE_NOTIFICATION_STATE_CHANGED).

  1. 獲取窗口內(nèi)容

僅僅知道事件的信息是不夠的,我們還希望通過事件來獲取發(fā)出該事件(事件源)的信息,比如Button按鈕被點擊時它的text.

一個服務(wù)可以配置為可以檢索窗口內(nèi)容,即獲取窗口內(nèi)容.整個窗口內(nèi)容本質(zhì)上是關(guān)于AccessibilityWindowInfo和AccessibilityNodeInfo的樹結(jié)構(gòu),我稱之為內(nèi)容樹.(類似View Tree,但由不完全相同)

需要注意,該服務(wù)可能配置了只檢測了部分事件,而不是全部事件,這就意味著,當(dāng)內(nèi)容樹發(fā)生變化后,該服務(wù)可能并不知道,即該服務(wù)無法及時的了解當(dāng)前的內(nèi)容樹是否發(fā)生了變化.比如說,你的服務(wù)只檢測了點擊事件,但是此時界面的輸入焦點已經(jīng)變化,這樣整個結(jié)點樹也發(fā)生了變化,但是你的服務(wù)卻不知道,此時你在結(jié)點中拿到的窗口內(nèi)容可能已經(jīng)不是最新的了.因此,如果你想及時的獲知當(dāng)前窗口的內(nèi)容,那么就在配置的時候,設(shè)置監(jiān)聽全部事件.

現(xiàn)在來看看如何獲取窗口內(nèi)容?

正如上面所提到的,要想獲取控件的相關(guān)信息,在配置AccessibilityService時需設(shè)置canRetrieveWindowContent為true.之后,便可以通過一下方法獲取窗口內(nèi)容:
AccessibilityEvent.getSource(),findFocus(int),getWindow()或者getRootInActiveWindow()

  1. 服務(wù)的生命周期

簡單的介紹下該類型服務(wù)相關(guān)的生命周期問題,主要是以下三點:

  • 該種服務(wù)完全由系統(tǒng)管理,并遵循已有的服務(wù)周期.
  • 開啟一個服務(wù)只能由用戶在設(shè)置中打開,而關(guān)閉則只能由用戶在設(shè)置中關(guān)閉或者服務(wù)本身通過diableSelf()方法關(guān)閉(當(dāng)然,現(xiàn)在有些第三放軟件也可以強制關(guān)閉該類型服務(wù))
  • 系統(tǒng)綁定該服務(wù)之后,會調(diào)用onServiceConnected()方法,這個方法可以被重寫,在其中,你可以做一些初始化的操作.
  1. 檢測服務(wù)是否開啟

介紹了一些AccessibilityService的基礎(chǔ)知識之后,再補充一點關(guān)于檢測某個服務(wù)是否開啟的知識.通常來說大體有一下兩種方法來檢測:

方法一:借助服務(wù)管理器AccessibilityManager來判斷,但是該方法不能檢測app本身開啟的服務(wù).

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", "all -->" + info.getId());
            if (name.equals(info.getId())) {
                return true;
            }
        }
        return false;
    }


既然談到了AccessibilityManager,那么在這里我們就做個簡單的介紹:
AccessibilityManager是系統(tǒng)級別的服務(wù),用來管理AccessibilityService服務(wù),比如分發(fā)事件,查詢系統(tǒng)中服務(wù)的狀態(tài)等等,更多信息參考官方文檔

方法 說明
getAccessibilityServiceList() 獲取服務(wù)列表(api 14之后廢棄,用下面的方法代替)
getInstalledAccessibilityServiceList() 獲取已安裝到系統(tǒng)的服務(wù)列表
getEnabledAccessibilityServiceList(int feedbackTypeFlags) 獲取已啟用的服務(wù)列表
isEnabled() 判斷服務(wù)是否啟用
sendAccessibilityEvent(AccessibilityEvent event) 發(fā)送事件

方法二:我們知道大部分的系統(tǒng)屬性都在settings中進行設(shè)置,比如wifi,藍牙狀態(tài)等,而這些信息主要是存儲在settings對應(yīng)的的數(shù)據(jù)庫中(system表和serure表),同樣我們也可以通過直接讀取setting設(shè)置來判斷相關(guān)服務(wù)是否開啟:

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;
                    }

                }
            }


到現(xiàn)在有關(guān)AccessibilityService的一些知識,我們已經(jīng)講完,下面我們就看它的具體使用,其中典型的應(yīng)用就是搶紅包插件.
另外,該服務(wù)不受系統(tǒng)重啟的的影響.除非你手動關(guān)閉或者通過第三方安全軟件將其強制關(guān)閉.


實際應(yīng)用

  1. 搶紅包插件

先回顧一下?lián)尲t包的的流程:

  1. 狀態(tài)欄出現(xiàn)"[微信紅包]"的消息提示,點擊進入聊天界面
  2. 點擊相應(yīng)的紅包信息,彈出搶紅包界面
  3. 在搶紅包界面點擊"開",打開紅包
  4. 在紅包詳情頁面,查看詳情,點擊返回按鈕返回微信聊天界面.

以上是不在微信聊天界面時的流程.如果你所在的微信聊天窗口出現(xiàn)紅包,則不會執(zhí)行步驟1,而是直接執(zhí)行2,3,4.如果是在微信好友列表時,收到紅包,則會在列表項中顯示[微信紅包],需要點即該列表項,進入聊天界面,隨后執(zhí)行2,3,4.為了方便演示,這里我們暫時不考慮好友列表時出現(xiàn)紅包的情況.

明白了搶紅包流程,之后我們通過AccessibilityService獲取通知欄信息及微信聊天窗口界面,繼而通過模擬點擊實現(xiàn)打開紅包,搶紅包等操作.
AccessibilityService配置如下:

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
                       android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged"
            android:accessibilityFeedbackType="feedbackGeneric"
            android:accessibilityFlags="flagDefault"
            android:canRetrieveWindowContent="true"
            android:notificationTimeout="100"
            android:packageNames="com.tencent.mm" />

具體實現(xiàn)代碼如下:

public class RobService extends AccessibilityService {


    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        switch (eventType) {
            case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
                handleNotification(event);
                break;
            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
            case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
                String className = event.getClassName().toString();
                if (className.equals("com.tencent.mm.ui.LauncherUI")) {
                    getPacket();
                } else if (className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI")) {
                    openPacket();
                } else if (className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI")) {
                    close();
                }

                break;
        }
    }

    /**
     * 處理通知欄信息
     *
     * 如果是微信紅包的提示信息,則模擬點擊
     *
     * @param event
     */
    private void handleNotification(AccessibilityEvent event) {
        List<CharSequence> texts = event.getText();
        if (!texts.isEmpty()) {
            for (CharSequence text : texts) {
                String content = text.toString();
                //如果微信紅包的提示信息,則模擬點擊進入相應(yīng)的聊天窗口
                if (content.contains("[微信紅包]")) {
                    if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) {
                        Notification notification = (Notification) event.getParcelableData();
                        PendingIntent pendingIntent = notification.contentIntent;
                        try {
                            pendingIntent.send();
                        } catch (PendingIntent.CanceledException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    /**
     * 關(guān)閉紅包詳情界面,實現(xiàn)自動返回聊天窗口
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    private void close() {
        AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
        if (nodeInfo != null) {
            //為了演示,直接查看了關(guān)閉按鈕的id
            List<AccessibilityNodeInfo> infos = nodeInfo.findAccessibilityNodeInfosByViewId("@id/ez");
            nodeInfo.recycle();
            for (AccessibilityNodeInfo item : infos) {
                item.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            }
        }
    }

    /**
     * 模擬點擊,拆開紅包
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    private void openPacket() {
        AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
        if (nodeInfo != null) {
            //為了演示,直接查看了紅包控件的id
            List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId("@id/b9m");
            nodeInfo.recycle();
            for (AccessibilityNodeInfo item : list) {
                item.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            }
        }
    }

    /**
     * 模擬點擊,打開搶紅包界面
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void getPacket() {
        AccessibilityNodeInfo rootNode = getRootInActiveWindow();
        AccessibilityNodeInfo node = recycle(rootNode);

        node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
        AccessibilityNodeInfo parent = node.getParent();
        while (parent != null) {
            if (parent.isClickable()) {
                parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                break;
            }
            parent = parent.getParent();
        }

    }

    /**
     * 遞歸查找當(dāng)前聊天窗口中的紅包信息
     *
     * 聊天窗口中的紅包都存在"領(lǐng)取紅包"一詞,因此可根據(jù)該詞查找紅包
     * 
     * @param node
     */
    public AccessibilityNodeInfo recycle(AccessibilityNodeInfo node) {
        if (node.getChildCount() == 0) {
            if (node.getText() != null) {
                if ("領(lǐng)取紅包".equals(node.getText().toString())) {
                    return node;
                }
            }
        } else {
            for (int i = 0; i < node.getChildCount(); i++) {
                if (node.getChild(i) != null) {
                    recycle(node.getChild(i));
                }
            }
        }
        return node;
    }

    @Override
    public void onInterrupt() {

    }

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


}


上面的代碼簡單演示了搶紅包的原理,為了方便起見,我直接通過findAccessibilityNodeInfosByViewId()獲取制定id控件.在實際中,這種方法不太可靠,到目前為止,微信已經(jīng)改過幾次相關(guān)控件的id了.

有童鞋問,怎么樣知道該控件的id呢.其實很簡單,android中已經(jīng)為我們提供了相關(guān)的工具:在Android Studio中開啟Android Device Monitor,選擇設(shè)備后點擊Dump View Hierarchy for UI Automator,如下:


這里寫圖片描述

稍等片刻之后,便會出現(xiàn)當(dāng)前設(shè)備的窗口,在該窗口中點擊相關(guān)控件,便會顯示該控件的屬性.借助該工具,可以幫我們快速的分析界面結(jié)構(gòu),幫助我們從其他app布局策略中學(xué)習(xí)
這里寫圖片描述

我們用Dump View Hierarchy for UI Automator分析聊天界面微信紅包信息:


這里寫圖片描述

搶紅包界面:


這里寫圖片描述
  1. App自動安裝

講完了微信紅包插件的實現(xiàn)原理,不難發(fā)現(xiàn)其本質(zhì)是根據(jù)相關(guān)的界面狀態(tài),模擬后續(xù)的操作(比如點擊等).
既然這樣,那么我們完全可以利用該服務(wù)實現(xiàn)更多的功能,比如apk自動安裝,傳統(tǒng)的安裝過程大概是如下流程:

點擊apk文件,彈出安裝信息界面,在該界面點擊"下一步",然后在點擊"安裝",最后在安裝完成界面點擊"完成".

不難發(fā)現(xiàn),該流程完全可以通過模擬點擊操作完成.現(xiàn)在我們簡單的講一下AccessibilityService在這方面的具體應(yīng)用.

我們知道系統(tǒng)的安裝程序由PackageInstaller負(fù)責(zé),其包名是com.android.packageinstaller,那么我們只需要監(jiān)聽該package下的安裝信息界面和安裝完成界面,并模擬點擊"下一步","安裝",完成""操作即可實現(xiàn)自動安裝.

AccessibilityService配置如下:

<?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:accessibilityFlags="flagDefault"
        android:canRetrieveWindowContent="true"
        android:description="@string/auto_service_des"
        android:notificationTimeout="100"
        android:packageNames="com.android.packageinstaller"/>

具體實現(xiàn)代碼如下:

public class InstallService extends AccessibilityService {
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        Log.d("InstallService", event.toString());
        checkInstall(event);
    }


    private void checkInstall(AccessibilityEvent event) {
        AccessibilityNodeInfo source = event.getSource();
        if (source != null) {
            boolean installPage = event.getPackageName().equals("com.android.packageinstaller");
            if (installPage) {
                installAPK(event);
            }
        }
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void installAPK(AccessibilityEvent event) {
        AccessibilityNodeInfo source = getRootInActiveWindow();
        List<AccessibilityNodeInfo> nextInfos = source.findAccessibilityNodeInfosByText("下一步");
        nextClick(nextInfos);
        List<AccessibilityNodeInfo> installInfos = source.findAccessibilityNodeInfosByText("安裝");
        nextClick(installInfos);
        List<AccessibilityNodeInfo> openInfos = source.findAccessibilityNodeInfosByText("打開");
        nextClick(openInfos);

        runInBack(event);

    }

    private void runInBack(AccessibilityEvent event) {
        event.getSource().performAction(AccessibilityService.GLOBAL_ACTION_BACK);
    }

    private void nextClick(List<AccessibilityNodeInfo> infos) {
        if (infos != null)
            for (AccessibilityNodeInfo info : infos) {
                if (info.isEnabled() && info.isClickable())
                    info.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            }
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    private boolean checkTilte(AccessibilityNodeInfo source) {
        List<AccessibilityNodeInfo> infos = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("@id/app_name");
        for (AccessibilityNodeInfo nodeInfo : infos) {
            if (nodeInfo.getClassName().equals("android.widget.TextView")) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void onInterrupt() {

    }

    @Override
    protected void onServiceConnected() {
        Log.d("InstallService", "auto install apk");
    }
}



  1. 檢測前臺服務(wù):

在很多情況下,我們需要檢測自己的app是不是處在前臺,借助該服務(wù)同樣也能夠完成該檢測操作.下面,我們就演示一下如何實現(xiàn):

AccessibilityService配置如下:

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"                 

        android:accessibilityEventTypes="typeWindowStateChanged"
        android:accessibilityFeedbackType="feedbackGeneric"
        android:accessibilityFlags="flagDefault"
        android:canRetrieveWindowContent="true"
        android:description="@string/auto_detection"
        android:notificationTimeout="100"
                       />

具體實現(xiàn)代碼如下:

public class DetectionService extends AccessibilityService {
    private static volatile String foregroundPackageName = "error";

    /**
     * 檢測是否是前臺服務(wù)
     *
     * @param packagenName
     * @return
     */
    public static boolean isForeground(String packagenName) {
        return foregroundPackageName.equals(packagenName);
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            foregroundPackageName = event.getPackageName().toString();
        }
    }

    @Override
    public void onInterrupt() {

    }
}


在使用時,需要引導(dǎo)用戶開啟該服務(wù),之后通過調(diào)用DetectionService.isForeground()即可.

  1. 竊取信息

上面的所有示例演示的都是AccessibilityService在具體應(yīng)用中發(fā)揮的良好作用.但是該服務(wù)也存在一定的風(fēng)險,很多人利用該服務(wù)做一些"壞事",比如竊取短信驗證碼,竊取短信內(nèi)容,想要看看自己女朋友最近在和誰聊QQ等等.

你現(xiàn)在是不是想能否借助該服務(wù)直接獲取一些app的密碼呢?凡是EditText中設(shè)置inputType為password類型的,都無法獲取其輸入值.除此之外,大多數(shù)軟件都針對該中風(fēng)險做了提前的防范.因此,你想要借助該服務(wù)來實現(xiàn)竊取密碼還是比較有難度的,所以,少年覺悟吧.

總結(jié)

暫時先到這里,后面再補充其他的吧.其實該服務(wù)能做的事情遠不止這些,比如也可以通過該服務(wù)獲取微信公眾號的key,進而爬去文章閱讀數(shù)等,UI自動化測試等.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末渣淳,一起剝皮案震驚了整個濱河市辞州,隨后出現(xiàn)的幾起案子蚊锹,更是在濱河造成了極大的恐慌憋槐,老刑警劉巖猿诸,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸥鹉,死亡現(xiàn)場離奇詭異,居然都是意外死亡蓉坎,警方通過查閱死者的電腦和手機肿孵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門唠粥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人停做,你說我怎么就攤上這事晤愧。” “怎么了蛉腌?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵养涮,是天一觀的道長。 經(jīng)常有香客問我眉抬,道長,這世上最難降的妖魔是什么懈凹? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任蜀变,我火速辦了婚禮,結(jié)果婚禮上介评,老公的妹妹穿的比我還像新娘库北。我一直安慰自己,他們只是感情好们陆,可當(dāng)我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布寒瓦。 她就那樣靜靜地躺著,像睡著了一般坪仇。 火紅的嫁衣襯著肌膚如雪杂腰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天椅文,我揣著相機與錄音喂很,去河邊找鬼。 笑死皆刺,一個胖子當(dāng)著我的面吹牛少辣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播羡蛾,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼漓帅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起忙干,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤器予,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后豪直,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體劣摇,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年弓乙,在試婚紗的時候發(fā)現(xiàn)自己被綠了末融。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡暇韧,死狀恐怖勾习,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情懈玻,我是刑警寧澤巧婶,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站涂乌,受9級特大地震影響艺栈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜湾盒,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一湿右、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧罚勾,春花似錦毅人、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至送丰,卻和暖如春缔俄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蚪战。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工牵现, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人邀桑。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓瞎疼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親壁畸。 傳聞我的和親對象是個殘疾皇子贼急,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,691評論 2 361

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

  • 微信紅包自打出世以來就極其受歡迎,搶紅包插件可謂紅極一時.今天,我們重新談?wù)創(chuàng)尲t包插件的哪些事兒.本質(zhì)上,搶紅包插...
    于加澤閱讀 1,884評論 0 6
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,309評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理茅茂,服務(wù)發(fā)現(xiàn),斷路器太抓,智...
    卡卡羅2017閱讀 134,711評論 18 139
  • 生活處處有美景空闲,只是你沒有發(fā)現(xiàn)美的眼睛。當(dāng)我隨手拍攝每一處風(fēng)景時走敌,才感嘆每次我們匆匆忙忙路過的地方竟然如此美麗!
    讀書聲gly閱讀 385評論 0 1
  • 親愛的路人碴倾,你是否安好? 感謝你路過我的身邊掉丽,也祝福你一切跌榔,或許曾經(jīng)的遇見是錯誤的,感謝緣分也讓你遇見對的捶障,希望你...
    eunice薇薇利閱讀 272評論 0 0