Fork me on GitHub

实现全自动微信抢红包自带关闭技能

接受红包的洗礼吧,凡人们!!

项目地址

https://github.com/sunxu3074/AutoRobRedPackage 如果觉还不错,请给个 star .

功能介绍

效果展示

传到优酷空间里面了,墙外的小伙伴可观看YouTube.

效果

你只用把手机打开到你想要抢红包的群里,然后把屏幕熄灭时间调到10分钟(如果超过10了还没人发红包,说明你选错群了..)就ok了,然后就可以撒手不管该干嘛干嘛去了.

Refer link

Refer video

实现原理

用到了 AccessibilityService 这个类.这个类的目的主要目的就是为了让有障碍人士进行无障碍操作.

小伙伴们可以按照上面最后一个Mars老师讲的视频写出个大概,我主要说明一下 当抢红包后怎么自动关闭 以及 被别人抢了,或者已经是自己抢过的判断逻辑及自动关闭.

首先我们注册一个Service继承AccessibilityService,重写onAccessibilityEvent() 和onInterrupt() 两个方法.在AndroidManifest.xml 中注册并配置service.

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

至于为啥这样写,android/training上写的很明白.直接复制过来就OK.

接着在res/xml中创建robber.xml(名字随意,符合规范就好).

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/app_name"
    android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
    android:packageNames="com.tencent.mm"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:notificationTimeout="10"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="true"
    />
    <!--android:canRequestTouchExplorationMode="true"-->
    <!--android:canRequestEnhancedWebAccessibility="true"-->

  • description的属性值会显示在 服务/yourapp/一段描述,说白了就是服务的描述说明,告诉他咋用,我不会骗你,相信我吧!
  • packageNames 对哪个程序有效,com.tencent.mm就是微信的包名
  • accessibilityEventTypes 监听的事件类型. typeWindowContentChanged 是当窗口内容发生变化,也就是当前页面有人说话或者你要打字弹出软键盘,或者有人发红包这都是窗口状态发生变化. typeWindowStateChanged 是在你与应用互动时,当你点开红包,还没有抢时,这个就会被触发.
  • canRetrieveWindowContent这个属性很关键,注意设置为true.

注意 android:resource=“@xml/robber” 是在API 14后才有,如果不想用这种方式,可以在onServiceConnected()动态添加.

在MainActivity中设置一个按钮打开辅助服务.

findViewById(R.id.btn_open_setting).setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
                startActivity(intent);
            }

接着到了在注册的service里写关键代码的时候了.写之前需要弄清楚一个概念,AccessibilityNodeInfo这个类是啥.

This class represents a node of the window content as well as actions that can be requested from its source. From the point of view of an AccessibilityService a window content is presented as tree of accessibility node info which may or may not map one-to-one to the view hierarchy. In other words, a custom view is free to report itself as a tree of accessibility node info.

node_info

微信红包是一个NodeInfo,领取红包也是一个NodeInfo,他们的父布局也是一个NodeInfo.

基本逻辑

如图所示:

当屏幕出现可点击的红包时,我们就去让程序去点击它.

wechat_structure
  • 怎么判断?

判断微信红包这四个字

  • 怎么模拟点击操作

accessibilityNodeInfo.perform(AccessibilityNodeInfo.ACTION_CLICK);

  • 这个时候问题来了,当点击过后应该是拆红包的字样,而当我去判断这三个字的时候,发现微信已经把微信换成了 这张图片.(好像微信也在判断如果你写了这样的程序,他们就把拆红包换成的图片.只是猜测,我也没弄懂为啥惟独我的微信不是拆红包而变成了開.不要问我为什么是開是张图片,而不是字.反编译一下就看见了) 这个时候该怎么判断呢?

我们来换一个方式来判断,当别人发来红包时.是介个样子滴.

wechat-open

每个发来的红包都有『发了一个红包…』的字样,我们用findAccessibilityNodeInfosByText(“发了一个红包”)来返回NodeInfo,而NodeInfo的getParent()方法会返回父控件.我们来判断一个info.getParent().getChindCount();从这个图以及Log信息中看出count的数量是5个.

我们来对着上图猜测一下View

  1. 头像+ T^T (TextView)
  2. 发了一个红包,金额随机(TextView)
  3. 恭喜发财,大吉大利!(TextView)
  4. (ImageView)
  5. X (View)

怎么来找到 这个图片呢,getChildAt(N)即可.代码中我是遍历所有的childNodeInfo都强制点击了一遍,其实是不用这样的.从布局上看父布局可以猜测RelativeLayout, X (关闭的x)这个View应该是在最后写入的,開是倒数第二个,也就是info.getParent().getChildAt(3).perfoerm(AssessibilityNodeInfo.ACTION_CLICK);有时候count数量也会出现6的时候,忘记是什么情况下了,感兴趣的可以测试一下.所以我还是坚持遍历来拆红包,反正X这个View是声明在最后的.凭感觉;)

其它的原理都差不多了,懂了上面说的大概看一下代码就都明白了.

