实现自动化服务分几步?
- 步骤一:准备工具
- UI Automator
- 获取节点的信息
- UI Automator
- 步骤二:编写代码
- AccessibilityService
- 对指定页面进行操作
- AccessibilityService
撸代码
-
首先创建服务
public class AppAccessibilityService extends AccessibilityService { private static final String TAG = AppAccessibilityService.class.getSimpleName(); // 保存当前实例,后面可以通过服务获取 AccessibilityNodeInfo private static AppAccessibilityService mService; @Override protected void onServiceConnected() { super.onServiceConnected(); mService = this; } @Override public void onAccessibilityEvent(AccessibilityEvent event) { // 获取当前整个活动窗口的根节点 AccessibilityNodeInfo rootNode = getRootInActiveWindow(); // 获取事件类型,在对应的事件类型中对相应的节点进行操作 int eventType = event.getEventType(); switch (eventType) { case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: LogUtil.d(TAG, "界面改变::PackageName::" + event.getPackageName() + "::ClassName::" + event.getClassName()); // 获取父节点 rootNode.getParent(); // 获取子节点 rootNode.getChild(0); // 在节点上执行一个动作,通过返回值判断是否执行成功 boolean bool = rootNode.performAction(AccessibilityNodeInfo.ACTION_CLICK); bool = nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); bool = rootNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); // 通过字符串查找节点元素 rootNode.findAccessibilityNodeInfosByText(""); // 通过视图id查找节点元素 rootNode.findAccessibilityNodeInfosByViewId(""); break; case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: LogUtil.d(TAG, "内容改变::PackageName::" + event.getPackageName() + "::ClassName::" + event.getClassName()); break; case AccessibilityEvent.TYPE_VIEW_SCROLLED: LogUtil.d(TAG, "滑动变化::PackageName::" + event.getPackageName() + "::ClassName::" + event.getClassName()); break; default: break; } } @Override public void onDestroy() { super.onDestroy(); mService = null; } }
-
编写accessibility_config.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="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews" android:canRetrieveWindowContent="true" android:canRequestFilterKeyEvents="true" android:canRequestEnhancedWebAccessibility="true" android:canPerformGestures="true" android:description="@string/device_accessibility_desc" android:notificationTimeout="100" />
-
在manifest.xml注册服务
<service android:name=".service.AppAccessibilityService" android:enabled="true" android:exported="true" android:label="@string/app_name" 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>
通过上面代码,已经能实现一些简单的操作啦
常见问题
- 节点无法点击
-
解决方法,使用模拟坐标点击
public static boolean performClick(AccessibilityService service, AccessibilityNodeInfo nodeInfo) { if (service == null || nodeInfo == null) { return false; } Rect rect = new Rect(); nodeInfo.getBoundsInScreen(rect); int x = (rect.left + rect.right) / 2; int y = (rect.top + rect.bottom) / 2; Point point = new Point(x, y); GestureDescription.Builder builder = new GestureDescription.Builder(); Path path = new Path(); path.moveTo(point.x, point.y); builder.addStroke(new GestureDescription.StrokeDescription(path, 0L, 100L)); GestureDescription gesture = builder.build(); boolean isDispatched = service.dispatchGesture(gesture, new AccessibilityService.GestureResultCallback() { @Override public void onCompleted(GestureDescription gestureDescription) { super.onCompleted(gestureDescription); LogUtil.d(TAG, "dispatchGesture onCompleted: 完成..."); } @Override public void onCancelled(GestureDescription gestureDescription) { super.onCancelled(gestureDescription); LogUtil.d(TAG, "dispatchGesture onCancelled: 取消..."); } }, null); return isDispatched; }
-
- 界面事件无响应
- 解决方法,使用超时处理机制
- 建立一个定时器,去判断当前界面是否超时,然后进行超时处理(判断当前位于哪个界面,获取根节点,模拟操作)
- 解决方法,使用超时处理机制
- 事件响应重复,导致方法执行多次
- 解决方法,使用延迟
- 触发了事件后,使用Handler进行延迟处理,等待事件都响应完后,主动获取根节点,模拟操作
- 解决方法,使用延迟