(Android无障碍服务开发QQ群:752296312)
    似乎除了input type=password的edit text 其他所有的UI都可以进行操作。直接开始。
1
首先配置环境。
创建一个service 清单中代码如下,就算用AS直接创建,也需要配置一些权限等东西。

<service
             android:name=".MyAccessibilityService"
             android:enabled="true"
             android:exported="true"
             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/accessible_service" />
         </service>



label 无障碍的名字,显示在系统辅助功能->无障碍界面的标题。权限不能掉,而且必须放在这。不能和其他权限一样放在上面。resource是无障碍服务的相关配置。xml文件如下:

//我直接写的注释 复制粘贴记得删除 不然会报错
 <accessibility-service
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:accessibilityEventTypes="typeAllMask"
     android:accessibilityFeedbackType="feedbackGeneric"
     android:accessibilityFlags="flagReportViewIds"
     android:canRetrieveWindowContent="true"
     android:notificationTimeout="100"
     android:description="@string/description"//无障碍的文字描述,展示在系统设置界面
     android:canPerformGestures="true"//允许模拟手势
     android:packageNames="com.tencent.mobileqq"/>//要检测的窗口或应用的包名



各个属性的内容和含义看这个无障碍参数详情
然后新建一个MyAccessibilityService继承AccessibilityService
重写onAccessibilityEvent()和onInterrupt()
在onAccessibilityEvent()中操作,在检测到目标包名在前台运行的时候会回调这个方法,然后我们在这获取根视图。

try {
             //拿到根节点
             AccessibilityNodeInfo rootInfo = getRootInActiveWindow();
             if (rootInfo == null) {
                 return;
             }
             //开始找目标节点,这里拎出来细讲,直接往下看正文
             if (rootInfo.getChildCount() != 0) {
                 if (rootInfo == null || TextUtils.isEmpty(rootInfo.getClassName())) {
                     return;
                 }
                 //开始去找
                 findByID(rootInfo, "com.tencent.mobileqq:id/chat_item_content_layout");
             }
         } catch (Exception e) {
             
         }



findByID方法如下:

private void findByID(AccessibilityNodeInfo rootInfo, String text) {
         if (rootInfo.getChildCount() > 0) {
             for (int i = 0; i < rootInfo.getChildCount(); i++) {
                 AccessibilityNodeInfo child = rootInfo.getChild(i);
                 try {
                     if (child.findAccessibilityNodeInfosByViewId(text).size() > 0) {
                         for (AccessibilityNodeInfo info : child.findAccessibilityNodeInfosByViewId(text)) {
                             performClick(getClickable(info));
                             //模仿全局手势
 //                        performGlobalAction(GLOBAL_ACTION_NOTIFICATIONS);
                         }
                     }
                 } catch (NullPointerException e) {
                 }
                 findByID(child, text);//递归一直找一层层的全部遍历
             }
         }
     }



id通过DDMS看 

有些控件处在listview或recyclerview下,就导致id重复,所以再用text匹配。
performClick(getClickable(info));这里的一个方法。

//有些节点不可点击 点击交给父级甚至父级的父级...来做的。
     private AccessibilityNodeInfo getClickable(AccessibilityNodeInfo info) {
         Log.i(TAG, info.getClassName() + ": " + info.isClickable());
         if (info.isClickable()) {
             return info;//如果可以点击就返回
         } else {//不可点击就检查父级 一直递归
             return getClickable(info.getParent());
         }
     }



此外通过文本内容来找到视图

private AccessibilityNodeInfo findByText(AccessibilityNodeInfo rootInfo, String text) {
         if (rootInfo.getChildCount() > 0) {
             for (int i = 0; i < rootInfo.getChildCount(); i++) {
                 AccessibilityNodeInfo child = rootInfo.getChild(i);
                 try {
                     if (child.findAccessibilityNodeInfosByText(text).size() > 0) {
                         for (AccessibilityNodeInfo info : child.findAccessibilityNodeInfosByText(text)) {
                             
                                 performClick(getClickable(info));
                                 return null;
                             
                         }
                     }
                 } catch (NullPointerException e) {
                 }
                 findByText(child, text);
             }
         }
         return null;    }



找到了视图之后,就可以做各种动作,如输入框输入:

private void changeInput(AccessibilityNodeInfo info,String text) {  //改变editText的内容
         Bundle arguments = new Bundle();
         arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
                 text);
         info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
     }



再如模拟手势:

private void MyGesture(){//仿滑动
         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
             Path path = new Path();
             path.moveTo(1000, 1000);//滑动起点
             path.lineTo(2000, 1000);//滑动终点
             GestureDescription.Builder builder = new GestureDescription.Builder();
             GestureDescription description = builder.addStroke(new GestureDescription.StrokeDescription(path, 100L, 100L)).build();
             //100L 第一个是开始的时间,第二个是持续时间
             dispatchGesture(description, new MyCallBack(), null);
         }
     }



模拟手势监听的回调:

//模拟手势的监听
     @RequiresApi(api = Build.VERSION_CODES.N)
     private class MyCallBack extends GestureResultCallback {
         public MyCallBack() {
             super();
         }        @Override
         public void onCompleted(GestureDescription gestureDescription) {
             super.onCompleted(gestureDescription);
            
         }        @Override
         public void onCancelled(GestureDescription gestureDescription) {
             super.onCancelled(gestureDescription);
       
         }
     }



只是用无障碍可以实现点击 向上滑动 向下滑动,输入等等,左右滑动需要通过模拟手势来达到,但是模拟手势需要7.0+。
因为系统原因,无障碍服务每次运行完都得重新开启
快捷跳转 到无障碍设置界面

Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
    startActivity(intent);



开启无障碍服务之后,再启动无障碍服务的Service

startService(new Intent(this, MyAccessibilityService.class));



无障碍服务基本可以捕捉所有界面并点击,包括6.0+弹窗授权。