Android 源碼系列之從源碼的角度深入理解AccessibilityService,打造自己的APP小外掛(上)

 

2016年10月24日 17:55:03

        轉載請註明出處:http://blog.csdn.net/llew2011/article/details/52822148

        說起外掛特別是玩遊戲的小夥伴估計對它很熟悉,肯定有部分小夥伴使用過,至於爲什麼使用它,你懂得(*^__^*) ……我最早接觸外掛是在大二的時候,那時候盛行玩QQ農場,早上一睜眼就是打開電腦先把自己的菜收了,收完之後再去偷別人的;後來童靴說非凡軟件上有一個偷菜外掛,於是趕緊整了一個,有了外掛之後就告別了體力時代,省時又省力……既然在PC上有外掛,那在智能手機上可以做外掛呢?答案是OK的,今天這篇文章就是講解一下如何在Android設備上製作自己的小外掛,需要說明的是本文僅僅做技術交流……

        產生做外掛的念頭是在去年春節時支付寶推的咻一咻咻大獎活動,那時候每到咻一咻的時間點就趕緊打開支付寶進入咻一咻頁面然後不停的點擊咻一咻按鈕,後來我就想與其這樣一直重複點擊按鈕不如花點時間整個咻一咻小外掛,於是花了小半天時間寫了一個,經過實踐發現效果還挺理想的……其實在Android設備上製作小外掛並不是多麼高深的技術,核心就是利用AccessibilityService,如果你對該類已經很熟悉,請跳過本文(*^__^*) ……

        AccessibilityService是Google爲了方便那些身體不便的用戶來使用Android設備而提供的一種無障礙服務,該服務可以幫助那些身體不便的用戶更加簡單的使用和操作Android設備,這些操作包括文字轉語音,觸覺反饋,收拾操作,軌跡球和手柄操作等。AccessibilityService提供的這種服務就是用來監聽指定的應用的,例如監聽指定應用頁面內容的邊界,頁面的跳轉,焦點的變化等等。因此我們可以利用該服務做我們想做的小外掛,比如自動安裝APP,搶紅包外掛還有我之前寫的咻一咻外掛,今天我們就講解一下如何利用AccessibilityService來實現自動安裝APP的小外掛。

        AccessibilityService是Service的子類,但是它的聲明週期是由系統來管理的,那也就是說我們要想啓動該服務就不能夠像平時那樣直接startService()了而是需要在Android設備的輔助功能列表中手動開啓該服務,當開啓該服務後其生命週期就交由系統來管理和維護了。需要注意的是雖然不需要通過startService()等方式來啓動AccessibilityService服務,但是AccessibilityService依然是需要在配置文件AndroidManifest.xml中配置。由於AccessibilityService是抽象類不能直接使用,所以需要先自定義一個類來繼承AccessibilityService,自定義AutoInstallApkService服務類代碼如下:  

public class AutoInstallApkService extends AccessibilityService {
 
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        // 事件入口處
    }
 
    @Override
    public void onInterrupt() {
    }
}

 

AutoInstallApkService重寫了AccessibilityServiced的倆抽象方法,onAccessibilityEvent()方法表示該服務接收系統傳遞進來的輔助事件(該事件可能是當前窗口內容發生變化觸發的,也可能是當前窗口焦點發生變化觸發的,還有可能是系統彈出Notification觸發的等等),該方法爲事件入口,每當監聽的指定應用觸發了指定事件的時候都會回調此方法。而onInterrupt()方法表示服務中斷髮生的回調,服務中斷意味着不能接收回調了,但是可以在方法中做些相關業務等操作。     

  定義完了我們的AutoInstallApkService服務後,接下就是在AndroidManifest.xml文件中配置該服務了,根據官方文檔,配置文件如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.llew.wb.project.service.accessibility.installapk">
 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ui.activity.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
 
        <service
            android:name=".ui.service.AutoInstallApkService"
            android:label="@string/app_name"
            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_config" />
        </service>
    </application>
 
</manifest>

 

