预备知识:了解Android开发环境,linux/mac环境,设备引荐运用pixel。

写在前面

AccessibilityService是Android体系供给的辅佐功用,能够让咱们实时监听体系的各种事情,并做出相应的操作。事情包含:屏幕activity改变,按钮点击,界面切换等;可履行的操作包含:点击,滑动,对话框输入等等。因而咱们能够运用这功用来完成一些自动化操作。本文以完成抖音的自动点赞,重视,谈论功用作为实战例子,详细介绍AccessibilityService的用法。

服务注册与权限请求

App运用Android AccessibilityService服务首要需求请求相应的权限,以及注册对应的服务可供体系回调。在AndroidManifest.xml文件中装备信息如下:

<service
    android:name=".AutoAccessibilityService"
    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_service_config" />
</service>

android.permission.BIND_ACCESSIBILITY_SERVICE是需求请求的权限。

.AutoAccessibilityService是自定义的服务类称号,可自定义命名。

@xml/accessibility_service_config自定义装备的辅佐服务信息,需求在res文件下创立accessibility_service_config.xml文件,文件事例如下:

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/accessibility_service_description"
    android:packageNames="com.android.automeng, com.ss.android.ugc.aweme"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFlags="flagReportViewIds"
    android:accessibilityFeedbackType="feedbackSpoken"
    android:notificationTimeout="100"
    android:canRetrieveWindowContent="true"
    android:canPerformGestures="true"
    android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity"
    />
  • android:description:服务的描述,出现在辅佐中心列表中,引用字符串资源。
  • android:packageNames:监听的运用包名,用逗号离隔,这里监听的包名为com.android.automeng,com.ss.android.ugc.aweme这两个运用触发的事情。
  • android:accessibilityEventTypes:指明要监听的事情类型,这里运用typeAllMask表明监听一切类型的辅佐事情。
  • android:accessibilityFlags:事情回调中报告视图ID,这样服务能够找到触发事情的具体视图。
  • ndroid:accessibilityFeedbackType:供给语音反馈。
  • android:notificationTimeout:事情通知的超时时刻,单位ms。假如超过这个时刻服务没有响应,该事情将被丢掉。
  • android:canRetrieveWindowContent:服务能够检索窗口内容,假如为false,onAccessibilityEvent()中的事情无法获取子View信息。
  • android:canPerformGestures:服务能够履行手势操作,如双击、滑动等。
  • android:settingsActivity服务的设置界面Activity,答应用户设置服务相关选项。

创立辅佐服务

创立辅佐服务类需求继承 android.accessibilityservice.AccessibilityService 并重载onAccessibilityEventonInterrupt 办法,让体系在触发到咱们accessibility_service_config.xml文件中注册的事情时,能够回调咱们重载的辅佐服务代码。

public class AutoAccessibilityService extends AccessibilityService {
    @Override
    public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        //恣意事情触发都会调用该函数,因而能够在此完成辅佐功用代码。
    }
    @Override
    public void onInterrupt() {
        //退出时调用。
    }

完成点击事情

要完成模仿点击,咱们首要需求获取需求点击的点坐(x,y)。在此能够运用AccessibilityServier中的AccessibilityEvent对象来获取,经过遍历当前设备Activity界面中各个view来得到咱们所要点击的view的坐标。

//依据veiwID拿到对应的节点信息。
static AccessibilityNodeInfo searchNodeInfoByViewId(AccessibilityEvent event, String viewId){
    AccessibilityNodeInfo nodeInfo = event.getSource();
    if (nodeInfo == null) return null;
    List<AccessibilityNodeInfo> nodeList = nodeInfo.findAccessibilityNodeInfosByViewId(viewId);
    if(nodeList == null){
        for(int i=0; i < nodeInfo.getChildCount(); ++i){
            AccessibilityNodeInfo node = searchNodeInfoByViewId(nodeInfo.getChild(i), viewId);
            if(node != null){
                return node;
            }
        }
    }else if(nodeList != null && nodeList.size() > 0){
        for(AccessibilityNodeInfo node : nodeList){
            if(node.isVisibleToUser()){ //查找到可见的view则回来。
                return node;
            }
        }
    }
    return null;
}
//依据节点信息可获得对应的x,y坐标
static Point getPointtByNode(AccessibilityNodeInfo node){
    if (node == null){
        return new Point(0, 0);
    }
    Rect rect = new Rect();
    node.getBoundsInScreen(rect);
    printNodeInfo(node, "- ");
    Point point = new Point(rect.centerX(), rect.centerY());
    return point;
}

运用GestureDescription完成点击操作。

//完成对(x,y)坐标进行点击操作。
public static boolean clickByNode(AccessibilityService service, int x, int y) {
    if (service == null) {
        AutoLog.e("clickByNode, service is NULL.");
        return false;
    }
    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, 200L));
    GestureDescription gesture = builder.build();
    boolean isDispatched = service.dispatchGesture(gesture, new AccessibilityService.GestureResultCallback() {
        @Override
        public void onCompleted(GestureDescription gestureDescription) {
            super.onCompleted(gestureDescription);
            AutoLog.d("dispatchGesture click onCompleted.");
        }
        @Override
        public void onCancelled(GestureDescription gestureDescription) {
            super.onCancelled(gestureDescription);
            AutoLog.d("dispatchGesture click onCancelled.");
        }
    }, null);
    return isDispatched;
}

