[关闭]
@Tyhj 2020-01-01T15:51:26.000000Z 字数 9026 阅读 890

Android微信抢红包辅助

Android


阅读原文:https://www.jianshu.com/p/5a44b6eaba20

快到年底了,又到了拼手速抢红包的时候了;其实很早之前就做过抢红包软件了,包括QQ和微信;但是大家都懂的,自己一个月前写的代码现在看起来都像是一坨shit一样;所以自己开始重新写一个抢红包的软件(其实是因为实在是太简单了),只做微信,因为QQ发红包的确用的太少了,而且QQ红包花样也太多了,什么唱歌、画画、成语接龙...

luckCat.png-97.5kB

目标

  1. 快,天下武功无坚不摧、唯快不破,肯定要比人的手速快
  2. 准,只要你手机解锁了,在任意一个界面都可以快速抢到红包
  3. 狠,其实狠不狠没什么关系了,最重要的是全自动,自己不用任何操作,不然怎么解放双手
  4. 稳,肯定要能一直抢红包,来一个抢一个,来两个抢两个,抢红包一时爽,一直抢一直爽;

手机配置要求

  1. Android系统 7.0及以上,辅助功能7.0以上支持模拟点击,模拟点击不是必须的,但是对于实现很重要
  2. 手机不能太垃圾了,手机慢有外挂也发挥不出来呀

实现原理

实现方法就是利用Android辅助功能,开启辅助功能相当于开启了一个服务,在手机界面改变的时候,就能监听到该页面的一些信息并且能拿到界面的一些控件,然后可以对控件进行模拟点击,从而实现我们想要的功能。

除此以外,不仅能够对获取到的控件进行模拟点击,在Android7.0及以上的版本,我们可以模拟任意位置的点击包括触摸、滑动等等,就是说我们可以实现任何人能够进行的操作,这个是很有用的,可以做出很多有意思的东西,如果再配上截图、录屏和图像识别,就更有意思了。

模拟点击,就是说我们的手机界面自己动,整个流程像是一只手在帮你操作一样的;其实我见过更牛逼的方法,连解锁都不需要直接就领了红包,界面没有任何变化的;感觉上是通过通信,发数据给微信服务器实现的,当然这种是需要root权限的,并且得去解析微信的通信协议,我自然没时间去搞(其实有时间也不一定能搞出来)。

具体实现

辅助功能

首先是辅助功能,新建一个Service继承AccessibilityService

  1. public class LuckMoneyService extends AccessibilityService

然后去AndroidManifest文件里面去注册一下这个Service

  1. <service
  2. android:name=".service.LuckMoneyService"
  3. android:label="小圆脸的红包助手"
  4. android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
  5. <intent-filter>
  6. <action android:name="android.accessibilityservice.AccessibilityService" />
  7. </intent-filter>
  8. <meta-data
  9. android:name="android.accessibilityservice"
  10. android:resource="@xml/accessible_service_wx_config" />
  11. </service>

meta-data节点下有个resource值,这是个xml文件,里面配置了该辅助的一些信息,在res目录下新建一个文件夹,名字叫xml,然后新建一个xml文件,名字和resource配置的一样就行了

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged|typeNotificationStateChanged"
  4. android:accessibilityFeedbackType="feedbackAllMask"
  5. android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews|flagReportViewIds"
  6. android:canRetrieveWindowContent="true"
  7. android:canRequestFilterKeyEvents="true"
  8. android:description="@string/wx_luck_money"
  9. android:canRequestEnhancedWebAccessibility="true"
  10. android:notificationTimeout="20"
  11. android:packageNames="com.tencent.mm"
  12. android:canPerformGestures="true" />

里面配置了一些参数,比如notificationTimeout是指定多少毫秒监听一次界面变化的,packageNames是指定监听哪个应用的,删掉这个配置就是监听全局,建议一定要删除掉,我这里只是展示用,description是对于该辅助的描述,其他配置不管也罢。

然后在LuckMoneyService里面重写一下onAccessibilityEvent方法

  1. @Override
  2. public void onAccessibilityEvent(AccessibilityEvent event) {
  3. //界面发生了变化
  4. }

每当界面改变的时候就会回调这个方法,通过event我们就可以获取到界面的信息包括界面上的控件

简单的用法

  1. //获取当前界面包名
  2. String packageName = event.getPackageName().toString();
  3. //获取当前类名
  4. String className = event.getClassName().toString();
  5. //获取当前界面父布局的控件
  6. AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
  7. //在父布局里面根据子控件**显示的文字**找到该子控件
  8. List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text);
  9. //在父布局里面根据子控件的**id**找到该子控件
  10. List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);
  11. //点击该控件
  12. nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);

