微信抢红包插件

这个Android插件可以帮助你在微信群聊抢红包时战无不胜。当检测到红包时,插件会自动点击屏幕,人工点击的速度无法比拟。

实现原理

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

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

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 阶段说明

阶段

说明

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 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 nodesToFetch = new ArrayList<>();

private List 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。

“过期”,说明红包超过有效时间

“手慢了”,说明红包发完但没抢到

“红包详情”,说明你已经抢到过

4.3 防止加载红包时返回

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

我们引入了TTL来记录尝试次数,并返回错误值-1。如果到达MAX_TTL时红包还没有加载出来就舍弃这个红包。

Stage.getInstance().entering(Stage.OPENING_STAGE);

ttl += 1;

return -1;

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

微信抢红包插件 android 8.0,微信抢红包插件相关推荐

  1. Appium appium android 6.0+ 微信 @driver.available_contexts 返回 webview_undefined 问题

    环境:Android 6.0.1+appium 1.4.16.1 在上面环境中执行微信公众号中的H5测试,发现driver.available_contexts 有时候返回:webview_undef ...

  2. Android+8.0+微信表情,微信8.0“爆炸式”更新!表情会动还可以扔炸弹!网友:安卓不配吗?...

    原标题:微信8.0"爆炸式"更新!表情会动还可以扔炸弹!网友:安卓不配吗? 本文转自:环球设计榜 ID:gdl17888 就在昨天晚上 微信悄悄更新了 iOS8.0版本 瞬间冲上微 ...

  3. Android+8.0+微信表情,微信表情包不会动是怎么回事 安卓微信8.0更新表情特效怎么弄?...

    微信更新8.0版本来了!微信新版本更新,微信8.0已经上架 App Store,其显示此次更新了若干功能,解决了一些已知问题,那么微信更新表情不动是什么原因?微信在1月21日晚间于iOS更新了8.0版 ...

  4. android仿微信聊天功能,Android高仿微信聊天界面代码分享

    微信聊天现在非常火,是因其界面漂亮吗,哈哈,也许吧.微信每条消息都带有一个气泡,非常迷人,看起来感觉实现起来非常难,其实并不难.下面小编给大家分享实现代码. 先给大家展示下实现效果图: OK,下面我们 ...

  5. android 微信高仿,Android高仿微信聊天界面代码分享

    微信聊天现在非常火,是因其界面漂亮吗,哈哈,也许吧.微信每条消息都带有一个气泡,非常迷人,看起来感觉实现起来非常难,其实并不难.下面小编给大家分享实现代码. 先给大家展示下实现效果图: OK,下面我们 ...

  6. android高仿微信聊天页面,Android 高仿微信语音聊天页面高斯模糊(毛玻璃效果)

    目前的应用市场上,使用毛玻璃效果的APP随处可见,比如用过微信语音聊天的人可以发现,语音聊天页面就使用了高斯模糊效果. 先看下效果图: 仔细观察上图,我们可以发现,背景图以用户头像为模板,对其进行了高 ...

  7. android仿微信充值布局,Android 高仿微信支付数字键盘功能

    现在很多app的支付.输入密码功能,都已经开始使用自定义数字键盘,不仅更加方便.其效果着实精致. 下面带着大家学习下,如何高仿微信的数字键盘,可以拿来直接用在自身的项目中. 先看下效果图: 1. 自定 ...

  8. android 微信高仿,Android 高仿微信朋友圈拍照上传功能

    模仿微信朋友圈发布动态,输入文字支持文字多少高度自增,有一个最小输入框高度,输入文字有限制,不过这些都很easy! 1. PhotoPicker的使用 这是一个支持选择多张图片,点击图片放大,图片之间 ...

  9. android studio微信界面设计,android studio开发微信界面

    android studio开发微信界面 android studio开发微信界面 功能说明:主要是做微信的简单的聊天界面,利用Fragment,进行微信界面的跳转 项目代码: 源代码地址 MainA ...

  10. android支付宝支付微信支付封装,Android仿支付宝微信支付密码界面弹窗封装dialog...

    一,功能效果 二,实现过程 1,先写xml文件:dialog_keyboard.xml 注意事项 (1),密码部分用的是一个线性布局中6个TextView,并设置android:inputType=& ...

最新文章

  1. 档案中级职称计算机需要考几个模块,2020年职称申报需要准备哪些档案资料?这些细节必须知道!...
  2. Java知识汇总-思维导图
  3. bootstrap轮播图怎么居中
  4. 透过微信应用号,看HTML5与Native进入融合时代
  5. 如何动态改变框架的大小[转]
  6. 如何在一天内被Google和百度收录
  7. bzoj 3232 01分数规划+最大权封闭子图判定
  8. 如果编程替换成中文就会怎样? 程序员看了表示头疼
  9. 测试框架 如何测试私有方法_高效的企业测试–测试框架(5/6)
  10. 贝叶斯公式设b_数据分析经典模型——朴素贝叶斯
  11. k8s 您的连接不是私密连接_直插式声测管是不是承插式?如何连接?
  12. c++ math头文件一些函数使用记录
  13. python 经纬度画北京地图_python basemap 画出经纬度并标定的实例
  14. python中fontsize_python size
  15. 数学规划求解器lp_solve超详细教程
  16. go服务器验证苹果账号登录
  17. Salesforce 解决chatter简档删除不掉记录类型问题
  18. ERROR in ./node_modules/element-plus/es/components/menu-item-group/style/css2.mjs 2:0-54
  19. wince车机可以连接电脑吗_WINCE车机平台手机互联使用说明
  20. C#之DES加密解密

热门文章

  1. 在mysql中 创建视图需要使用_语句_在MySQL中创建视图的X种方式
  2. UE5 C++教程(三、多人游戏网络基础)
  3. 网关报错:Load balancer does not have available server for client: xxx
  4. 李宏毅机器学习-- RNN
  5. Python - OpenCV 图像二值化处理
  6. java怎么做摇杆_DJI虚拟摇杆控制未正确应用
  7. linux github安装 Pytorch
  8. c语言保留三位小数用float,float保留三位小数
  9. 毕设专用 基于Vue的大病保险管理系统 这个开源项目你值得拥有
  10. 小白能读懂的 《手把手教你学DSP(TMS320X281X)》第四章(3) 创建新工程