参考资料:https://github.com/PopFisher/AccessibilitySample

1.Android文档里AccessibilityService简介

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

2.实现辅助功能服务

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

代码:

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() {    }}

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

packageName = com.android.mms eventType = 2048packageName = com.miui.home eventType = 32packageName = com.miui.home eventType = 1packageName = com.android.camera eventType = 2048packageName = com.android.camera eventType = 32packageName = com.android.camera eventType = 2048packageName = com.android.camera eventType = 2048packageName = com.miui.home eventType = 32packageName = com.miui.securitycenter eventType = 64packageName = 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是必须的,缺少任何一个系统个都会无视这个服务。是该服务的一些配置,在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(通过调用AccessibilityNodeInfogetViewIdResourceName()方法),就必须加上权限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);

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;}

更多相关文章

  1. Android内部存储和外部存储的获取方法
  2. 原生Android也能做Web开发了
  3. android 获取sim卡运营商信息
  4. android init 进程分析 (4 属性服务)
  5. Android上下文服务管理查询过程
  6. android,java实现NanoHTTPD 轻量级 服务器
  7. 学习笔记(01):FFmpeg打造Android万能音频播放器-OpenSL ES介绍并
  8. Android获取用户已安装app列表
  9. Android(安卓)Service AIDL 远程调用服务之简单音乐播放实例

随机推荐

  1. Android(安卓)数据存储:五大存储之SQLite
  2. Mono for Android——使用C#,在手机上运行
  3. Android之fragment点击切换和滑动切换结
  4. 重温String和StringBuffer
  5. Android之GridLayout用法
  6. android学习——使用TableLayout动态生成
  7. Android(安卓)Glide图片加载库基础使用详
  8. Android(安卓)浏览器Browser二次开发(一)
  9. Android(安卓)Hack Retrofit 增强参数(固
  10. android应用创建子进程的方法探究