Android AccessibilityService --- 小白視角

>>長路漫漫立志行高遠,寒夜漆漆心勇敢獨行。 --致青春

前言:

朋友的一個奇思妙想,促使我接觸了 AccessibilityService ,這是個什麼東西呢,我們看看申請權限時系統怎麼提醒的

權限申請圖

意思吧,很清楚,就是這項服務開啓之後,您的屏幕操作動作,甚至是操作的屏幕上的視圖的內容,這貨都能獲取到,發揮一下想象力,可以搞事情!(微信自動搶紅包等自動化軟件就是用的這貨)。

 

正文:

STEP 1    Android studio 新建項目

首先,鑑於此功能一般有開發經驗的人員纔會想到或者用到,所以,android studio 那一堆新建項目之類的就直接跳過了。

STEP 2    項目配置

1、新建服務。比如:

public class AccessibilityServiceHelper extends AccessibilityService {

    private static final String TAG = "AccessibilityServiceHelper";


    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        try {
            if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
                
            } else if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
                
            } else if (event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
                
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

   


    @Override
    public void onInterrupt() {

    }
}

根據本人測試的經驗解釋一下以上監聽的三種狀態(可監聽的狀態不止這三種,我這裏只用到了這三種,具體可看AccessibilityEvent 類下的各個狀態,基本上見名知意)

  •  event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:這種狀態是頁面切換纔會觸發,我測試出來的是,必須是Activity切換纔會觸發,如果Activity中包含ViewPager,ViewPager中是Fragment,那麼切換Fragment是不會觸發此狀態的。
  • event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:這種狀態是監聽窗口內容變化,看名字意思是窗口內容變化纔會觸發,但是我這裏實測的是即使沒看到變化也會觸發,不知道是不是有肉眼看不到的變化。
  • event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:通知狀態變化,實測是當有彈窗和有通知欄消息時都會觸發。

 

2、新建xml配置。app--res 下新建 xml 文件夾,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="feedbackGeneric"
    android:canRetrieveWindowContent="true"
    android:description="@string/accessibility_service_description"
    android:notificationTimeout="100"
    android:packageNames="com.xxx.xxx"/>

<!--accessibilityEventTypes:表示該服務對界面中的哪些變化感興趣,即哪些事件通知,比如窗口打開,
滑動,焦點變化,長按等.具體的值可以在AccessibilityEvent類中查到,如typeAllMask表示接受所有的事
件通知.-->

<!--accessibilityFeedbackType:表示反饋方式,比如是語音播放,還是震動-->

<!--canRetrieveWindowContent:表示該服務能否訪問活動窗口中的內容.也就是如果你希望在服務中獲取
窗體內容的化,則需要設置其值爲true.-->

<!--notificationTimeout:接受事件的時間間隔,通常將其設置爲100即可.-->

<!--packageNames:表示對該服務是用來監聽哪個包的產生的事件-->

各個屬性註釋都有做解釋。(此文件的配置也可在 AccessibilityServiceHelper  的 onServiceConnected 進行配置)

 

3、註冊服務、配置服務。(AndroidManifest.xml 中註冊服務就不用我說了吧)  示例內容如下:

<service
    android:name="com.xxx.xxx.AccessibilityServiceHelper"
    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/accessibility_service_config"/>

</service>

CSDN這個代碼編輯器太不智能了,都不會縮進,還得我一行一行調,心累。

注意:meta-data部分,android:resource 指向的就是上面 第 2 小步中的xml文件。

 

STEP 3    正主登場

1、判斷當前服務開啓狀態。

