AccessibilityService使用詳解

前奏:

在你的手機更多設置或者高級設置中,我們會發現有個無障礙的功能,很多人不知道這個功能具體是干嘛的,其實這個功能是為了增強用戶界面以幫助殘障人士,或者可能暫時無法與設備充分交互的人們


image.png

它的具體實現是通過AccessibilityService服務運行在后臺中,通過AccessibilityEvent接收指定事件的回調。
例如:焦點改變了,一個按鈕被點擊,等等。這樣的服務可以選擇請求活動窗口的內容的能力。簡單的說AccessibilityService就是一個后臺監控服務,當你監控的內容發生改變時,就會調用后臺服務的回調方法

AccessibilityService使用

1.1創建服務類

編寫自己的Service類,重寫onServiceConnected()方法、onAccessibilityEvent()方法和onInterrupt()方法

/**
 * Created by zxn on 2019/4/29.
 */
public class PlugAccessibilityService extends AccessibilityService {

    /**
     * 當啟動服務的時候就會被調用,系統成功綁定該服務時被觸發,也就是當你在設置中開啟相應的服務,
     * 系統成功的綁定了該服務時會觸發,通常我們可以在這里做一些初始化操作
     */
    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
    }

    /**
     * 通過系統監聽窗口變化的回調,sendAccessibiliyEvent()不斷的發送AccessibilityEvent到此處
     *
     * @param event
     */
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        //根據時間回調類型進行處理.
    }

    /**
     * 中斷服務時的回調.
     */
    @Override
    public void onInterrupt() {

    }

    /**
     * 查找擁有特定焦點類型的控件
     *
     * @param focus
     * @return
     */
    @Override
    public AccessibilityNodeInfo findFocus(int focus) {
        return super.findFocus(focus);
    }

    /**
     * 如果配置能夠獲取窗口內容,則會返回當前活動窗口的根結點
     *
     * @return
     */
    @Override
    public AccessibilityNodeInfo getRootInActiveWindow() {
        return super.getRootInActiveWindow();
    }


    /**
     * 獲取系統服務
     *
     * @param name
     * @return
     */
    @Override
    public Object getSystemService(String name) {
        return super.getSystemService(name);
    }

    /**
     * 如果允許服務監聽按鍵操作,該方法是按鍵事件的回調,需要注意,這個過程發生了系統處理按鍵事件之前
     *
     * @param event
     * @return
     */
    @Override
    protected boolean onKeyEvent(KeyEvent event) {
        return super.onKeyEvent(event);
    }
}

1.1.1AccessibilityService中常用的方法的介紹

  • findFocus(int focus)
    查找擁有特定焦點類型的控件

  • getRootInActiveWindow()
    如果配置能夠獲取窗口內容,則會返回當前活動窗口的根結點

  • final void disableSelf()
    禁用當前服務,也就是在服務可以通過該方法停止運行

  • final AccessibilityServiceInfo getServiceInfo()
    獲取當前服務的配置信息

  • onAccessibilityEvent(AccessibilityEvent event)
    有關AccessibilityEvent事件的回調函數,系統通過sendAccessibiliyEvent()不斷的發送AccessibilityEvent到此處

  • performGlobalAction(int action)
    執行全局操作,比如返回,回到主頁,打開最近等操作

  • setServiceInfo(AccessibilityServiceInfo info)
    設置當前服務的配置信息

  • getSystemService(String name)
    獲取系統服務

  • onKeyEvent(KeyEvent event)
    如果允許服務監聽按鍵操作,該方法是按鍵事件的回調,需要注意,這個過程發生了系統處理按鍵事件之前

  • onServiceConnected()
    系統成功綁定該服務時被觸發,也就是當你在設置中開啟相應的服務,系統成功的綁定了該服務時會觸發,通常我們可以在這里做一些初始化操作

  • onInterrupt()
    服務中斷時的回調

1.2 聲明服務

在manifests中配置該服務信息

<service
    android:name=".PlugAccessibilityService"
    android:enabled="true"
    android:exported="true"
    android:label="@string/plug_name"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
    </intent-filter>
</service>

注意:任何一個信息配置錯誤,都會使該服務無反應

  • android:label:在無障礙列表中顯示該服務的名字


    image.png
  • android:permission:需要指定BIND_ACCESSIBILITY_SERVICE權限,這是4.0以上的系統要求的
  • intent-filter:這個name是固定不變的

