安卓AccessibilityService实现蚂蚁森林自动收集能量 最新 多线程 + 手势 + 深搜webView

因蚂蚁森林界面UI更新,本方法原理已失效

于2020年11月30日 更新

应用界面

蚂蚁森林自动收集能量APP
蚂蚁森林自动收集能量APP
蚂蚁森林自动收集能量APP

前言

最初,我有一个朋友问我能不能有办法监听到另一个App界面的内容,一旦有特定的消息出现就提醒用户,就这样,我接触到了AccessibityService。这个项目很好写,所以很快就实现了。主要是那个app结点都能直接获取到,也都能点击。有一天,我收蚂蚁森林能量,好友比较多,就在想,我能不能写一个基于AccessibilityService的自动收集能量的应用,造福一下懒人

程序员三大美德:懒惰、急躁、傲慢


运行效果

APP已放在末尾,欢迎大家测试
在这里插入图片描述


所用技术

AccessibilityService + 多线程 + 手势


项目开始

实现逻辑
在这里插入图片描述
说明
所有判断页面是否加载完成,用户名是否匹配的操作都是在开辟的子线程(图中橙色部分)中进行,主线程的MainHandler 接收来自子线程发送的消息,进行处理。


一些问题的解决

一、怎么获得webView中的结点信息

二、怎么判断webView加载完成了

三、怎么点击按钮


第一个问题,webView就像浏览器一样,里面的内容是网页的内容。

在webView中,所有的通过搜索获取结点List的方法失效了,但是可以通过不停地得到子节点、判断、得到子节点、判断的方法粗略的得到结点信息。

可以借助安卓的 uiautomatorviewer 工具查看结点树,但是这里的结点树也可能和实际运行的有点点出入,主要是webview 加载成功和不成功 之间有些变化,其他的结点结构是固定的,所以可取。

我是用dfs的方式得到webView的结点

private AccessibilityNodeInfo returnWebView(AccessibilityNodeInfo nowNode){
    if(nowNode==null) return null;

    if(nowNode.getClassName().toString().equals("android.webkit.WebView")){
        return nowNode;
    }
    if(nowNode.getChildCount()==0) return null;
    
    int size = nowNode.getChildCount();
    AccessibilityNodeInfo webViewNode = null;
    for(int i=0;i<size;i++){
        webViewNode = returnWebView(nowNode.getChild(i));
        if(webViewNode!=null) return webViewNode;
    }
    return null;
}

UIautomatorviewer具体怎么使用网上有很多博客,就不说了


第二个问题,怎么判断webView是否加载完成。

老实说,没有特别好的办法。你看一般的浏览器加载网页,有些时候加载的快,有些时候加载得慢,网页上很多东西的加载都是异步的。我很难准确的找到一个判定条件说网页绝对加载完成了。我用的办法是子线程判断关键信息是否加载完成了,只要我要点击的按钮加载完成了,就判定webView加载完成了。

private void LoadingForest(){
    //判定已载入的规则:只包含能量的view节点子孩子数大于等于4
    new Thread(new Runnable() {
        @Override
        public void run() {
        debug("加载蚂蚁森林...");
        Message message = Message.obtain();
        int MaxCount = MAX_REQUEST_TIME;
        WebViewNode = null;
        while(MaxCount > 0 ){
            MaxCount--;
            sleep(500);
            rootNode = getRootInActiveWindow();//Frame
            if(rootNode==null || rootNode.getChildCount()==0) continue;
            WebViewNode = returnWebView(rootNode);
            if(WebViewNode==null || WebViewNode.getChildCount()==0) continue;
            nowNode = WebViewNode.getChild(0);
            if(nowNode==null || nowNode.getChildCount()==0) continue;
            nowNode = nowNode.getChild(0);
            if (nowNode==null || nowNode.getChildCount()<3) continue;
            nowNode = nowNode.getChild(2);
//                        dfs(nowNode);
            if(nowNode.getChildCount()>=4) break;
        }
        if(MaxCount==0){
            message.what = -1;
        }else{
            message.what = 6;
        }
        MainHandler.sendMessage(message);
        }
    }).start();
}

第三个问题 ,怎么点击