手慢了

wechat-slow

手慢了的情况下,好的处理办法是直接X.但是我点击 X 之后会再次点开这个红包,一直循环.解决办法:当出现手慢了的时候,模拟点击看看大家的手气,然后进入红包详情页面,再关闭即可.

领取过了

wechat-details
package xyz.isunxu.robredpackage.service;

import android.accessibilityservice.AccessibilityService;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityRecord;
import android.webkit.WebView;
import java.util.List;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import xyz.isunxu.robredpackage.Application.MyApplication;

public class RobRedPackageService extends AccessibilityService {

    private AccessibilityNodeInfo mAccessibilityNodeInfo = null;


    @Override public void onAccessibilityEvent(AccessibilityEvent event) {

        AccessibilityNodeInfo previousAccessibilityNodeInfo = null;

        mAccessibilityNodeInfo = event.getSource();

        if (mAccessibilityNodeInfo == null) {
            return;
        }

        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
            List<AccessibilityNodeInfo> accessibilityNodeInfos
                    = mAccessibilityNodeInfo.findAccessibilityNodeInfosByText("微信红包");
            if (accessibilityNodeInfos != null && accessibilityNodeInfos.size() > 0) {
                AccessibilityNodeInfo info = accessibilityNodeInfos.get(accessibilityNodeInfos.size() - 1);
                if (previousAccessibilityNodeInfo == null) {
                    previousAccessibilityNodeInfo = info;
                    info.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
                }
                else if (previousAccessibilityNodeInfo != null && previousAccessibilityNodeInfo != info) {
                    info.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
                }
                else {
                    return;
                }

            }

        }

        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {

            List<AccessibilityNodeInfo> infos = mAccessibilityNodeInfo.findAccessibilityNodeInfosByText("拆红包");
            if (infos != null && infos.size() > 0) {
                AccessibilityNodeInfo accessibilityNodeInfo = infos.get(infos.size() - 1);
                accessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            }

            List<AccessibilityNodeInfo> infoDetails = mAccessibilityNodeInfo.findAccessibilityNodeInfosByText("红包详情");
            if (infoDetails != null && infoDetails.size() > 0) {
                AccessibilityNodeInfo accessibilityNodeInfo = infoDetails.get(infoDetails.size() - 1);
                accessibilityNodeInfo.getParent().getChild(0).performAction(AccessibilityNodeInfo.ACTION_CLICK);
                return;
            }

            List<AccessibilityNodeInfo> infoSlows = mAccessibilityNodeInfo.findAccessibilityNodeInfosByText("手慢了");
            if (infoSlows != null && infoSlows.size() > 0) {
                AccessibilityNodeInfo accessibilityNodeInfo = infoSlows.get(infoSlows.size() - 1);
                accessibilityNodeInfo.getParent().getChild(3).performAction(AccessibilityNodeInfo.ACTION_CLICK);
                return;
            }

            List<AccessibilityNodeInfo> infoKais = mAccessibilityNodeInfo.findAccessibilityNodeInfosByText("发了一个红包");
            if (infoKais != null && infoKais.size() > 0) {
                AccessibilityNodeInfo accessibilityNodeInfo = infoKais.get(infoKais.size() - 1);
                int size = accessibilityNodeInfo.getParent().getChildCount();
                Log.d("sunxu_log", "size-->" + size);
                //for (AccessibilityNodeInfo info : infoKais) {
                //    info.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                //}
                for (int i = 0; i < accessibilityNodeInfo.getParent().getChildCount(); i++) {
                    accessibilityNodeInfo.getParent().getChild(i).performAction(AccessibilityNodeInfo.ACTION_CLICK);
                }
            }
        }
    }

    @Override public void onInterrupt() {

    }


}

TODO

  • 如果不是最新的红包,那就不抢.

这牵扯到AccessibilityNodeInfo的比较,我也尝试过.NodeInfo的getContentDescription(),getViewIdResoucreName()当我想打印出它们的信息时,程序后面就不执行了.而且previousNodeInfo的数据储存也没有实现,有兴趣的小伙伴可以实现下.

  • 开启服务后不能与群里的小伙伴交流,只能一味地抢.

这是因为要说话时,会触碰语音按键,点击按键会出现浮动窗口,此时就是窗口出现变化了.然后就是那一堆判断逻辑了..这个怎么改我还没想好,可能还是涉及到上面的问题,AccessibilityNodeInfo的比较.等我想好了,会继续完善.

说明

  • 保持程序运行在后台与服务开启.
  • 要抢红包之前可以先把程序关闭,然后重新开启程序与服务.(以免程序会被系统强制关闭)
  • 设置屏幕熄灭时间长一点
  • 停留在选择要抢红包的群的页面中.

支持

  • 在github上给我一个star就好.
  • 捐赠: https://sunxu.work/about (支付宝和微信的二维码都在这个页面上,star为主,捐赠为辅)

这个程序完全是娱乐为主,并不作商业目的.

编程让我很快乐,希望能永远快乐下去.

最后,祝好;)