1.3 配置服務參數

配置服務參數是指:配置用來接受指定類型的事件,監聽指定package,檢索窗口內容,獲取事件類型的時間等等。其配置服務參數有兩種方法:

  • 方法一:安卓4.0之后可以通過meta-data標簽指定xml文件進行配置

  • 方法二:通過代碼動態配置參數

1.3.1 方法一

在原先的manifests中增加meta-data標簽指定xml文件

<service
    android:name=".PlugAccessibilityService"
    android:enabled="true"
    android:exported="true"
    android:label="@string/plug_name"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
    </intent-filter>

    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/accessibility_service_config"
        />
</service>

接下來是accessibility_service_config文件的配置

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:accessibilityFlags="flagRequestFilterKeyEvents"
    android:canRequestFilterKeyEvents="true"
    android:canRetrieveWindowContent="true"
    android:notificationTimeout="100" />

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

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

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

description:對該無障礙功能的描述,具體體現在下圖
notificationTimeout:接受事件的時間間隔,通常將其設置為100即可

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

1.3.2 方法二

通過代碼為我們的AccessibilityService配置AccessibilityServiceInfo信息

public void setAccessibilityServiceInfo() {
    String[] packageNames = {"com.tencent.mm"};
    AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
    //相應時間的類型,(長安,點擊,滑動)
    accessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
    //反饋給用戶的類型,這里是語音
    accessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;

    //過濾的包名
    accessibilityServiceInfo.packageNames = packageNames;
    setServiceInfo(accessibilityServiceInfo);
}

在這里涉及到了AccessibilityServiceInfo類,AccessibilityServiceInfo類被用于配置AccessibilityService信息,該類中包含了大量用于配置的常量字段及用來xml屬性,常見的有:accessibilityEventTypes,canRequestFilterKeyEvents,packageNames等等

1.4 啟動服務

這里我們需要在無障礙功能里面手動打開該項功能,否則無法繼續進行,通過

下面代碼可以打開系統的無障礙功能列表

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

1.5 處理事件信息

由于我們監聽了事件的通知欄和界面等信息,當我們指定packageNames的通知欄或者界面發生變化時,會通過onAccessibilityEvent回調我們的事件,接著進行事件的處理

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    int eventType = event.getEventType();
    //根據時間回調類型進行處理.
    switch (eventType) {
        //通知欄變化時
        case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
            break;
        case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
            //當窗口狀態發生改變時.
            break;
    }
}

當我們微信收到通知時,狀態欄會有一條推送信息到達,這個時候就會被TYPE_NOTIFICATION_STATE_CHANGED監聽,執行里面的內容,當我們切換微信界面時,或者使用微信時,這個時候就會被TYPE_WINDOW_STATE_CHANGED監聽,執行里面的內容

AccessibilityEvent的方法

getEventType():事件類型

getSource():獲取事件源對應的結點信息

getClassName():獲取事件源對應類的類型,比如點擊事件是有某個Button產生的,那么此時獲取的就是Button的完整類名

getText():獲取事件源的文本信息,比如事件是有TextView發出的,此時獲取的就是TextView的text屬性。如果該事件源是樹結構,那么此時獲取的是這個樹上所有具有text屬性的值的集合

isEnabled():事件源(對應的界面控件)是否處在可用狀態

getItemCount():如果事件源是樹結構,將返回該樹根節點下子節點的數量

1.6 獲取節點信息

AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();

1.6.2 獲取指定子節點(控件節點)

//通過文本找到對應節點集合.
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();

List<AccessibilityNodeInfo> textList = nodeInfo.findAccessibilityNodeInfosByText("");

//通過空間id找到對應的節點集合.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
    List<AccessibilityNodeInfo> textIdList = nodeInfo.findAccessibilityNodeInfosByViewId("000");
}

1.7 模擬節點點擊

當我們獲取了節點信息之后,對控件節點進行模擬點擊、長按等操作,AccessibilityNodeInfo類提供了performAction()方法讓我們執行模擬操作,具體操作可看官方文檔介紹,這里列舉常用的操作

//模擬點擊
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
//模擬長安
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
//模擬獲取焦點
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
//模擬粘貼.
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_PASTE);

監聽控件插件實現

2.1 原理分析

