微信抢红包插件

微信抢红包插件

下载地址:https://pan.baidu.com/s/1HJSI...
提取码:ntuwnode

这个Android插件能够帮助你在微信群聊抢红包时望风披靡。当检测到红包时,插件会自动点击屏幕,人工点击的速度没法比拟。android

实现原理

1. 抢红包流程的逻辑控制git

这个插件经过一个Stage类来记录当前对应的阶段。Stage类被设计成单例并惰性实例化,由于一个Service不须要也不该该处在不一样的阶段。对外暴露阶段常量和entering和getCurrentStage两个方法,分别记录和获取当前的阶段。github

public class Stage {正则表达式

private static Stage stageInstance;

public static final int FETCHING\_STAGE \= 0, OPENING\_STAGE \= 1, FETCHED\_STAGE \= 2, OPENED\_STAGE \= 3;

private int currentStage \= FETCHED\_STAGE;

private Stage() {}

public static Stage getInstance() {
    if (stageInstance \== null) stageInstance \= new Stage();
    return stageInstance;
}

public void entering(int \_stage) {
    stageInstance.currentStage \= \_stage;
}

public int getCurrentStage() {
    return stageInstance.currentStage;
}

}安全

1.1 阶段说明微信

阶段ide

说明函数

FETCHING_STAGE性能

正在读取屏幕上的红包,此时不该有别的操做

FETCHED_STAGE

已经结束一个FETCH阶段,屏幕上的红包都已加入待抢队列

OPENING_STAGE

正在拆红包,此时不该有别的操做

OPENED_STAGE

红包成功抢到,进入红包详情页面

1.程序以FETCHED_STAGE 开始,将屏幕上的红包加入待抢队列:

--> FETCHED_STAGE --> FETCHING_STAGE  --> FETCHED_STAGE -->

2.处理待抢队列中的红包:

--> [CLICK] --> OPENING_STAGE --> [CLICK] --> OPENED_STAGE --> [BACK] --> FETCHED_STAGE -->(抢到)

--> [CLICK] --> OPENING_STAGE --> [BACK] --> FETCHED_STAGE -->(没抢到)

3.不断重复流程1和2

1.2 根据阶段选择不一样的入口

在每次窗体状态发生变化后,根据当前所在的阶段选择入口。

switch (Stage.getInstance().getCurrentStage()) {

case Stage.OPENING\_STAGE:
    // .......
    Stage.getInstance().entering(Stage.FETCHED\_STAGE);
    performGlobalAction(GLOBAL\_ACTION\_BACK);
    break;
case Stage.OPENED\_STAGE:
    Stage.getInstance().entering(Stage.FETCHED\_STAGE);
    performGlobalAction(GLOBAL\_ACTION\_BACK);
    break;
case Stage.FETCHED\_STAGE:
    if (nodesToFetch.size() \> 0) {
        AccessibilityNodeInfo node \= nodesToFetch.remove(nodesToFetch.size() \- 1);
        if (node.getParent() != null) {
            // .......
            Stage.getInstance().entering(Stage.OPENING\_STAGE);
            node.getParent().performAction(AccessibilityNodeInfo.ACTION\_CLICK);
        }
        return;
    }
    Stage.getInstance().entering(Stage.FETCHING\_STAGE);
    fetchHongbao(nodeInfo);
    Stage.getInstance().entering(Stage.FETCHED\_STAGE);
    break;

}

2. 屏幕内容检测和自动化点击的实现

和其余插件同样,这里使用的是Android API提供的AccessibilityService。这个类位于android.accessibilityservice包内,该包中的类用于开发无障碍服务,提供代替或加强的用户反馈。

AccessibilityService 服务在后台运行,等待系统在发生 AccessibilityEvent 事件时回调。这些事件指的是用户界面上发生的状态变化, 好比焦点变动、按钮按下等等。服务能够请求“查询当前窗口中内容”的能力。 开发辅助服务须要继承该类并实现其抽象方法。