上面的操作都比较基础,根据控件显示的文字查找控件,找出来的肯定是TextView和Button了,根据ID查找控件,ID就是指的写布局文件的时候设置的控件的ID

模拟触摸

模拟触摸就是可以模拟人的触摸动作,也比较简单

  1. protected void gestureOnScreen(Path path, long startTime, long duration,
  2. AccessibilityService.GestureResultCallback callback) {
  3. GestureDescription.Builder builde = new GestureDescription.Builder();
  4. builde.addStroke(new GestureDescription.StrokeDescription(path, startTime, duration));
  5. GestureDescription gestureDescription = builde.build();
  6. dispatchGesture(gestureDescription, callback, null);
  7. }

可以看到需要传入path就是一个路径嘛,模拟滑动的路径,用canvas画过画的都知道这东西还是比较简单的,不清楚也没关系,继续看,startTime就是多久后开始模拟事件,duration就是该滑动的时间,其他回调什么的为空就可以了;

辅助功能能做的东西大概就上面这些了,接下来看看

微信应用外的红包处理

首先实现在微信界面外怎么抢红包,在微信界面外有红包出现必然会在通知栏会显示微信红包(如果没开通知消息,那你自己开一下不就完事了吗),只需要在回调方法里面判断一下是不是通知消息,如果是通知消息,获取里面的信息,判断是不是微信红包通知消息,是就点击该消息,会自动跳转到聊天界面;