2.2 注意事項

1,每個版本的按鈕ID都是不一樣的,在我們的程序中是需要去修改按鈕ID,以達到版本的適配
2,在獲取控件ID的時候,注意其布局是否可點擊,否則獲取不可點擊的控件,會使程序無反應

2.3 獲取控件ID

當我們手機接入USB線時,在Android Device Monitor中的選擇設備并開啟Dump View Hierarchy for UI Automator工具,通過它可以獲取控件信息
monitor的安裝目錄.
C:\ZxnProgram\Android\Sdk\tools\monitor.bat

image.png

2.4 代碼實現

科邁贏錢

包名:
com.pos.kmretailpos.mini
id:

com.pos.kmretailpos.mini:id/tv_waipay
com.pos.kmretailpos.mini:id/et_ys
科邁贏錢

包名:
com.pos.kmretailpos.mini
id:

com.pos.kmretailpos.mini:id/tv_waipay
com.pos.kmretailpos.mini:id/et_ys

AccessibilityEvent時間類型:

 /**
 * View被點擊--->1
 * Represents the event of clicking on a {@link android.view.View} like
 * {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc.
 */
public static final int TYPE_VIEW_CLICKED = 0x00000001;

/**
 * View被長按
 * Represents the event of long clicking on a {@link android.view.View} like
 * {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc.
 */
public static final int TYPE_VIEW_LONG_CLICKED = 0x00000002;

/**
 * View被選中
 * Represents the event of selecting an item usually in the context of an
 * {@link android.widget.AdapterView}.
 */
public static final int TYPE_VIEW_SELECTED = 0x00000004;

/**
 * View獲得焦點
 * Represents the event of setting input focus of a {@link android.view.View}.
 */
public static final int TYPE_VIEW_FOCUSED = 0x00000008;

/**
 * View文本變化
 * Represents the event of changing the text of an {@link android.widget.EditText}.
 */
public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010;

/**
 * 監聽窗口狀態變化,比如打開一個popupWindow,dialog,Activity切換等等.打開了一個PopupWindow,Menu或Dialog(--->32)
 * Represents the event of a change to a visually distinct section of the user interface.
 * These events should only be dispatched from {@link android.view.View}s that have
 * accessibility pane titles, and replaces {@link #TYPE_WINDOW_CONTENT_CHANGED} for those
 * sources. Details about the change are available from {@link #getContentChangeTypes()}.
 */
public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020;

/**
 * Notification通知變化
 * Represents the event showing a {@link android.app.Notification}.
 */
public static final int TYPE_NOTIFICATION_STATE_CHANGED = 0x00000040;

/**
 * 一個View進入懸停
 * Represents the event of a hover enter over a {@link android.view.View}.
 */
public static final int TYPE_VIEW_HOVER_ENTER = 0x00000080;

/**
 *  一個View退出懸停
 * Represents the event of a hover exit over a {@link android.view.View}.
 */
public static final int TYPE_VIEW_HOVER_EXIT = 0x00000100;

/**
 * 觸摸瀏覽事件開始
 * Represents the event of starting a touch exploration gesture.
 */
public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 0x00000200;

/**
 * 觸摸瀏覽事件完成
 * Represents the event of ending a touch exploration gesture.
 */
public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400;

/**
 * 窗口的內容發生變化,或子樹根布局發生變化(--->2048)
 * Represents the event of changing the content of a window and more
 * specifically the sub-tree rooted at the event's source.
 */
public static final int TYPE_WINDOW_CONTENT_CHANGED = 0x00000800;

/**
 *  View滾動
 * Represents the event of scrolling a view. This event type is generally not sent directly.
 * @see View#onScrollChanged(int, int, int, int)
 */
public static final int TYPE_VIEW_SCROLLED = 0x00001000;

/**
 *  Edittext文字選中發生改變事件
 * Represents the event of changing the selection in an {@link android.widget.EditText}.
 */
public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000;

/**
 * 應用產生一個通知事件
 * Represents the event of an application making an announcement.
 */
public static final int TYPE_ANNOUNCEMENT = 0x00004000;

/**
 * 獲得無障礙焦點事件
 * Represents the event of gaining accessibility focus.
 */
public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000;

/**
 * 無障礙焦點事件清除
 * Represents the event of clearing accessibility focus.
 */
