AccessibilityService(无障碍服务)小结

https://developer.android.google.cn/guide/topics/ui/accessibility/services.html
无障碍服务,可以监听界面的操作,比如:点击、拖动、界面更新等信息。更为强大的是可以获取屏幕信息,同时具备普通Service的能力。(在别人手机中植入一个无障碍服务并开启,可以监听他的手机操作和屏幕信息,eg:获取微信、QQ当前聊天文字并上传)
因为无障碍服务相比一般Service过于强大,安装后还需要在设置->辅助功能中手动开启。

创建AccessibilityService与声明

https://developer.android.google.cn/reference/android/accessibilityservice/AccessibilityService.html

创建一个类继承自AccessibilityService

    public class MyAccessibilityService extends AccessibilityService{
        @Override
        protected void onServiceConnected() {
            super.onServiceConnected();
        }
        @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {
        }
        @Override
        public void onInterrupt() {
        }
        @Override
        public boolean onUnbind(Intent intent) {
            return super.onUnbind(intent);
        }
    }

AccessibilityService继承自普通的Service,因而具备普通Service的生存周期,同时具有自己的一些生命周期。

函数名 描述
onServiceConnected() (可选)当系统成功连接到该AccessibilityService时,将调用此方法。主要用与一次性配置或调整的代码。
onAccessibilityEvent() (必要)当系统监测到相匹配的AccessibilityEvent事件时,将调用此方法,在整个Service的生命周期中,该方法将被多次调用。
onInterrupt() (必要)系统需要中断AccessibilityService反馈时,将调用此方法。AccessibilityService反馈包括服务发起的震动、音频等行为。
onUnbind() (可选)系统要关闭该服务是,将调用此方法。主要用来释放资源。

AccessibilityService服务声明

和普通Service一样,AccessibilityService同样需要在Manifest.xml中注册。

<service
    android:name=".MyAccessibilityService"
    android:label="@string/accessibility_service_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>

android.permission.BIND_ACCESSIBILITY_SERVICE权限和action是必须的。
同时AccessibilityService需要提供设置列表(meta-data),该设置也可以在运行时通过AccessibilityService.setServiceInfo (AccessibilityServiceInfo info)进行动态设置。不过该方式并不能设置所有的参数,因而推荐的方法为(meta-data)。
android:resource中的地址为:/res/xml/accessibility_service_config.xml。

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/accessibility_service_description"
    android:packageNames="com.example.android.apis"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFlags="flagDefault"
    android:accessibilityFeedbackType="feedbackSpoken"
    android:notificationTimeout="100"
    android:canRetrieveWindowContent="true"
    android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity"
/>

各项参数参考AccessibilityServiceInfo。

AccessibilityServiceInfo 配置类

https://developer.android.google.cn/reference/android/accessibilityservice/AccessibilityServiceInfo.html
AccessibilityService的配置类,可使用setService进行动态设置,同时和上述的accessibility_service_config.xml相对应。
以下参数可以使用“|”表示使用多个选项。

android:accessibilityEventTypes 事件类型

AccessibilityService服务响应的事件类型,只有声明了的类型,系统才会调用该服务的onAccessibilityEvent。

常量 描述
typeViewClicked 点击事件
typeViewSelected view被选择
typeViewScrolled 滑动事件
typeWindowContentChanged 窗口内容该表
typeAllMask 所有事件

完整列表如下:
typeViewClicked typeViewLongClicked typeViewSelected typeViewFocused typeViewTextChanged typeWindowStateChanged typeNotificationStateChanged typeViewHoverEnter typeViewHoverExit typeTouchExplorationGestureStart typeTouchExplorationGestureEnd typeWindowContentChanged typeViewScrolled typeViewTextSelectionChanged typeAnnouncement typeViewAccessibilityFocused typeViewAccessibilityFocusCleared typeViewTextTraversedAtMovementGranularity typeGestureDetectionStart typeGestureDetectionEnd typeTouchInteractionStart typeTouchInteractionEnd typeWindowsChanged typeContextClicked typeAssistReadingContext typeAllMask

android:accessibilityFeedbackType 反馈类型

AccessibilityService服务的反馈类型。

常量 描述
feedbackSpoken 语音反馈
feedbackHaptic 触觉(震动)反馈
feedbackAudible 音频反馈
feedbackVisual 视频反馈
feedbackGeneric 通用反馈
feedbackAllMask 以上都具有

android:accessibilityFlags 额外声明

一些格外的参数。

