其他分享
首页 > 其他分享> > Android自动化之AccessibilityService

Android自动化之AccessibilityService

作者:互联网

功能介绍

无障碍服务是一种应用,可提供界面增强功能,来协助残障用户或可能暂时无法与设备进行全面互动的用户完成操作。例如,正在开车、照顾孩子或参加喧闹聚会的用户可能需要其他或替代的界面反馈方式。

AccessibilityService: 类

当AccessibilityEvent事件被启动后AccessibilityService 会接收回调函数运行于后台,这些事件指的是在用户接口间的状态转换,比如,焦点变化,按钮被点击等。

简单来说,通过继承于此类并且实现它的抽象方法,我们可以在service中接收到页面变动、页面组件变动和应用通知等消息,并做出响应的操作,并且可以通过该类获取到的视图节点做一些点击、滑动等操作,而且在android8.0之后支持连续手势。

借助此功能可以实现某某App的某某辅助、某某脚本、某某爬虫工具,官 方 wài guà 最 为 致 命。

快速开始

详细请看官方文档

1. 创建无障碍服务

创建一个扩展 AccessibilityService 的类

package com.example.android.apis.accessibility;

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

public class MyAccessibilityService extends AccessibilityService {
    @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();//事件类型
        CharSequence packageName = event.getPackageName();//触发事件的包名
        if (packageName == null) return;
        String packageNameString = packageName.toString();
        // 要过滤的包名
        if (!packageNameString.startsWith("com.tencent")) return;
        switch (eventType) {
            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
                // 页面发生变化
                AccessibilityNodeInfo rootNode = getRootInActiveWindow();
                if (rootNode == null) return;
                curWin = event.getClassName().toString();
                Log.d(TAG, "Activity切换,当前窗体:" + curWin);
                break;
            }
            case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
                // 组件发生变化
                String className = event.getClassName().toString();
                Log.d(TAG, "Activity内容改变事件:" + className.toString());
                AccessibilityNodeInfo rootNode = getRootInActiveWindow();
                if (rootNode == null) return;
            }
            case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED: {
                // 应用通知
                List<CharSequence> texts = event.getText();
                if (!texts.isEmpty()) {
                    for (CharSequence text : texts) {
                        String content = text.toString();
                        Log.d(TAG, "收到通知:" + content);
                    }
                }
                break;
            }
        }
    }

    @Override
    public void onInterrupt() {
    }
    
    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        Toast.makeText(this, "xxx辅助功能已开启", Toast.LENGTH_SHORT).show();
    }
}

2. 清单声明、权限以及无障碍物服务配置

AndroidManifest.xml<application />中声明

<service android:name=".MyAccessibilityService"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
        android:label="@string/accessibility_service_label">
      <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
      </intent-filter>
      <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/robot_accessibility" />
</service>

其中@xml/robot_accessibility是无障碍服务的配置

在res中新建文件夹xml,在xml中创建robot_accessibility.xml
在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews|flagRequestEnhancedWebAccessibility"
    android:canPerformGestures="true"
    android:canRetrieveWindowContent="true"
    android:notificationTimeout="300" />

打开辅助功能,运行应用,查看日志