因为我们是监听界面变化来实现功能的,所以在一个界面触发了界面变化的时候,接下来的处理就应该交给下一个界面的方法了,所以微信界面外的操作就是这些了

  1. /**
  2. * 红包标识字段
  3. */
  4. public static final String HONG_BAO_TXT = "[微信红包]";
  5. //通知栏消息,判断是不是红包消息
  6. if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) {
  7. Notification notification = (Notification) event.getParcelableData();
  8. //获取通知消息详情
  9. String content = notification.tickerText.toString();
  10. //解析消息
  11. String[] msg = content.split(":");
  12. String text = msg[1].trim();
  13. if (text.contains(HONG_BAO_TXT)) {
  14. PendingIntent pendingIntent = notification.contentIntent;
  15. try {
  16. //点击消息,进入聊天界面
  17. pendingIntent.send();
  18. } catch (PendingIntent.CanceledException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }

其中PendingIntent这个东西写过通知栏的都知道,这个是设置跳转到哪个界面的,所以直接调用它的方法就完成了界面跳转了

聊天界面的红包处理

界面外的红包点击通知栏消息就来到了聊天界面,其实所有的界面都必须经过这个界面才能领取到红包,所以这个界面很重要;

实现

思路是这样的,聊天消息肯定是一个列表控件,其实是个ListView,而且肯定有控件ID,我们获取到这个ListView,然后遍历它的每个消息(只能遍历到当前界面显示的),判断这个消息是不是微信红包,如果是,并且未被领取,而且这个红包还得是别人发的,不是自己发的,我们才去点击这个消息,触发界面变化,然后丢给下一个界面处理;

  1. //获取聊天消息列表List控件
  2. AccessibilityNodeInfo nodeInfo = findViewByID(DETAIL_CHAT_LIST_ID);
  3. //这个消息列表不为空,那么肯定在聊天详情页
  4. if (nodeInfo != null) {
  5. //判断有没有未领取红包并进行点击
  6. clickItem(nodeInfo);
  7. return;
  8. }
  9. /**
  10. * 进行消息列表未领取红包的点击
  11. *
  12. * @param nodeInfo
  13. */
  14. private void clickItem(AccessibilityNodeInfo nodeInfo) {
  15. //遍历消息列表的每个消息
  16. for (int i = 0; i < nodeInfo.getChildCount(); i++) {
  17. //获取到子控件
  18. AccessibilityNodeInfo nodeInfoChild = nodeInfo.getChild(i);
  19. //获取红包控件
  20. AccessibilityNodeInfo target = findViewByID(nodeInfoChild, AUM_ID);
  21. //获取头像的控件
  22. AccessibilityNodeInfo avatar = findViewByID(nodeInfoChild, AVATAR_ID);
  23. boolean selfLuckMoney = false;
  24. //获取头像的位置,判断红包是否是自己发的,自己发的不抢
  25. if (avatar != null) {
  26. Rect rect = new Rect();
  27. avatar.getBoundsInScreen(rect);
  28. if (rect.left > screenWidth / 2) {
  29. selfLuckMoney = true;
  30. }
  31. }
  32. //如果不是自己发的红包,并且获取到的微信红包这个控件不为空
  33. if (target != null && !selfLuckMoney) {
  34. //已领取这个控件为空,红包还没有被领取
  35. if (findViewByID(nodeInfoChild, AUL_ID) == null) {
  36. //点击红包控件
  37. performViewClick(target);
  38. return;
  39. }
  40. }
  41. }
  42. }

里面每个细节都注释了,获取ListView控件,获取到了说明是在消息界面,获取到消息列表的每一个控件,根据 是否是红包消息,是否是别人发的,是否是未领取的三点,去判断是否是可以领取的红包,然后点击可领取的红包,到达弹出的这个弹窗的界面;

monitor

如何获取这个ListView控件的ID呢,而我又是如何知道是ListView的呢,可以通过一个工具来实现,就是在sdk工具下面的一个叫monitor的工具,其实之前的AndroidStudio是带这个工具的,但是后来界面上是没有了,但是其实还在的

  1. /Users/Tyhj/Library/Android/sdk/tools/monitor

连上手机,打开这个工具,手机上打开你要查看的界面,点击工具手机的小手机的图标,就会截屏,显示出这个界面的信息
截屏2019-12-09上午1.00.04.png-752.2kB

红包弹窗界面处理

截屏2019-12-09上午1.11.52.png-613.4kB

同样的红包弹窗这个界面也是必须经过的,十分重要;你要说这个弹窗界面也比较简单,我们判断一下是不是这个界面,然后点击开不就完事儿了;测试可以发现,这个弹窗出现的时候,当前的界面className是这个

  1. **
  2. * 红包弹出的class的名字
  3. */
  4. private static final String ACTIVITY_DIALOG_LUCKYMONEY = "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI";

事情没有这么容易,当我去获取这个的这个控件的时候,发现为空,获取不到,其实整个弹窗都获取不到,遇到这个问题的人肯定不少;

  1. //获取開的控件布局
  2. AccessibilityNodeInfo target = findViewByID("com.tencent.mm:id/dan");

其实深究下去,发现获取根布局都为空了,测试发现必须等待一段时间再去获取这个弹窗才行,但是等多久呢,大概几百毫秒吧,不定时的,不同手机也不一定,那么随便设一个就不行,因为你时间设置小了,程序可能会卡在这里抢不了红包了,肯定不行;设置大了,行,但是影响速度呀。那么开个循环去获取直到获取到不为空行吗?不行,奇怪的就是你一次去获取为空了,之后获取都为空了;只有等待一段时间后第一次去获取才不为空,这TMD就很奇怪了,看了一下的确没法解决;这个问题其实和手机有关,在三星s9上的确有问题,在华为nova5 pro上没问题

  1. //获取根布局
  2. AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();

如果获取不到,那其实还有个办法就是模拟点击,在红包弹窗弹出来的时候,我疯狂点击这个開字的位置,就行了;開字的位置可以通过屏幕比例来计算出来,这个就算不同的手机屏幕都可以点击到这个開字;但是其实还是有个问题,弹窗弹出其实有个动画,不同的手机其实弹出的时间也不不一样,华为nova5 pro都不用等待,可以直接执行点击操作,三星s9得等待200ms左右

  1. //当前为红包弹出窗(那个开的那个弹窗)
  2. if (className.equals(ACTIVITY_DIALOG_LUCKYMONEY)) {
  3. //进行红包开点击
  4. clickOpen();
  5. return;
  6. }
  7. /**
  8. * 点击开红包按钮
  9. */
  10. private void clickOpen() {
  11. //等待红包弹窗完成,直接使用模拟点击比较快,根据手机性能等待响应的时长
  12. SystemClock.sleep(100);
  13. for (int i = 0; i < 20; i++) {
  14. SystemClock.sleep(10);
  15. //计算了一下这个開字在屏幕中的位置,按照屏幕比例计算
  16. clickOnScreen(screenWidth / 2, screenHeight * POINT_Y_SCAL, 1, null);
  17. }
  18. /*AccessibilityNodeInfo target = findViewByID("com.tencent.mm:id/dan");
  19. if (target != null) {
  20. performViewClick(target);
  21. return;
  22. } else {
  23. //如果没有找到按钮,再进行模拟点击
  24. for (int i = 0; i < 20; i++) {
  25. SystemClock.sleep(10);
  26. clickOnScreen(screenWidth / 2, screenHeight * POINT_Y_SCAL, 1, null);
  27. }
  28. }*/
  29. }

点击了这个開字后,进入了红包详情页,进行下一步处理。

红包详情页处理

进入了红包详情页,红包已经到手了,想要继续抢红包,肯定需要退出去,这个简单,有返回键的方法;这时候你可以返回聊天界面继续抢这个群的红包(如果专抢一个群的,这样效率高),也可以返回到最近消息列表(微信主页面第一个界面),可以抢其他群的红包(抢其多个群的红包,这样效率高),也可以退回手机主界面(抢红包效率低,因为还需要点击通知栏消息进去);可以设置一下,如果开启专抢一个群,就退回该群聊天界面,否则退回最近消息列表界面。

  1. //红包领取后的详情页面,自动返回
  2. if (className.equals(LUCKY_MONEY_DETAIL)) {
  3. //返回聊天界面
  4. performGlobalAction(GLOBAL_ACTION_BACK);
  5. //如果不是专抢一个群
  6. if (!isSingle) {
  7. SystemClock.sleep(50);
  8. performGlobalAction(GLOBAL_ACTION_BACK);
  9. }
  10. return;
  11. }

最近消息列表界面处理

截屏2019-12-09上午2.01.36.png-541kB

当领完红包后,退出到最近消息列表界面是比较好的选择;这个界面上当收到红包消息通知栏是不会有提醒的;我们需要根据界面的显示去判断有没有红包;其实也是特别简单,它也是一个ListView,同样的遍历一下每个item,判断有没有微信红包消息,然后点击进入聊天消息界面

  1. //在最近聊天列表,检测有没有红包消息出现
  2. nodeInfo = findViewByID(HUMAN_LIST);
  3. //联系人列表
  4. if (nodeInfo != null) {
  5. //判断最近聊天列表有没有未领取红包
  6. clickHumanItem(nodeInfo);
  7. return;
  8. }
  9. /**
  10. * 进行联系人列表的红包消息点击
  11. *
  12. * @param nodeInfo
  13. */
  14. private void clickHumanItem(AccessibilityNodeInfo nodeInfo) {
  15. for (int i = 0; i < nodeInfo.getChildCount(); i++) {
  16. AccessibilityNodeInfo nodeInfoChild = nodeInfo.getChild(i);
  17. AccessibilityNodeInfo target = findViewByID(nodeInfoChild, HUMAN_LIST_TXT_ID);
  18. if (target != null && target.getText() != null && target.getText().toString().contains(HONG_BAO_TXT)) {
  19. performViewClick(target);
  20. return;
  21. }
  22. }
  23. }

看似没有问题,实则有一个问题,就是在这个聊天列表里面,没法判断这个红包是别人发的还是你自己发的,如果是你自己发的那肯定有问题的,这是一个坑,当然可以通过保存一些数据,比如说第一次进去后发现是自己发的红包就退出来,如果界面没变化第二次就不再进行点击了;但是其实问题也不大吧,最多就是你发完红包后自己再发个消息就可以避免了。

测试总结

其实到这里就全完成了,实际效果也不错,测了一下,4个人和一个辅助比,发了20次红包,辅助大概能抢到18次吧,并不是百分百抢到,主要是人有准备的话疯狂点屏幕其实也挺快的(单身20年的同学的手速不得不服,毕竟有个地方我还是sleep了100毫秒的,其实去掉应该更快的),一般情况下辅助还是有绝对优势的。

一般情况下感觉用到的这些控件ID、布局、界面所在的类、包名什么的是不太会改变的,当然如果微信版本升级比较大,估计布局什么的有变化,还得根据新的布局去重新实现,但是思路其实都是一样的。

更新

更新前抢一次红包时间大概为1300毫秒左右,更新了弹窗等待那一步,不等待直接模拟点击一次也是可以的(自己手机测试通过),更新后时间减少到1000毫秒左右;自己测试手速抢红包,时间大概是1400毫秒以上,感觉真的比人手速快了

屏幕快照 2019-12-28 下午4.59.05.png-283.4kB

项目地址

里面有一些方法是封装了的,方便调用,具体实现可以看代码
原文地址:Android微信抢红包辅助
github地址:Android微信抢红包辅助
软件下载地址(老版本):https://github.com/tyhjh/LuckMoney/raw/master/%E6%8A%A2%E7%BA%A2%E5%8C%85.apk

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注