AccessiblityService知多少

最近研究了自動化操做的相關事宜,輔助服務就是其中一項技術。下面介紹一下相關方面技術。這項技術能夠用做搶紅包、App自動安裝卸載、頁面內容抓取,WX消息的自動發送、自動發送朋友圈,H5頁面內容抓取也能夠。php

原理

對於那些因爲視力、聽力或其它身體緣由致使不能方便使用Android智能手機的用戶,Android提供了Accessibility功能和服務輔助這些用戶更加簡單地操做設備,包括文字轉語音(不支持中文)、觸覺反饋、手勢操做、軌跡球和手柄操做。開發者能夠搭建本身的Accessibility服務,這能夠增強可用性,例如聲音提示,物理反饋,和其餘可選的操做模式。java

AccessibilityService是一個運行在後臺的服務,有相關回調方法onAccessibilityEvent(AccessibilityEvent event)可以收到由系統發出的一些事件。界面中產生的任何變化都會產生一個事件,並由系統通知給AccessibilityService這就像監視器監視着界面的一舉一動,一旦界面發生變化,馬上進行相關操做。node

實現

Api 4就已經加入了這項技術,但Android 4.1算是一個分水嶺,此版本之後,系統輔助服務增長了與窗口元素的雙向交互,能夠經過輔助功能服務操做窗口元素,好比點擊按鈕等android

AccessibilityService 實現

import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;

public class AutoAccessibilityService extends AccessibilityService {

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        //當咱們當服務被系統服務連接成功後,這裏能夠作一些初始化當工做
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        int eventType = accessibilityEvent.getEventType();  
        //事件監聽回調,開發者能夠作不少事情
        switch (eventType) {
            //當窗口的狀態發生改變時
            case AccessibilityEvent.TYPE_WINDOWS_CHANGED://窗口變化
                break;
            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED://對話框、popupwindow、菜單
                 break;
            case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED://通知
                 break;
            case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED://基於當前event.source中子view的內容變化
                 break;
                .....
                .....
        }
    }

    @Override
    public void onInterrupt() {
       //這個在系統想要中斷AccessibilityService返給的響應時會調用。在整個生命週期裏會被調用屢次。
    }
    
    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
        //在系統將要關閉Service時被調用。在這個方法主要作釋放資源的工做。
    }
}
複製代碼

AndroidMenifest.xml

<service android:name=".AutoAccessibilityService" android:enabled="true" android:exported="true" android:canRetrieveWindowContent="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_config" />
</service>
複製代碼

res/xml/accessibility_service_config

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged|typeWindowsChanged" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagDefault|flagRequestEnhancedWebAccessibility" android:canRetrieveWindowContent="true" android:description="@string/app_name" android:notificationTimeout="100" android:packageNames="com.weibo"//這是咱們要監聽的包名 android:canRequestEnhancedWebAccessibility="true" />
複製代碼

服務狀態監控回調

added API level 14微信

AccessibilityManager accessibilityManager = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
accessibilityManager.addAccessibilityStateChangeListener(new AccessibilityManager.AccessibilityStateChangeListener() {
        @Override
        public void onAccessibilityStateChanged(boolean isChange) {
            Log.i(TAG,"onAccessibilityStateChanged isChange = " + isChange);
            if(isChange){
                 //do something
            }else{
                //do something
            }
        }
    });
複製代碼

以上就是最基本的實現,可是這個輔助服務須要用戶手動開啓受權才能用,固然某些黑科技下也是能夠不用手動開啓的,好比,電腦經過adb命令來控制已經root的手機。呵呵~app

舉例

搶紅包的插件、微信消息的自動發送、自動發送朋友圈網上不少了,就不說了。下面說一下像H5頁面的內容怎麼抓取,直接上代碼:ide

一、遍歷當前頁面每一個節點工具

AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();//獲取當前頁面的跟頁面的節點
for (int i = 0; i < accessibilityNodeInfo.getChildCount(); i++) {
   AccessibilityNodeInfo child = accessibilityNodeInfo.getChild(i);
   findEveryViewNode(child);
}
複製代碼

二、找出每一個節點的子節點的信息源碼分析

public void findEveryViewNode(AccessibilityNodeInfo node) {
        if (null != node && node.getChildCount() > 0) {
            for (int i = 0; i < node.getChildCount(); i++) {
                AccessibilityNodeInfo child = node.getChild(i);
                if (child == null) {
                    continue;
                }
                Log.i(TAG, "節點數據 ======= " +
                        ",text = " + child.getText()
                        + ", descript = " + child.getContentDescription() +
                        ", className = " + child.getClassName() +
                        ", resId = " + child.getViewIdResourceName());
                // 遞歸調用
                findEveryViewNode(child);
            }
        }
    }
複製代碼

這裏須要注意的是,某些狀況下,返回的節點信息會是null。this

有了神操做,在自動化的路上,無往不利。

方法舉例

/** * 模擬點擊事件 */
public void performViewClick(AccessibilityNodeInfo nodeInfo) {
    if (nodeInfo == null) {
        return;
    }
    while (nodeInfo != null) {
        if (nodeInfo.isClickable()) {
            nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            break;
        }
        nodeInfo = nodeInfo.getParent();
    }
}

/** * 模擬返回操做 */
public void performBackClick() {
    performGlobalAction(GLOBAL_ACTION_BACK);
}

/** * 模擬Home操做 */
public void performHomeClick() {
    performGlobalAction(GLOBAL_ACTION_HOME);
}

/** * 模擬下滑操做 */
public void performScrollBackward() {
    performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
}

/** * 模擬上滑操做 */
public void performScrollForward() {
    performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
}
複製代碼

方法還有不少這裏就不一一列舉。

源碼分析

以點擊事件爲例,AccessibilityService是如何作到監控捕捉用戶行爲的

一、AccessibilityEvent產生:

View#performClick()
    -> View#sendAccessibilityEventUncheckedInternal()
    -> ViewGroup#requestSendAccessibilityEvent()
    -> ViewRootImpl#requestSendAccessibilityEvent()
    -> AccessibilityManager#sendAccessibilityEvent(event)
    -> AccessibilityManagerService#sendAccessibilityEvent()
    -> AccessibilityManagerService#notifyAccessibilityServicesDelayedLocked()
    -> Service#notifyAccessibilityEvent(event)
複製代碼

二、AccessibilityEvent處理:

AccessibilityEvent
    -> Binder
    -> IAccessibilityServiceClientWrapper#onAccessibilityEvent(AccessibilityEvent)
    -> HandlerCaller#sendMessage(message); // message中包括AccessibilityEvent
    -> IAccessibilityServiceClientWrapper#executeMessage()
    -> Callbacks#onAccessibilityEvent(event)
    -> AccessibilityService.this.onAccessibilityEvent(event)
複製代碼

總結

在研究這些技術時,深感系統的良心大做,用來給使用手機困難的人使用,同時也提供了使用手機方便的人更加方便。

開發人員的困難點就是對目標程序的研究,分析出各個頁面以及節點的信息,並對目的操做流程細化抽象,最終完成目標。

工具推薦:UI Automator Viewer分析頁面元素

關注微信公衆號,最新技術乾貨實時推送

image
相關文章
相關標籤/搜索