使用手册

  1. 能有单独的音量设置
  2. 有快速开启关闭的快捷键(Android 8.0以上在虚拟导航栏有快捷设置)
  3. 指纹手势
  4. 多语言文字转语音
  5. 为用户执行操作 (更改输入焦点和选择、滚动列表、转到主屏幕、按“返回”按钮,以及打开通知屏幕和最近用过的应用列表等,所有可见元素都能由无障碍服务选择)
    // 封装一个点击节点的方法 (NodeID为自定义枚举类,getId得到的是字符串,可以直接用字符串)
    private boolean clickView(NodeID nodeId, int index) {
        AccessibilityNodeInfo rootNode = getRootInActiveWindow();
        if (rootNode == null) {
            List<AccessibilityWindowInfo> windows = getWindows();
            if (windows.size() != 0) {
                Log.d(TAG, "windows: " + windows);
                rootNode = windows.get(0).getRoot();
            }
            if (rootNode == null) {
                Log.d(TAG, "获取不到rootNode");
                return false;
            }
        }
        List<AccessibilityNodeInfo> nodes = rootNode.findAccessibilityNodeInfosByViewId(nodeId.getId());
        int size = nodes.size();
        if (size != 0) {
            AccessibilityNodeInfo node = nodes.get(index);
            Log.d(TAG, nodeId + " 共找到" + size + "个控件, 点击第" + (index + 1) + "个," + node);
            node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            return true;
        }
        Log.d(TAG, nodeId + " 点击失败");
        return false;
    }
    // 当一些控件没有id,可以通过getChild或者getParent的方式拿到 返回为一个AccessibilityNodeInfo实例,此后可以调用方法进行点击、滑动或者获取它在屏幕中的位置
    private AccessibilityNodeInfo getViewChild(NodeID nodeId, int index, int childIndex) {
        AccessibilityNodeInfo rootNode = getRootInActiveWindow();
        if (rootNode == null) {
            List<AccessibilityWindowInfo> windows = getWindows();
            if (windows.size() != 0) {
                Log.d(TAG, "windows: " + windows);
                rootNode = windows.get(0).getRoot();
            }
            if (rootNode == null) {
                Log.d(TAG, "获取不到rootNode");
                return null;
            }
        }
        List<AccessibilityNodeInfo> nodes = rootNode.findAccessibilityNodeInfosByViewId(nodeId.getId());
        int size = nodes.size();
        if (size != 0) {
            AccessibilityNodeInfo node = nodes.get(index);
            Log.d(TAG, nodeId + " 共找到" + size + "个控件");
            AccessibilityNodeInfo nodeChild = node.getChild(childIndex);
            return nodeChild;
        }
        Log.d(TAG, nodeId + " childIndex: " + childIndex + " 点击失败");
        return null;
    }
  1. 监听手势
  2. 连续手势 (Android8.0以上)
    // 一个通过手势模拟点击的例子 rect为控件的位置 可以通过AccessibilityNodeInfo实例的getBoundsInScreen方法获取
    public void clickGesture(Rect rect) {
        Path path = new Path();
        path.moveTo(rect.centerX(), rect.centerY());
        GestureDescription.StrokeDescription strokeDescription;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            strokeDescription = new GestureDescription.StrokeDescription(path, 10, 10);
            GestureDescription.Builder gestureDescription = new GestureDescription.Builder().addStroke(strokeDescription);
            this.dispatchGesture(gestureDescription.build(), null, null);
        }
    }
    // 通过手势在x轴上滑动的例子 注意的距离可能需要通过dpi计算
    public void horizontalSlideGesture(Rect rect, int distanceX) {
        // 有需要请认真阅读Path类说明
        Path path = new Path();
        path.moveTo(rect.centerX(), rect.centerY());
        path.lineTo((float) (rect.centerX() + distanceX), rect.centerY());
        GestureDescription.StrokeDescription strokeDescription;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            strokeDescription = new GestureDescription.StrokeDescription(path, 0, 1500);
            GestureDescription.Builder gestureDescription = new GestureDescription.Builder().addStroke(strokeDescription);
            this.dispatchGesture(gestureDescription.build(), null, null);
        }
    }
  1. 使用焦点类型

无障碍服务可以使用 AccessibilityNodeInfo.findFocus() 方法来确定哪个界面元素具有输入焦点或无障碍功能焦点。您还可以使用 focusSearch() 方法搜索可通过输入焦点选择的元素。最后,无障碍服务可以使用 performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS) 方法设置无障碍功能焦点。

后记

本文仅个人记录使用,还有一些常用封装方法,仅供参考,并非最优解。
实际使用请通读官方文档

标签:Log,windows,TAG,rootNode,AccessibilityNodeInfo,自动化,Android,null,AccessibilitySer
来源: https://blog.csdn.net/qq_39649991/article/details/118968466