利用AccessibilityService实现探探自动右划

https://github.com/zhaokidd/TanTanAutoSwipe

核心类: AccessibiliiiiiiiityService


简单的说,AccessibilityService 为了方便残障人士实现操作,可以监控app的运行,在界面上实现一些自动操作.
AccessibilityService 的原理简单的来说就是: 安卓中 View 组件会主动发出各种状态改变的广播,Service收到后可以判断状态也可以获取发出广播的组件的view和view的子组件.
而实现一个 AccessibilityerService 需要三步

  • 在xml文件中进行配置
  • 在 AndroidManifest 文件中进行配置
  • 实现AccessibilityService,监控app界面中的操作.

配置 xml 文件


在 res -> xml -> 路径下新建一个 autoservice_config 文件

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags=""
    android:canRetrieveWindowContent="true"
    android:description="@string/app_name"
    android:notificationTimeout="100">

</accessibility-service>

这个配置文件中有两个选项比较重要:

android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged"

eventTypes 这个选项决定了你写的Service能收到什么类型的事件。
android:canRetrieveWindowContent 这个选项决定了能否通过 service提供的方法获取到app中的view.


在AndroidManifest文件中配置 AccessibilityService


        <service
            android:name=".service.MyAutoService"
            android:enabled="true"
            android:label="@string/app_label"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
            android:process="@string/isolated_process">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>

            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/autoservice_config" />
        </service>

接着在manifest中配置 AccessibilityService,这里按配置规则来就可以了.


实现自己的自定义 AccessibilityService


接着就是最重要的部分,我们要根据要监控的app实现自己的自定义AccessibilityService , 要做的并不复杂,实现要清楚几个概念

AccessibilityNodeInfo

界面中所有的view元素都会作为这么一个类出现.

AccessibilityEvent

Event事件,当app进出前后台或者有点击滑动等事件的时候,都会发出这么一个event

onAccessibilityEvent()

接收 event的回调方法,我们就在这个回调中实现自己的逻辑.

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        String packageName = event.getPackageName().toString();
        if (PACKAGE_NAME_TANTAN.equals(packageName)) {
            Log.d(TAG, event.getPackageName().toString() + event.getEventType());
            switch (event.getEventType()) {
                case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
                    AccessibilityNodeInfo mNodeInfo = getRootInActiveWindow();
                    Rect rect = new Rect();
                    mNodeInfo.getBoundsInScreen(rect);
                    boolean isPerformed = mNodeInfo.performAction(GESTURE_SWIPE_RIGHT);
                    accessibilityNodeInfo = mNodeInfo;
                    traversalNodeInfo(mNodeInfo);
                    performLoveButtonClick(mLoveButton);
//                    performSwipeRight(mNodeInfo);
                    Log.d(TAG, "child counts is :" + mNodeInfo.getChildCount() + "\nrect is :" + rect.toString() + "\n performed is :" + isPerformed);
                    break;
            }
        }
    }

    private void performLoveButtonClick(final AccessibilityNodeInfo mLoveButton) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        boolean isClicked = mLoveButton.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                        Log.d(TAG, "love Button is clicked = " + isClicked);
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    private void traversalNodeInfo(AccessibilityNodeInfo mNodeInfo) {
        int mChildCount = mNodeInfo.getChildCount();
        Log.d(TAG, "class name:" + mNodeInfo.getClassName().toString() + "  child count is :" + mChildCount + "\n");
        mLoveButton = mNodeInfo;
        if (mChildCount > 0) {
            for (int i = 0; i < mChildCount; i++) {
                traversalNodeInfo(mNodeInfo.getChild(i));
            }
        }
    }

思路很简单:在 onAccessibilityEvent() 方法中收到 event后,判断事件类型,如果要监控的app出现在前台(接收到TYPE_WINDOW_STATE_CHANGED事件),此时遍历所有view找到探探软件中的喜欢按钮,然后模拟点击事件就OK了。


如何找到指定的view呢?


微信图片_20170604132450.png

之前的话,可以通过 android自带的 uiautomatorviewer 这个工具来获取到界面上的控件的id,然后通过 findAccessibilityNodeInfosByViewId() 这个方法来获取到制定的view,但是现在的很多app已经把id隐藏掉了,所以这个方法行不通了.
现在除了遍历所有的view也没有更好的解决办法了.
好在探探里的这个喜欢按钮在view树的最后一个节点,我们只要前序遍历这棵view树,保留最后一个view就能获取的这个button。

    private void traversalNodeInfo(AccessibilityNodeInfo mNodeInfo) {
        int mChildCount = mNodeInfo.getChildCount();
        Log.d(TAG, "class name:" + mNodeInfo.getClassName().toString() + "  child count is :" + mChildCount + "\n");
        mLoveButton = mNodeInfo;
        if (mChildCount > 0) {
            for (int i = 0; i < mChildCount; i++) {
                traversalNodeInfo(mNodeInfo.getChild(i));
            }
        }
    }
 boolean isClicked = mLoveButton.performAction(AccessibilityNodeInfo.ACTION_CLICK);

最后一步开启一个工作线程,定时点击button就ok了.

需要注意的问题


所有的自动操作都需要app保持在与用户交互的状态下才能进行,目前没有找到在后台也能让监控服务正常运行的方法.

最后附上github地址,想了解的朋友可以看下源码:https://github.com/zhaokidd/TanTanAutoSwipe

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 174,089评论 25 709
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,131评论 19 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,993评论 6 342
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,683评论 0 17
  • 1在最没有安全感的爱情里,寻找安全感,注定是要失望的。 五瓶啤酒下肚,一直强忍着的维然终于绷不住,在我面前哭成了泪...
    路人记阅读 1,849评论 13 14