/**
* 檢測輔助功能是否開啓<br>
*/
public static boolean isAccessibilitySettingsOn(Context mContext) {
    int accessibilityEnabled = 0;
    final String service = mContext.getPackageName() + "/" + 替換爲自定義的服務類名.class.getCanonicalName();
    try {
        accessibilityEnabled = Settings.Secure.getInt(mContext.getApplicationContext().getContentResolver(),
                    android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
        } catch (Settings.SettingNotFoundException e) {
            
        }
    TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');

    if (accessibilityEnabled == 1) {
        String settingValue = Settings.Secure.getString(mContext.getApplicationContext().getContentResolver(),
                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
        if (settingValue != null) {
            mStringColonSplitter.setString(settingValue);
            while (mStringColonSplitter.hasNext()) {
                String accessibilityService = mStringColonSplitter.next();
                if (accessibilityService.equalsIgnoreCase(service)) {
                    return true;
                }
            }
        }
    } else {
        Log.v(TAG, "***ACCESSIBILITY IS DISABLED***");
    }
    return false;
}

 

2、跳轉至設置也開啓服務。

Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);

 

3、獲取屏幕根節點。

在 AccessibilityServiceHelper 中的

@Override
public void onAccessibilityEvent(AccessibilityEvent event){
    
}

可以通過如下代碼獲取當前屏幕根節點:

AccessibilityNodeInfo accessibilityNodeInfo = accessibilityService.getRootInActiveWindow();

TIP 1: AccessibilityNodeInfo 有以下幾個方法,用於組件的查找:

//通過屏幕顯示的內容,比如:下一步、完成等字眼,獲取包含該文本的所有節點
List<AccessibilityNodeInfo> nodeInfoList = rootNode.findAccessibilityNodeInfosByText(text);

//通過屏幕顯示的view組件的id查找節點,不過前提是要知道該view的節點id,可以通過android SDK -- tools -- monitor.bat 啓動 Android Device Monitor 通過捕獲屏幕組件獲取各個組件的信息,不過這傢伙太TM不好用了,點100次也不一定能成功一次,所以建議在 AccessiblityServicerHelper 中遍歷根節點,找到你要找的組件的節點,然後查看其信息,進行下一步操作
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);

有以下幾個方法,用於組件的操作:

//對當前nodeInfo執行單擊操作
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);

//對當前nodeInfo執行長按操作
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);

//對當前nodeInfo執行復制操作
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_COPY);

//對當前nodeInfo執行剪切操作
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CUT);

//對當前nodeInfo執行粘貼操作
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_PASTE);

僅列出常用的幾個,在 AccessibilityNodeInfo 中也可以找到更多操作指令。

 

TIP 2: AccessibilityService 也有一個本人比較常用的方法,用於執行全局返回操作:

//執行全局返回操作
service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);

AccessibilityService 中也可以找到更多操作指令。

 

4、通知內容的捕獲,就是 event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED 時獲取的內容 。

//捕獲的信息都在集合裏了,斷點看一下就知道都有啥了,後續內容處理不再贅述
List<CharSequence> text = event.getText();

 

5、獲取 WebView 內容。

@Override
protected void onServiceConnected() {
    super.onServiceConnected();
    AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo();
    serviceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
    serviceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
    serviceInfo.packageNames = new String[]{"com.unionpay"};// 監控的app
    serviceInfo.notificationTimeout = 100;
    serviceInfo.flags = serviceInfo.flags | AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY;
    setServiceInfo(serviceInfo);
}

重點:serviceInfo.flags = serviceInfo.flags | AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY;

這個設置可以讓 AccessibilityService 能捕獲到WebView,只有捕獲到了才能拿裏面的內容。

其他設置可以保持和 accessibility_service_config.xml 中的各項設置一致,不設置好像會讓 accessibility_service_config.xml 對應屬性失效,建議設置一遍。

 

下面是對獲取到的WebView節點的實際操作:

//這裏我是根據頁面標題定位獲取的,因爲我不知道WebView的相關信息,但是我遍歷根節點時拿到了頁面標題的信息,然後通過節點的 getParent()方法往上找其父節點,一直找到頁面根節點,然後再往下找各個子節點,直到找到需要的那個WebView節點
AccessibilityNodeInfo text = AccessibilityUtils.findViewId(this, "com.xxx.xxx:id/tv_title").get(0);

//找到父節點爲一個FrameLayout
AccessibilityNodeInfo framelayout = text.getParent();

//發現FrameLayout下的第三個字節點爲WebView,但此WebView節點的子節點並不是我要的內容,繼續往下找
AccessibilityNodeInfo webview = framelayout.getChild(2);

//又是一個WebView,仍不是目標view
AccessibilityNodeInfo textwebview = webview.getChild(0);

//繼續深入找
AccessibilityNodeInfo textwebview1 = textwebview.getChild(0);

//終於不是WebView了,而是一個View結合,其下有很多子view,而子view的信息正是我要找的
AccessibilityNodeInfo views = textwebview1.getChild(0);

//獲取子view的內容
String content = views.getChild(0).getContentDescription().toString();
                            

註釋已很詳細,每個要監聽的app頁面內容都是不一樣的,這裏只是一個示例,不要死板的複製,這裏只是一個查找思路和方法。

 

至此,AccessibilityService 的相關操作就結束了,可能還有沒有說到的地方,但,我相信上面所講的已經能滿足大部分需求了。

也算是嘔心瀝血了,認真看完必有收穫。

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