在配置文件中配置了我們的AutoInstallApkService服務,配置服務的時候需要注意以下幾點:

  1. 添加label標籤
            AutoInstallApkService需要添加label標籤,標籤表示服務的名字,應用安裝後會在手機輔助功能的列表中顯示,若沒定義標籤則不顯示
  2. 添加系統權限
            系統權限android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"是一定要添加的,否則該服務會被系統忽略
  3. 添加過濾器
            一定要添加intent爲的android:name="android.accessibilityservice.AccessibilityService"過濾器,否則該服務會被系統忽略
  4. meta-data配置文件
            meta-data中android:name表示配置的服務名稱,值是固定寫法不能修改,android:resource表示引用的具體配置文件,本例引用的是accessibility_service_config.xml文件【注意:此配置是在4.0版本之後的寫法,在低版本中可使用另一種寫法,稍後會有講解】

        看完了manifest配置文件後,我們看一下在res目錄下的xml文件夾中的accessibility_service_config.xml文件,代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="true"
    android:description="@string/app_name"
    android:notificationTimeout="100"
    android:packageNames="com.android.packageinstaller" />

        在accessibility_service_config.xml中根元素爲accessibility-service爲,這是固定寫法不能更改;各屬性值的說明如下:

  • android:accessibilityEventTypes
            該屬性表示當前AutoInstallApkService服務接收的事件類型,typeAllMask表示接收所有的事件類型,常見的事件有:typeWindowContentChanged(窗口內容發生變化的事件),typeWindowStateChanged(窗口焦點發生變化的事件),typeNotificationStateChanged(彈出Notification的事件)等等,如果想了解更多的輔助事件可參閱官方文檔
  • android:accessibilityFeedbackType
            該屬性表示設置反饋給用戶的方式,常見的有語音播報,手機振動等
  • android:canRetrieveWindowContent
            該屬性表示是否可以獲取當前窗口內容,true表示可以獲取否則不可以獲取
  • android:description
            表示對當前輔助功能的描述,該值會在Android設置的輔助列表中顯示
  • android:notificationTimeout
            表示響應時間,目前設置爲100毫秒
  • android:packageNames
            表示當前輔助服務需要監聽的應用包名,目前我們是實現自動安裝外掛,而安裝應用是調用系統的Installer應用,該應用的包名爲com.android.packageinstaller,如果想要監聽多個應用,中間加逗號。

        好了,配置完了我們的AutoInstallApkService服務後,接下來是實現具體的安裝APK的邏輯了。我們知道在Android設備上安裝應用的時候會彈出一個安裝確認頁面,只有確認後安裝纔會繼續執行……這其實是調用系統默認的PackageInstaller安裝器(需要指出的是這個APK是第三方的應用,系統應用的安裝可以不通過PackageInstaller來安裝)。在PackageInstaller安裝界面的操作流程一般是:是否允許安裝 → 正在進行安裝 → 安裝完成確認;這三個安裝流程的界面是不一樣的,拿中國移動的APK來舉個栗子,如下圖所示:

      

        上圖分別展示了在Android手機上安裝APP應用時調用系統安裝器PackageInstaller不同狀態時的樣式,我們的自動安裝應用小外掛就是當出現了這以上頁面中的任一個時我們該外觀都能自動來執行安裝流程,既然是自動安裝也就是說當出現了這些按鈕時我們的外掛能主動的執行按鈕的點擊操作,這樣就省去了人爲的手動點擊操作,這也是外掛的核心作用。由於PackageInstaller的頁面發生了變化都會回調AutoInstallApkService的onAccessibilityEvent()方法,因此我們可以在該方法中來模擬用戶的操作,要模擬點擊操作就要得到對應的按鈕,然後執行按鈕的點擊事件;那怎麼樣才能得到目標按鈕這個對象呢?在AccessibilityService中提供了一個getRootInActiveWindow()方法,該方法返回一個代表當前活動窗口的根節點AccessibilityNodeInfo實例對象,該對象保存了當前窗口界面的相關信息,比如控件在窗口的位置信息,id信息,文本信息,類型信息,文本信息等等,它和ViewGroup類似,對外提供了諸如findAccessibilityNodeInfosByViewId(),findAccessibilityNodeInfosByText(),performAction()等方法。其中findAccessibilityNodeInfosByViewId()是4.3版本之後的新增方法,表示根據給定控件的ID來獲取到對應控件,獲取到對應控件後就可以通過performAction()方法來執行點擊事件了,那怎麼獲取到指定控件的ID呢?

        在Android的sdk目錄中有個tools目錄,在該目錄下有個uiautomatorviewer工具,該工具很有用,特別是分析apk的頁面佈局信息,它可以獲取到當前手機屏幕上的界面信息,如下圖所示:

        在上圖中我們通過uiautomatorviewer工具展示了安裝APP的界面信息。左側表示截屏信息,當點擊界面上的相關控件的時候,右側就會出現該控件的相關信息,比如id,text,package,class,clickable等,而這個ID就是我們想要的id,又因爲在一個頁面上id是唯一的,所以只要我們獲取到了所有的符合條件的ID後就可以通過該id獲取到對應的AccessibilityNodeInfo對象了,然後通過調用該對象的performAction()方法就可以實現自動點擊效果了。通過uiautomatorviewer工具找到所有操作按鈕的id後,就可以在我們的AutoInstallApkService的onAccessibilityEvent()方法中做操作了,代碼如下:

public class AutoInstallApkService extends AccessibilityService {
 