2.1 配置AccessibilityService

在这个例子中,咱们须要监听的事件是当红包来或者滑动屏幕时引发的屏幕内容变化,和点开红包时窗体状态的变化,所以咱们只须要在配置XML的accessibility-service标签中加入一条

android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"

或在onAccessibilityEvent回调函数中对事件进行一次类型判断

final int eventType = event.getEventType();
if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED

|| eventType \== AccessibilityEvent.TYPE\_WINDOW\_CONTENT\_CHANGED) {
     // ...

}

除此以外,因为咱们只监听微信,还须要指定微信的包名

android:packageNames="com.tencent.mm"

为了获取窗口内容,咱们还须要指定

android:canRetrieveWindowContent="true"

其余配置请看代码。

2.2 获取红包所在的节点

首先,咱们要获取当前屏幕的根节点,下面两种方式效果是相同的:

AccessibilityNodeInfo nodeInfo = event.getSource();

AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();

这里返回的AccessibilityNodeInfo是窗体内容的节点,包含节点在屏幕上的位置、文本描述、子节点id、可否点击等信息。从AccessibilityService的角度来看,窗体上的内容表示为辅助节点树,虽然和视图的结构不必定一一对应。换句话说,自定义的视图能够本身描述上面的辅助节点信息。当辅助节点传递给AccessibilityService以后就不可更改了,若是强行调用引发状态变化的方法会报错。

在聊天页面,每一个红包上面都有“领取红包”这几个字,咱们把它做为识别红包的依据。若是你收到了这四个字的文本消息,可能其余的插件会作出误判。由于咱们加入了阶段的概念,所以不会出现这个问题。

AccessibilityNodeInfo的API中有一个findAccessibilityNodeInfosByText方法容许咱们经过文原本搜索界面中的节点。匹配是大小写敏感的,它会从遍历树的根节点开始查找。API文档中特别指出,为了防止建立大量实例,节点回收是调用者的责任,这一点会在接下来的部分中讲到。

List<AccessibilityNodeInfo> node1 = nodeInfo.findAccessibilityNodeInfosByText("领取红包");

2.3 对节点进行操做

AccessibilityNodeInfo一样暴露了一个API——performAction来对节点进行点击或者其余操做。出于安全性考虑,只有这个操做来自AccessibilityService时才会被执行。

nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);

不过,咱们在调试时发现"领取红包"的mClickable属性为false,说明点击的监听加在它父辈的节点上。经过getParent获取父节点,这个节点是能够点击的。

咱们还须要全局的返回操做,最方便的办法就是performGlobalAction,不过注意这个方法是API 16后才有的。

performGlobalAction(GLOBAL_ACTION_BACK);

3. 获取屏幕上的全部红包

和其余插件最大的区别是,这个插件的逻辑是获取屏幕上全部的红包节点,去掉已经获取过的以后,将待抢红包加入队列,再将队列中的红包一个个打开。

3.1 判断红包节点是否已被抢过

实现这一点是编写时最大的障碍。对于通常的Java对象实例来讲,除非被GC回收,实例的Id都不会变化。我最初的想法是经过正则表达式匹配下面的十六进制对象id来表示一个红包。

android.view.accessibility.AccessibilityNodeInfo@2a5a7c; .......

但在测试中,队列中的部分成包没有被戳开。进一步观察发现,新的红包节点和旧的红包节点id出现了重复,且出现几率较大。因为GC日志正常,我推测AccessibilityNode可能有一个实例池的设计。获取当前窗体节点树的时候,从一个可重用的实例池中获取一个辅助节点信息 (AccessibilityNodeInfo)实例。在接下来的获取时,仍然从实例池中获取节点实例,这时可能会重用以前的实例。这样的设计是有好处的,能够防止每次返回都建立大量的实例,影响性能。AccessibilityNodeProvider的源码代表了这样的设计。