public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000;

/**
 *  在給定的移動粒度下遍歷視圖文本的事件
 * Represents the event of traversing the text of a view at a given movement granularity.
 */
public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 0x00020000;

/**
 *  開始手勢監測
 * Represents the event of beginning gesture detection.
 */
public static final int TYPE_GESTURE_DETECTION_START = 0x00040000;

/**
 * 結束手勢監測
 * Represents the event of ending gesture detection.
 */
public static final int TYPE_GESTURE_DETECTION_END = 0x00080000;

/**
 *  觸摸屏幕事件開始
 * Represents the event of the user starting to touch the screen.
 */
public static final int TYPE_TOUCH_INTERACTION_START = 0x00100000;

/**
 * 觸摸屏幕事件結束
 * Represents the event of the user ending to touch the screen.
 */
public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000;

/**
 * 屏幕上的窗口變化事件,需要API 21+
 * Represents the event change in the system windows shown on the screen. This event type should
 * only be dispatched by the system.
 */
public static final int TYPE_WINDOWS_CHANGED = 0x00400000;

/**
 * View中的上下文點擊事件
 * Represents the event of a context click on a {@link android.view.View}.
 */
public static final int TYPE_VIEW_CONTEXT_CLICKED = 0x00800000;

/**
 * 輔助用戶讀取當前屏幕事件
 * Represents the event of the assistant currently reading the users screen context.
 */
public static final int TYPE_ASSIST_READING_CONTEXT = 0x01000000;

/**
 * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
 * The type of change is not defined.
 */
public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0x00000000;

/**
 * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
 * One or more content changes occurred in the the subtree rooted at the source node,
 * or the subtree's structure changed when a node was added or removed.
 */
public static final int CONTENT_CHANGE_TYPE_SUBTREE = 0x00000001;

/**
 * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
 * The node's text changed.
 */
public static final int CONTENT_CHANGE_TYPE_TEXT = 0x00000002;

/**
 * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
 * The node's content description changed.
 */
public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;

/**
 * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
 * The node's pane title changed.
 */
public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 0x00000008;

/**
 * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
 * The node has a pane title, and either just appeared or just was assigned a title when it
 * had none before.
 */
public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 0x00000010;

/**
 *
 * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
 * Can mean one of two slightly different things. The primary meaning is that the node has
 * a pane title, and was removed from the node hierarchy. It will also be sent if the pane
 * title is set to {@code null} after it contained a title.
 * No source will be returned if the node is no longer on the screen. To make the change more
 * clear for the user, the first entry in {@link #getText()} will return the value that would
 * have been returned by {@code getSource().getPaneTitle()}.
 */
public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 0x00000020;

/**
 * 一個窗口被添加了
 * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
 * The window was added.
 */
public static final int WINDOWS_CHANGE_ADDED = 0x00000001;

/**
 * 一個被窗口移除了.
 * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
 * A window was removed.
 */
public static final int WINDOWS_CHANGE_REMOVED = 0x00000002;

/**
 * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
 * The window's title changed.
 */
public static final int WINDOWS_CHANGE_TITLE = 0x00000004;

/**
 * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
 * The window's bounds changed.
 */
public static final int WINDOWS_CHANGE_BOUNDS = 0x00000008;

/**
 * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
 * The window's layer changed.
 */
public static final int WINDOWS_CHANGE_LAYER = 0x00000010;

/**
 * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
 * The window's {@link AccessibilityWindowInfo#isActive()} changed.
 */
public static final int WINDOWS_CHANGE_ACTIVE = 0x00000020;

/**
 * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
 * The window's {@link AccessibilityWindowInfo#isFocused()} changed.
 */
public static final int WINDOWS_CHANGE_FOCUSED = 0x00000040;

/**
 * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
 * The window's {@link AccessibilityWindowInfo#isAccessibilityFocused()} changed.
 */
public static final int WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED = 0x00000080;

/**
 * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
 * The window's parent changed.
 */
public static final int WINDOWS_CHANGE_PARENT = 0x00000100;

/**
 * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
 * The window's children changed.
 */
public static final int WINDOWS_CHANGE_CHILDREN = 0x00000200;

/**
 *
 * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
 * The window either entered or exited picture-in-picture mode.
 */
public static final int WINDOWS_CHANGE_PIP = 0x00000400;
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容