常量 描述
flagDefault 默认
flagIncludeNotImportantViews
flagRequestTouchExplorationMode
flagRequestEnhancedWebAccessibility
flagReportViewIds 允许获得view id,需要获取viewid的时候需要该参数,开始没声明导致nodeInfo. getViewIdResourceName()返回的为null
flagRequestFilterKeyEvents
flagRetrieveInteractiveWindows 允许获得windows,使用getWindows时需要该参数,否则会返回空列表

其他可设置参数

参数名 描述
android:canRetrieveWindowContent 设置为“true”表示允许获取屏幕信息,使用getWindows、getRootInActiveWindow等函数时需要为“true”
android:packageNames 服务响应的事件来源,若设置,则服务只能获取该package发出的事件,不设置可获得所有的事件源
android:notificationTimeout 同一种事件类型触发的最短时间间隔(毫秒)
android:description 服务和行为的简短描述

AccessibilityEvent 事件类

https://developer.android.google.cn/reference/android/view/accessibility/AccessibilityEvent.html
常用实例化途径:在MyAccessibilityService 中 onAccessibilityEvent(AccessibilityEvent event) 获得该实例,表示监听事件触发。

返回值 方法 描述
int getEventType() 获取事件类型(点击等)
CharSequence getPackageName() 时间来源包名
String toString() 打印事件

AccessibilityNodeInfo 结点类

https://developer.android.google.cn/reference/android/view/accessibility/AccessibilityNodeInfo.html
常用实例化途径:AccessibilityService.getRootInActiveWindow()获得(当前活动窗口的根节点)。AccessibilityWindowInfo类也能获取该window下的node。
该类与下图有对应关系(通过uiautomatorviewer工具获得):
AccessibilityService(无障碍服务)小结_第1张图片
常用方法:

返回值 方法 描述
List findAccessibilityNodeInfosByText(String text) 通过text寻找子节点
List findAccessibilityNodeInfosByViewId(String viewId) 通过id查找子节点
CharSequence getPackageName() Gets the package this node comes from.
AccessibilityNodeInfo getParent() Gets the parent.
AccessibilityNodeInfo getChild(int index) Get the child at given index.
int getChildCount() Gets the number of children.
CharSequence getText() Gets the text of this node.
String getViewIdResourceName() Gets the fully qualified resource name of the source view’s id.
AccessibilityWindowInfo getWindow() Gets the window to which this node belongs.
boolean isChecked() Gets whether this node is checked.
String toString() Returns a string representation of the object.

比如: 遍历打印当前布局信息可以使用一下代码(布局节点树)

    @Override
    public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        if(accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED){
              AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
              dfsnode(nodeInfo,0);
        }
    }
    public void dfsnode(AccessibilityNodeInfo node , int num){
        StringBuilder stringBuilder = new StringBuilder();
        for(int i = 0 ;i < num ; i++){
            stringBuilder.append("__ ");    //父子节点之间的缩进
        }
        Log.i("####",stringBuilder.toString() + node.toString());   //打印
        for(int i = 0 ; i < node.getChildCount()  ; i++){      //遍历子节点
            dfsnode(node.getChild(i),num+1);
        }
    }

AccessibilityWindowInfo 窗口类

https://developer.android.google.cn/reference/android/view/accessibility/AccessibilityWindowInfo.html
常用实例化途径:AccessibilityService.getWindows()获得,返回值是一个list列表。

返回值 方法 描述
AccessibilityNodeInfo getRoot() 获得该窗口的根节点信息
AccessibilityWindowInfo getChild(int index) Gets the child window at a given index.
int getChildCount() Gets the number of child windows.
int getId() Gets the unique window id.
boolean isActive() Gets if this window is active.
boolean isFocused() Gets if this window has input focus.

综合应用,抢红包插件

代码来自涅槃1992:http://www.jianshu.com/p/4cd8c109cdfb
配置代码:


<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" />

Service代码:

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 texts = event.getText();
        if (!texts.isEmpty()) {
            for (CharSequence text : texts) {
                String content = text.toString();
                //如果微信红包的提示信息,则模拟点击进入相应的聊天窗口
                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();
                        }
                    }
                }
            }
        }
    }

    /**
     * 关闭红包详情界面,实现自动返回聊天窗口
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    private void close() {
        AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
        if (nodeInfo != null) {
            //为了演示,直接查看了关闭按钮的id
            List 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 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();
        }

    }

    /**
     * 递归查找当前聊天窗口中的红包信息
     *
     * 聊天窗口中的红包都存在"领取红包"一词,因此可根据该词查找红包
     * 
     * @param node
     */
    public AccessibilityNodeInfo recycle(AccessibilityNodeInfo node) {
        if (node.getChildCount() == 0) {
            if (node.getText() != null) {
                if ("领取红包".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();
    }
}

你可能感兴趣的:(android学习)