在咱们得知dou音app中点赞位置的viewid为"com.ss.android.ugc.aweme:id/dxk"后,即可运用上面代码完成自动点击操作。

public class AutoAccessibilityService extends AccessibilityService {
    @Override
    public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        //恣意事情触发都会调用该函数,因而能够在此完成辅佐功用代码。
        String viewId = "com.ss.android.ugc.aweme:id/dxk"; //该id为点赞view的id。
        AccessibilityNodeInfo node = searchNodeInfoByViewId(accessibilityEvent, viewId);
        Point p = getPointtByNode(node);
        clickByNode(this, p.x, p.y);
    }
    @Override
    public void onInterrupt() {
        //退出时调用。
    }

如何获取viewid?最简单的就是经过对一切view进行打印,然后依据getContentDescription或许getText回来的view描述信息来猜想对应的view。下面为打印出来的部分AccessibilityNodeInfo信息,从中不难看出来“重视”的viewid为com.ss.android.ugc.aweme:id/vf4

- className[android.widget.TextView], viewIdResourceName[null], text[NULL], contentDescription[探究,按钮], stateDescription[NULL], top[63], bottom[178], left[145], right[303], centerX[224], centerY[120]
---- className[android.widget.TextView], viewIdResourceName[com.ss.android.ugc.aweme:id/vf4], text[探究], contentDescription[NULL], stateDescription[NULL], top[90], bottom[151], left[179], right[269], centerX[224], centerY[120]
--- className[android.widget.TextView], viewIdResourceName[null], text[NULL], contentDescription[同城,按钮], stateDescription[NULL], top[63], bottom[178], left[303], right[461], centerX[382], centerY[120]
---- className[android.widget.TextView], viewIdResourceName[com.ss.android.ugc.aweme:id/vf4], text[同城], contentDescription[NULL], stateDescription[NULL], top[90], bottom[151], left[337], right[427], centerX[382], centerY[120]
--- className[android.widget.TextView], viewIdResourceName[null], text[NULL], contentDescription[重视,按钮有未读消息], stateDescription[NULL], top[63], bottom[178], left[461], right[619], centerX[540], centerY[120]
---- className[android.widget.TextView], viewIdResourceName[com.ss.android.ugc.aweme:id/vf4], text[重视], contentDescription[NULL], stateDescription[NULL], top[90], bottom[151], left[495], right[585], centerX[540], centerY[120]

获取viewId还能够经过运用Androidstudio中的Layout Inspection来检查页面布局信息,可是该办法只能对debug包或许处于debug状态的体系才能生效。假如关于已发布的app则需求运用apktool等东西来反编译修正Manifest中的debuggable信息。或许直接修正体系的debuggable特点。因为与本文章主题不太一致,在此不做详细描述。不建议运用该办法,因为关于视频类app来说界面改变太快,Layout Inspection会反应不过来,经常出现卡死状态。

完成滑动事情

与“点击事情”类似,相同运用GestureDescription完成点击操作,履行向上滑动操作,代码如下。

private boolean scrolUp(AccessibilityService service, int center_X, int center_Y) {
    if (service == null) {
        AutoLog.e("ScrolUp, service is NULL.");
        return false;
    }
    AutoLog.d("center position:" + "(" + center_X + "," + center_Y + ")");
    final android.graphics.Path path = new Path();
    path.moveTo((int) (center_X), (int) (center_Y * 1.5)); //起点坐标。
    path.lineTo((int) (center_X), (int) (center_Y * 0.5)); //终点坐标。
    GestureDescription.Builder builder = new GestureDescription.Builder();
    GestureDescription gestureDescription = builder.addStroke(
            new GestureDescription.StrokeDescription(path, 0, 800)
    ).build();
    boolean isDispatched = service.dispatchGesture(gestureDescription, new AccessibilityService.GestureResultCallback() {
        @Override
        public void onCompleted(GestureDescription gestureDescription) {
            super.onCompleted(gestureDescription);
            AutoLog.d("dispatchGesture ScrollUp onCompleted.");
            path.close();
        }
        @Override
        public void onCancelled(GestureDescription gestureDescription) {
            super.onCancelled(gestureDescription);
            AutoLog.d("dispatchGesture ScrollUp cancel.");
        }
    }, null);
    return isDispatched;
}

完成谈论事情

谈论操作需求往文本框中设置文本内容,并点击发送操作,即可完成自动谈论操作。

    //往谈论框输入文本内容。
    String commentEditViewId = "com.ss.android.ugc.aweme:id/csa"; //谈论文本框viewId
    AccessibilityNodeInfo node = searchNodeInfoByViewId(accessibilityEvent, commentEditViewId);
    if (node != null) {
         Bundle bundle = new Bundle();
         String commentMsg = "hello world"; 
         bundle.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
                        commentMsg);
         node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, bundle);
    }
    Thread.sleep(2000); //等待2秒
    //点击发送按钮,发送谈论内容。
    String sendViewId = "com.ss.android.ugc.aweme:id/ct_";
    AccessibilityNodeInfo node2 = AccessibilityUtils.searchNodeInfoByViewId(rootNodeInfo, sendViewId);
    if (node2 != null) {
        Point p = getPointtByNode(node2);
        boolean ret = clickByNode(this, p.x, p.y);
    }

最终作用

将上面的内容组合起来即可完成某音的自动化操作,示例视频如下,其中运用的抖音23.1.0版本包。

注:示例代码中,的viewid均为Android端抖音23.1.0版本包。