也就是说,为了标识一个惟一的红包,只用实例id是不充分的。这个插件采用的是红包内容+节点实例id的hash来标记。由于同一屏下,同一个节点树下的节点id是必定不会重复的,滑动屏幕后新红包的内容和节点id同时重复的几率已经大大减少。更改标识策略后,实测中几乎没有出现误判。

3.2 将新出现的红包加入待抢队列

咱们维护了两个列表,分别记录待抢红包和抢过的红包标识。

private List<AccessibilityNodeInfo> nodesToFetch = new ArrayList<>();

private List<String> fetchedIdentifiers = new ArrayList<>();

在每次读取聊天屏幕的时候,会检查这个红包是否在fetchedIdentifiers队列中,若是没有,则加入nodesToFetch队列。

for (AccessibilityNodeInfo cellNode : fetchNodes) {

String id \= getHongbaoHash(cellNode);
/\* 若是节点没有被回收且该红包没有抢过 \*/
if (id != null && !fetchedIdentifiers.contains(id)) {
    nodesToFetch.add(cellNode);
}

}

4. 打开队列中的红包

经过红包打开后显示的文本判断这个红包是否能够抢,进行接下来的操做。

4.1 判断红包节点是否被重用

这也是实现时的一个坑。前面提到了实例池的设计,当咱们把红包们加入待抢队列,戳完一个红包再回来时,队列中的其余红包节点可能已被回收重用,若是再去点击这个节点,显然没有什么卵用。

为了解决这个问题,咱们只能退而求其次,在点开前作一次检查。若是发现被重用了,就舍弃这个节点,在下一轮fetch的阶段从新加入待抢队列。确认没有重用当即打开,并把节点hash加入fetchedIdentifiers队列。这里若是node失效getHongbaoHash会返回null。

AccessibilityNodeInfo node = nodesToFetch.remove(nodesToFetch.size() - 1);
if (node.getParent() != null) {

String id \= getHongbaoHash(node);
if (id \== null) return;
fetchedIdentifiers.add(id);
Stage.getInstance().entering(Stage.OPENING\_STAGE);
node.getParent().performAction(AccessibilityNodeInfo.ACTION\_CLICK);

}

能够看出这并非颇有效率的解决方案。在实测中,有时队列中中红包失效后被舍弃,但没有新的AccessibilityEvent发生,接下来的操做都被挂起了。在戳过较多红包以后,这种状况表现得尤其明显,必需要显式地改变窗体内容才能解决。

4.2 根据红包类型选择操做

红包被戳开前会进行查重。戳开后若是界面上出现了“拆红包”几个字,说明红包尚未被别人抢走,马上点击“拆红包”并将stage标记为OPENED_STAGE。

此时,另三种状况代表抢红包失败了,直接返回,接下来状态会被标记为FETCHED_STAGE。

  1. “过时”,说明红包超过有效时间
  2. “手慢了”,说明红包发完但没抢到
  3. “红包详情”,说明你已经抢到过

4.3 防止加载红包时返回

戳开红包和红包加载完之间有一个“正在加载”的过渡动画,会触发onAccessibilityEvent回调方法。若是在加载完以前判断,上述文本还没出现,会被默认标记为FETCHED_STAGE并触发返回。所以,咱们要在返回前特殊断定这种情形。

咱们引入了TTL来记录尝试次数,并返回错误值-1。若是到达MAX_TTL时红包尚未加载出来就舍弃这个红包。

Stage.getInstance().entering(Stage.OPENING_STAGE);
ttl += 1;
return -1;

转自:https://github.com/geeeeeeeee...

注:基于https://github.com/geeeeeeeee... 开发,因为原做者已经两年没有维护了,本插件几乎重写了全部逻辑,以上技术分析只是大体实现思路,与本项目有部分出入。同时支持了企业微信红包。因为项目刚刚完成,尚未在github发布代码,后续会将项目开源。

相关文章
相关标签/搜索