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点击事件
typeViewSelectedview被选择
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) 获得该实例,表示监听事件触发。

返回值方法描述
intgetEventType()获取事件类型(点击等)
CharSequencegetPackageName()时间来源包名
StringtoString()打印事件

AccessibilityNodeInfo 结点类

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

返回值方法描述
ListfindAccessibilityNodeInfosByText(String text)通过text寻找子节点
ListfindAccessibilityNodeInfosByViewId(String viewId)通过id查找子节点
CharSequencegetPackageName()Gets the package this node comes from.
AccessibilityNodeInfogetParent()Gets the parent.
AccessibilityNodeInfogetChild(int index)Get the child at given index.
intgetChildCount()Gets the number of children.
CharSequencegetText()Gets the text of this node.
StringgetViewIdResourceName()Gets the fully qualified resource name of the source view’s id.
AccessibilityWindowInfogetWindow()Gets the window to which this node belongs.
booleanisChecked()Gets whether this node is checked.
StringtoString()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列表。

返回值方法描述
AccessibilityNodeInfogetRoot()获得该窗口的根节点信息
AccessibilityWindowInfogetChild(int index)Gets the child window at a given index.
intgetChildCount()Gets the number of child windows.
intgetId()Gets the unique window id.
booleanisActive()Gets if this window is active.
booleanisFocused()Gets if this window has input focus.

综合应用,抢红包插件

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

<?xml version="1.0" encoding="utf-8"?>
<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<CharSequence> 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<AccessibilityNodeInfo> 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<AccessibilityNodeInfo> 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();
    }
}
  • 6
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Android 中,可以使用 AccessibilityService 来模拟用户的操作,包括返回操作。 以下是实现模拟返回操作的基本步骤: 1. 在 AndroidManifest.xml 文件中声明一个 AccessibilityService: ```xml <service android:name=".MyAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service> ``` 其中,MyAccessibilityService 是自定义的 AccessibilityServiceandroid:resource="@xml/accessibility_service_config" 指定了服务的配置文件。 2. 创建服务的配置文件 accessibility_service_config.xml: ```xml <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeAllMask" android:accessibilityFlags="flagDefault|flagIncludeNotImportantViews" android:accessibilityFeedbackType="feedbackGeneric" android:notificationTimeout="100" android:canRetrieveWindowContent="true" android:settingsActivity=".SettingsActivity" /> ``` 其中,android:accessibilityEventTypes 指定服务能够处理的事件类型,android:settingsActivity 指定服务的设置界面。 3. 实现 MyAccessibilityService 类: ```java public class MyAccessibilityService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { // 处理事件 } @Override public void onInterrupt() { // 中断服务 } @Override protected boolean onKeyEvent(KeyEvent event) { if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { performGlobalAction(GLOBAL_ACTION_BACK); return true; } return super.onKeyEvent(event); } } ``` 其中,onKeyEvent 方法会在用户按下键盘时被调用,如果按下的是返回键,就调用 performGlobalAction(GLOBAL_ACTION_BACK) 方法模拟返回操作。 4. 在应用中启动服务: ```java Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); startActivity(intent); ``` 以上就是实现模拟返回操作的基本步骤。需要注意的是,使用 AccessibilityService 来模拟用户操作需要用户授权,因此在启动服务时需要跳转到无障碍设置界面让用户手动开启服务
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值