美文网首页
Android辅助功能(无障碍)使用

Android辅助功能(无障碍)使用

作者: cain07 | 来源:发表于2020-06-25 17:44 被阅读0次

    1.Android文档里AccessibilityService简介

    辅助功能只在帮助残障人士使用Android设备和app的时候使用。
    服务进程被杀掉后,下次启动,需再次申请权限

    2.实现辅助功能服务

    实现辅助功能,需要实现AccessibilityService类,并重写onAccessibilityEvent和onInterrupt方法。

    代码:

    public class AccessibilityTestService extends AccessibilityService {
    
        @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {
            String packageName = event.getPackageName().toString();
            int eventType = event.getEventType();
            Log.i("accessibility", "packageName = " + packageName + " eventType = " + eventType);
        }
    
        @Override
        public void onInterrupt() {
    
        }
    }
    

    开启服务权限后运行,不同页面,打印不同的packageName和eventType
    结果:

    packageName = com.android.mms eventType = 2048
    packageName = com.miui.home eventType = 32
    packageName = com.miui.home eventType = 1
    packageName = com.android.camera eventType = 2048
    packageName = com.android.camera eventType = 32
    packageName = com.android.camera eventType = 2048
    packageName = com.android.camera eventType = 2048
    packageName = com.miui.home eventType = 32
    packageName = com.miui.securitycenter eventType = 64
    packageName = com.android.systemui eventType = 2048
    

    3.注册服务
    在AndroidManifest中注册服务完成配置。
    代码:

    <service
        android:name=".android.accessibility.AccessibilityTestService"
        android:exported="true"
        android:label="@string/testAccessibility"
        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_config" />
    </service>
    

    label标签是在申请权限的设置页面显示该服务的名字。其中需要的permission和<intent-filter>是必须的,缺少任何一个系统个都会无视这个服务。<meta-data>是该服务的一些配置,在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:accessibilityFlags="flagReportViewIds"
        android:canRetrieveWindowContent="true"
        android:description="@string/accessibility_desc"
        android:notificationTimeout="100" />
    

    其中accessibilityFlags是一些额外声明,如果代码重要获取View的ID(通过调用AccessibilityNodeInfo的getViewIdResourceName()方法),就必须加上权限flagReportViewIds。

    4.跳转设置页面开启服务
    先判断是否开启服务

    if (!OpenAccessibilitySettingHelper.isAccessibilitySettingsOn(this,
            AccessibilityTestService.class.getName())) {// 判断服务是否开启
        OpenAccessibilitySettingHelper.jumpToSettingPage(this);// 跳转到开启页面
    } else {
        ToastUtil.showShortToast(this, "服务已开启");
        //do other things...
    }
    

    OpenAccessibilitySettingHelper类:

    public class OpenAccessibilitySettingHelper {

        //跳转到设置页面无障碍服务开启自定义辅助功能服务
        public static void jumpToSettingPage(Context context) {
            Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(intent);
        }
    
        //判断自定义辅助功能服务是否开启
        public static boolean isAccessibilitySettingsOn(Context context, String className) {
            if (context == null) {
                return false;
            }
            ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            if (activityManager != null) {
                List<ActivityManager.RunningServiceInfo> runningServices =
                        activityManager.getRunningServices(100);// 获取正在运行的服务列表
                if (runningServices.size() < 0) {
                    return false;
                }
                for (int i = 0; i < runningServices.size(); i++) {
                    ComponentName service = runningServices.get(i).service;
                    if (service.getClassName().equals(className)) {
                        return true;
                    }
                }
                return false;
            } else {
                return false;
            }
        }
    }
    

    5.辅助功能具体实现类
    模拟的一些点击,退出,拖动,获取界面信息等具体的实现方法,主要是通过节点查找,找到要操作的控件。
    首先要在实现的AccessibilityTestService中重写的onAccessibilityEvent方法中和event绑定。

    AccessibilityOperator.getInstance().updateEvent(this, event);
    

    1
    updateEvent方法

    public void updateEvent(AccessibilityService service, AccessibilityEvent event) {
        if (service != null && mAccessibilityService == null) {
            mAccessibilityService = service;
        }
        if (event != null) {
            mAccessibilityEvent = event;
        }
    }
    

    根据text搜索所有符合的节点

    public List<AccessibilityNodeInfo> findNodesByText(String text) {
        AccessibilityNodeInfo nodeInfo = getRootNodeInfo();
        if (nodeInfo != null) {
            Log.i("accessibility", "getClassName:" + nodeInfo.getClassName());
            Log.i("accessibility", "getText:" + nodeInfo.getText());
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                //需要在xml文件中声明权限android:accessibilityFlags="flagReportViewIds"
                // 并且版本大于4.3 才能获取到view 的 ID
                Log.i("accessibility", "getClassName:" + nodeInfo.getViewIdResourceName());
            }
            return nodeInfo.findAccessibilityNodeInfosByText(text);
        }
        return null;
    }
    

    根据ID搜索所有符合的节点

    public List<AccessibilityNodeInfo> findNodesById(String viewId) {
        AccessibilityNodeInfo nodeInfo = getRootNodeInfo();
        if (nodeInfo != null) {
            if (Build.VERSION.SDK_INT >= 18) {
                return nodeInfo.findAccessibilityNodeInfosByViewId(viewId);
            }
        }
        return null;
    }
    

    获取跟节点

    private AccessibilityNodeInfo getRootNodeInfo() {
        AccessibilityEvent curEvent = mAccessibilityEvent;
        AccessibilityNodeInfo nodeInfo = null;
        if (Build.VERSION.SDK_INT >= 16) {
            if (mAccessibilityService != null) {
                nodeInfo = mAccessibilityService.getRootInActiveWindow();
            }
        } else {
            nodeInfo = curEvent.getSource();
        }
        return nodeInfo;
    }
    

    模拟点击

    private boolean performClick(List<AccessibilityNodeInfo> nodeInfos) {
        if (nodeInfos != null && !nodeInfos.isEmpty()) {
            AccessibilityNodeInfo node;
            for (int i = 0; i < nodeInfos.size(); i++) {
                node = nodeInfos.get(i);
                // 获得点击View的类型
                Log.i("accessibility", "getClassName:" + node.getClassName());
                // 进行模拟点击
                if (node.isEnabled()) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                        return node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                    }
                }
            }
        }
        return false;
    }
    

    模拟退出键

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public boolean clickBackKey() {
        return mAccessibilityService.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
    }
    

    需要调用的地方直接获取AccessibilityOperator实例,获取到需要操作的节点后调用模拟方法。

    6.通过Android Device Monitor来直接获取界面中View的ID
    如图所示,打开Android Device Monitor

    可能会有报错信息,因为AndroidStudio自动默认选的是64位的环境,如果出错的话,可直接在该运行程序的目录手动开启

    默认打开的是64位的,可手动打开第一个。
    然后选中连接的模拟器,点击方框的按钮后,可以将手机当前页面的布局框架加载进来。

    点击布局上需要获取resourceid的view 后,会出现该view的id,但是有些view没有id,代码里获取的也是null,例如GooglePlay里某个应用的安装按钮就没有resourceid。但是他的父布局是有resourceid的,可以利用获取父布局的节点,再获取该父布局的某个子节点。

    获取某个节点的某个子节点代码:

    private AccessibilityNodeInfo getChildNodeInfos(String id, int childIndex) {
        List<AccessibilityNodeInfo> listChatRecord = findNodesById(id);
        if (listChatRecord == null || listChatRecord.size() == 0) {
            return null;
        }
        AccessibilityNodeInfo parentNode = listChatRecord.get(0);//该节点
        int count = parentNode.getChildCount();
        Log.i("accessibility", "子节点个数 " + count);
        return childIndex < count ? parentNode.getChild(childIndex) : null;
    }
    

    相关文章

      网友评论

          本文标题:Android辅助功能(无障碍)使用

          本文链接:https://www.haomeiwen.com/subject/zyosfktx.html