    private static final String DEFAULT_PACKAGE_NAME = "com.android.packageinstaller";
    
    private static final String[] IDS = {
            "com.android.packageinstaller:id/ok_button",        // 下一步按鈕的ID,注意ID的格式,必須這樣寫
            "com.android.packageinstaller:id/done_button",      // 完成按鈕的ID,注意ID的格式,必須這樣寫
            "com.android.packageinstaller:id/confirm_button"    // 確認按鈕的ID,注意ID的格式,必須這樣寫
    };
 
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (null == event) return;
        installApkIfNecessary(event);
        recycleAccessibilityEvent(event);
    }
 
    private void installApkIfNecessary(AccessibilityEvent event) {
        AccessibilityNodeInfo rootInfo = getRootInActiveWindow();
        if (null == rootInfo) return;
        String packageName = rootInfo.getPackageName().toString();
        if (DEFAULT_PACKAGE_NAME.equals(packageName)) {
            int length = IDS.length;
            AccessibilityNodeInfo availableNode = null;
            for (int i = 0; i < length; i++) {
                availableNode = findAvailableNodeInfoByViewId(rootInfo, IDS[i]);
                if (null != availableNode) {
                    break;
                }
            }
            if (null != availableNode) {
                performClickWithAccessibilityNode(availableNode);
            }
        }
    }
 
    private AccessibilityNodeInfo findAvailableNodeInfoByViewId(AccessibilityNodeInfo root, String id) {
        List<AccessibilityNodeInfo> availableNodes = root.findAccessibilityNodeInfosByViewId(id);
        if (null == availableNodes || availableNodes.isEmpty()) {
            return null;
        }
        return availableNodes.get(0);
    }
 
    private void performClickWithAccessibilityNode(AccessibilityNodeInfo nodeInfo) {
        if (null != nodeInfo) {
            if (nodeInfo.isClickable()) {
                nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            } else {
                performClickWithAccessibilityNode(nodeInfo.getParent());
            }
        }
    }
 
    @Override
    public void onInterrupt() {
    }
 
    private void recycleAccessibilityEvent(AccessibilityEvent event) {
        if (null != event) {
            event.recycle();
            event = null;
        }
    }
}

        以上就是我們AutoInstallApkService的全部代碼,相信小夥伴們都看的懂,DEFAULT_PACKAGE_NAME表示監聽應用的包名,IDS表示需要點擊的所有的ID控件集合,在onAccessibilityEvent()方法中我們首先判斷傳遞進來的event是否非空,如果非空就執行installApkIfNecessary()方法,在該方法中先獲取根節點rootInfo,然後循環IDS通過調用rootInfo的findAccessibilityNodeInfosByViewId()方法找到對應的控件節點,如果找到了對應節點就調用performClickWithAccessibilityNode()方法來執行點擊動作,需要注意的是在performClickWithAccessibilityNode()方法中如果當前控件不可點擊我們就遞歸調用找其父控件來執行點擊事件,只所以這麼做是爲了避免爲了擴大點擊面積我們往往在當前控件外嵌套一個父佈局然後使父佈局來響應點擊事件的情況存在(因爲之前在做搶紅包外掛時碰見這種情況)。

        好了,現在我們的自動安裝APP的外掛已經完成,接下來運行在手機上後要在手機的輔助功能列表中開啓該服務,否則該服務不起作用,操作截圖如下:

        開啓了AutoInstallApkService服務後,就可以在手機上嘗試安裝一個APP看看效果了。因爲我們的AutoInstallApkService是監聽的系統的PackageInstaller應用,所以只要我們點擊了已經下載過的APP後都是可以調用此應用的,直接點擊一個應用,效果如下:

        根據截圖效果來看,安裝APP時的操作流程都可以自動完成,運行結果也達到了我們的預期,這就是在手機上做的一個小外掛,是不是很簡單?(*^__^*) ……其實外掛聽起來很高大上,但只要瞭解了它的核心思想,做一個小外掛出來還是很容易的(就像當時我寫了一個咻一咻外掛),現在想想當年的偷菜外掛猜測應該也是藉助了系統的輔助功能來實現的吧。

        好了,到這裏有關自動安裝APP的小外掛實驗已經結果了,在下篇文章Android 源碼系列之<十一>從源碼的角度深入理解AccessibilityService,打造自己的APP小外掛(下)中,我將帶領小夥伴們們從源碼出發深入理解一下AccessibilityService的執行原理,敬請期待!!!最後感謝觀看(*^__^*) ……

 

教你如何自己寫一個微信小遊戲「跳一跳」外掛

http://blog.csdn.net/OQjya206rsQ71/article/details/78970088其實也不能說算是外掛吧,算是個遊戲小助手吧,畢竟不能抓包,也不能直接修改分數(據說...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章