通过手势,可以实现滑动 和点击

滑动

private void scroll(){
    new Thread(new Runnable() {
        @Override
        public void run() {
            int sx = (nowUserEntrance.left+nowUserEntrance.right)>>1;
            int sy = nowUserEntrance.bottom;
            int ey = nowUserEntrance.top-21;
            dispatchGestureScroll(2,sx,sy,sx,ey);
            sleep(100);
            Message message = Message.obtain();
            message.what = 10;
            MainHandler.sendMessage(message);
        }
    }).start();
}

private void dispatchGestureScroll(final int flag, int sx, int sy,int ex,int ey) {
//        debug("sx:"+sx+"sy:"+sy+"ex:"+ex+"ey:"+ey);
    GestureDescription.Builder builder = new GestureDescription.Builder();
    Path p = new Path();
    p.moveTo(sx, sy);
    p.lineTo(ex, ey);
    builder.addStroke(new GestureDescription.StrokeDescription(p, 0L, 100L));
    GestureDescription gesture = builder.build();
    dispatchGesture(gesture, new AccessibilityService.GestureResultCallback() {
        @Override
        public void onCompleted(GestureDescription gestureDescription) {
            super.onCompleted(gestureDescription);
            Log.d(TAG, flag+"onCompleted: 完成..........");
        }

        @Override
        public void onCancelled(GestureDescription gestureDescription) {
            super.onCancelled(gestureDescription);
            Log.d(TAG, flag+"onCompleted: 取消..........");
        }
    }, null);
}

点击

/**
 * 模拟点击事件
 * @param flag 点击的控件类型(1 能量) (0 用户入口)
 * @param x 横坐标
 * @param y 纵坐标
 */
private boolean dispatchGestureView(final int flag, int x, int y) {
    boolean res = false;
    GestureDescription.Builder builder = new GestureDescription.Builder();
    Path p = new Path();
    p.moveTo(x, y);
    p.lineTo(x, y);
    builder.addStroke(new GestureDescription.StrokeDescription(p, 0L, 100L));
    GestureDescription gesture = builder.build();
    Log.d("","点击了位置"+"("+x+","+y+")");
    sleep(200);
    res = dispatchGesture(gesture, new GestureResultCallback(){}, null);
    return res;
}

关于点击

为什么不直接用

Node.performAction(AccessibilityNodeIndo.CLICK)

来点击呢。呵,最初我就是这么做的,而且UIautomatorviewer也显示该按钮的clickable是true 但是就是点不了呀,后来研究了一下,发现安卓的按钮还有

btn.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
    	//点击事件
        return false;
    }
});

来实现点击事件,注意这里的return false ,返回false的时候,点击按钮onClick和onTouch 都会执行,而当返回true的时候,只有onTouch会执行。所以我打算用OnTouch来实现点击事件,蚂蚁森林能给我的只有按钮的坐标,通过给指定坐标的View分发TouchEvent

/**
 * 模拟点击事件
 * @param x 横坐标
 * @param y 纵坐标
 */
private void onTouchClick(int x,int y){
    final long downTime = SystemClock.uptimeMillis();
    View b1 = findViewById(R.id.button);
    Log.d("","onTouchClick");
    MotionEvent downEvent = MotionEvent.obtain(downTime,downTime, MotionEvent.ACTION_DOWN,x,y,0);
    MotionEvent upEvent = MotionEvent.obtain(downTime,SystemClock.uptimeMillis(), MotionEvent.ACTION_UP,x,y,0);
    b1.dispatchTouchEvent(downEvent);
    b1.dispatchTouchEvent(upEvent);
    downEvent.recycle();
    upEvent.recycle();
}

理论上可以通过坐标来获取view,网上有方法。但是我试了很多办法也获取不到第三方的AppDecorView,因为连第三方的Activity实例都获取不到。我当时卡了好久,差点想放弃这个项目,因为连点击都不能实现还怎么收能量。。

幸好,后来发现了手势也可以实现点击,才使得前面的努力没有白费。

想要了解更多,您也可以看看续篇

蚂蚁森林自动收集能量(续)

  • 35
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 39
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 39
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值