美文网首页Android Other开源项目Android 无障碍
【为生活开发系列之二】Android微信新版全自动抢红包助手

【为生活开发系列之二】Android微信新版全自动抢红包助手

作者: Roll圈圈 | 来源:发表于2018-02-10 13:36 被阅读465次

    前言

    新的一年又到了,又到了拼手速和网速的时候了,网速是硬件条件,没有办法了,不过手速这种东西,没有还不能创造么,哈哈。其实之前网上有很多老铁已经分享过类似的插件的实现方式,但是微信其实本身也是在做对第三方插件的规避操作,所以,微信的每一个新版本都会修改相同控件的id,所以之前的很多插件都不能再使用了,而且之前的有些判断方法也不能再适用新版本的微信,所以我研究了几天,新版全自动微信抢红包助手就应运而生了,老规矩,给大家看下效果。

    电点红包助手

    主要功能介绍

    • 具有监听通知栏红包消息的功能,发现红包自动跳转页面抢红包
    • 聊天页面实时监控私信和群红包
    • 一旦发现红包,自动进入聊天页面,从下往上依次遍历未抢过的红包,点击进入抢红包界面
    • 自动点击“开”按钮,完成自动收红包动作
    • 聊天页面红包抢完之后,自动回到聊天列表页面,继续监听下一个红包的到来,做到红包遗漏少,成功率高。

    技能点介绍

    一、核心中的核心(无障碍服务的使用)

    全自动抢红包无非也就是写个逻辑代替你手动点击的过程,要实现这个功能,就要用到Android提供的无障碍服务(AccessibilityService)的功能。辅助功能可以得到系统级别的事件和服务,通过这些事件和服务,我们就能监控微信的红包消息,不过第三方应用的辅助功能都需要手动开启。

    关于AccessibilityService的使用,简单的介绍下,不做过多的介绍,简单的分成三部:
    第一步:自定义一个服务继承自AccessibilityService,重写对应的方法

     package com.cretin.www.redpacketplugin.services;
    
    import android.accessibilityservice.AccessibilityService;
    import android.annotation.TargetApi;
    import android.os.Build;
    import android.view.accessibility.AccessibilityEvent;
    
    /**
     * Created by cretin on 2018/2/9.
     */
    public class RedPackageService extends AccessibilityService {
    
        @Override
        public void onCreate() {
            super.onCreate();
        }
    
        @Override
        protected void onServiceConnected() {
            //系统成功连接到辅助功能服务时调用
            super.onServiceConnected();
        }
    
        @TargetApi( Build.VERSION_CODES.JELLY_BEAN_MR2 )
        @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {
            //当系统检测到与Accessibility服务指定的事件过滤参数
            // 匹配的AccessibilityEvent时调用
        }
    
        @Override
        public void onInterrupt() {
            //当系统想要中断服务提供的反馈时调用
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            //当系统即将关闭辅助功能服务时调用
        }
    
    }
    
    
    

    第二步:给辅助服务书写配置文件

    <?xml version="1.0" encoding="utf-8"?>
    <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
                           android:accessibilityEventTypes="typeAllMask"
                           android:accessibilityFeedbackType="feedbackSpoken"
                           android:accessibilityFlags="flagDefault"
                           android:canRetrieveWindowContent="true"
                           android:description="@string/accessibility_service_description"
                           android:notificationTimeout="100"
                           android:packageNames="com.tencent.mm"
                           android:settingsActivity="com.cretin.www.redpacketplugin.android.accessibility.ServiceSettingsActivity"/>
    

    对属性做一个简单的解释
    accessibilityEventTypes:响应那种类型的事件
    accessibilityFeedbackType:设置回馈给用户的方式,有语音播出和振动
    notificationTimeout:响应时间
    packageNames:指定响应哪个应用的事件。这里填的是微信的包名,如果不填则是响应所有的应用事件
    description:辅助服务的描述信息,会显示在无障碍服务的描述那里。

    第三步:注册服务

    <service
                android:name=".services.PackageAccessibilityService"
                android:description="@string/accessibility_service_description"
                android:enabled="true"
                android:exported="true"
                android:label="@string/accessibility_service_label"
                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/accessibility_service_config"/>
            </service>
    

    属性的简单说明

    //辅助功能的名称
    android:label="@string/accessibility_service_label"   
    //此处必须声明一次权限
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" 
    //指定配置文件的名字和位置
    android:name="android.accessibilityservice"
    android:resource="@xml/accessibility_service_config"
    

    做好上面的准备工作后,我们就可以在onAccessibilityEvent(AccessibilityEvent event)方法中写我们具体的逻辑了。

    二、针对通知栏事件非通知栏事件分开处理

    看过之前老铁的处理方式是对AccessibilityEvent中getEventType来判断是所有类型,经过实验这种方式是不可靠的,经过多次测试,最终我觉得用getEventType只判断是否是通知栏事件比较靠谱。

    if ( event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED ) {
          //通知栏事件
    } else {
          //非通知栏事件    处理其他事件
    }
    

    三、对非通知栏事件做细分处理

    因为通知栏事件比较简单,直接点击通知栏就好了,点击通知栏后会自己跳转到聊天页面,剩下的事情也是交给对非通知栏事件来处理。

    那么现在需要考虑的事情有以下几点:
    第一:如何获取我们希望处理的控件并操控它。
    第二:如何判断当前在哪个页面,是聊天列表页面,是聊天页面,是打开红包的页面还是打开红包后的详情页面。
    第三:在不同的页面我们需要做什么事情,点击哪个控件。


    3.1、如何获取我们希望处理的控件并操控它。

    获取一个有文本的控件有两种方式,一种是根据文本找控件,一种是根据id找控件,对于没有文本的控件,就使用id找控件。找到控件之后可以对控件主动触发一定的事件,比如最常用的点击事件。

    //获取整个窗口根节点
    AccessibilityNodeInfo nodeInfo = getService().getRootInActiveWindow();
    //根据id获取所有使用这个id的控件节点集合
    List<AccessibilityNodeInfo> idNodes =
                            nodeInfo.findAccessibilityNodeInfosByViewId(VIEW_ID_RECEIVE_BTN_OPEN);
    //根据内容获取所有这个有这个文本的控件节点集合
    List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(TEXT_LINGQUHONGBAO);
    //对控件主动触发事件(这里触发的是点击事件,其他事件类型可自行研究 AccessibilityNodeInfo)
    if(!idNodes.isEmpty()){
        idNodes.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK);
    }
    

    ?问题:那么图和获取控件的id呢?
    找到uiautomatorviewer后点击运行。


    找到uiautomatorviewer

    按如下操作就可以获取到控件id(记得插上手机或开启模拟器,手机或模拟器开启调试模式)


    获取控件id
    3.2、如何判断当前在哪个页面

    就目前来看,我们需要区分聊天列表页面(就是微信的首页),聊天页面(包括私信和群聊天),点击红包后的红包页面(这里包括两种情况,一种是红包还没有被别人抢,点“开”按钮会进入到详情页面,还有一种是红包被别人抢了,此时点击“开”出现的是“手慢了,红包派完了”的页面)和开红包后的详情页面。

    3.2.1、判断聊天列表页面

    看过之前老铁判断首页的方式是判断className,因为回到首页的时候className是com.tencent.mm.ui.LauncherUI(这个值也不是永恒不变的,要根据微信版本来),但是经过多次测试,当不在微信首页,在其他页面的时候,也会触发这个className,所以不靠谱。

    后来经过多次测试,发现获取首页listview的item列表项的id,这个id只会在首页聊天列表页面出现,所以我就按照这个方式来确定当前页面是不是首页。

    List<AccessibilityNodeInfo> listItemNodes =
                                nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/apr");
    if ( listItemNodes.isEmpty() ) {
           //反正不是在首页 不理会
           return;
    } else {
          //在首页
    }
    
    listview的列表项id
    3.2.2、判断聊天页面

    其实判断在哪个页面,最主要的就是找其他页面没有的特征控件,比如在聊天页面中,右下角那个“+”按钮才是最独特的,所以可以根据是否有这个按钮来判断是否是聊天页面。但是这个只能判断是否是聊天页面,不能判断是私信页面还是群聊页面。在对比了私信和群聊的页面之后,没有找到特别稳的方式来判断聊天类型,只能根据标题来判断,群聊的标题后面一定会有一个括号,括号里面是群成员人数。所以我们只需要来判断标题最后是否有一个括号里面是数字,当然这种方式不是特别准,不过够用了,一般用户也不会这个起昵称,万一这样起了也只是判断类型出错,也不会影响抢红包的功能。

     List<AccessibilityNodeInfo> chatNodes =
                            nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/aak");
     if ( chatNodes.isEmpty() ) {
            //不在聊天页面 不好说在哪儿
    }else{
            //在聊天页面
            List<AccessibilityNodeInfo> titleNodes =
                                nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/ha");
            if ( titleNodes.isEmpty() ) {
                     //无法判断类型
            } else {
                    //判断标题最后是否是一个括号,括号中是数字,当然最好是用正则
                    String title = titleNodes.get(0).getText().toString();
                            if ( !TextUtils.isEmpty(title) ) {
                                if ( title.contains("(") ) {
                                    int indexLeft = title.lastIndexOf("(");
                                    String end = title.substring(indexLeft);
                                    end = end.substring(1, end.length() - 1);
                                    try {
                                        Integer.parseInt(end);
                                        //群聊
                                    } catch ( Exception e ) {
                                        //私聊
                                    }
                                } else {
                                    //私聊 默认私聊
                                }
                            }
            }
    }
    
    添加按钮
    标题
    3.2.3、判断打开红包的页面

    还记得之前提到过的className吗,打开红包和红包详情页面就可以用这个了,别问我为什么知道啥时候用className,啥时候自己判断控件,这都是几十次调试和实验得到的。%>_<%
    经过实现,我们发现了,弹出红包页面的className是com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI,所以我们只需要判断当前的className是这个就可以判断出当前是打开红包的页面。但是还有一种情况就是打开红包后有可能是红包已经被别人抢完了,所以此时会显示“手慢了,红包派完了”页面,这个页面的className也是这个,所以单单靠这个是不能准确判断的。我们依然需要找这两个页面的特征控件。

    在“开”红包页面,特征元素是“开”按钮,在“手慢了,红包派完了”页面,特征元素是“手慢了,红包派完了”所在的控件。

            String className = event.getClassName().toString();
            if ( "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI".equals(className) ) {
                    //点中了红包 有两种操作 一种是点开红包  一种是手慢了
                    /**
                     * 一种是点开红包
                     */
                    //获取开按钮
                    List<AccessibilityNodeInfo> kaiNodes =
                            nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/c2i");
                    //获取 手慢了 提示语句的控件
                    List<AccessibilityNodeInfo> slowNodes =
                            nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/c2h");
                    //获取关闭按钮
                    List<AccessibilityNodeInfo> closeNodes =
                            nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/c07");
                    if ( !kaiNodes.isEmpty() ) {
                        //获取到开按钮 点击此按钮 
                        NotifyHelper.playEffect(getContext(), getConfig());
                        AccessibilityHelper.performClick(kaiNodes.get(0));
                    } else {
                        if ( !slowNodes.isEmpty() && !closeNodes.isEmpty() )
                            //手慢了 提示语句的控件 关闭对话框
                            AccessibilityHelper.performClick(closeNodes.get(0));
                    }
                } 
    
    开红包 手慢了 返回按钮
    3.2.4、判断打开红包后的详情页面

    这个页面是最简单的,根据className来判断,如果是这个页面,直接点击返回按钮就好了。className值为com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI。

         String className = event.getClassName().toString();
         if ( "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI".equals(className) ) {
                    //拆完红包后看详细的纪录界面 这里退出就好
                    //获取关闭按钮
                    List<AccessibilityNodeInfo> closeNodes =
                            nodeInfo.findAccessibilityNodeInfosByViewId(VIEW_ID_DETAIL_CLOSE);
                    if ( !closeNodes.isEmpty() ) {
                        //关掉
                        AccessibilityHelper.performClick(closeNodes.get(0));
                        return;
                    } else {
                        AccessibilityHelper.performBack(getService());
                    }
                } 
    

    3.3、判断打开红包的页面在不同的页面我们需要做什么事情,点击哪个控件。

    其实上面已经捎带分析了一些。我们从首页开始分析

    • 首页
      这个页面只需要做一件事,就是监听列表信息的变化,当聊天列表中的消息出现了”[微信红包]“字样,说明有人发红包,那么此时点击那条消息,进入到聊天页面。但是这里需要注意一点,如果你没有抢到红包,红包被别人抢完了,那么你的聊天列表依然显示的是"[微信红包]",如果不处理这种情况,你就会进入到一种死循环的情况,首页说有红包,跳转聊天页面,聊天页面说没有,返回来,首页又说有......但其实,这些红包早就已经不能抢了,所以这样的消息就需要屏蔽掉,不能跳转页面,那么有什么好的办法吗?答案是并没有。
      那怎么办?我们只能通过消息的未读数来判断,如果当前列表项的未读数不为0,而且聊天内容中有”[微信红包]“字样是,才跳转页面,这样就可以防止上面的情况发生。
                        //获取首页的listview 的 item 的 列表
                        List<AccessibilityNodeInfo> listItemNodes =
                                nodeInfo.findAccessibilityNodeInfosByViewId(VIEW_ID_HOME_LV_ITEM);
                        if ( listItemNodes.isEmpty() ) {
                            //反正不是在首页 不理会
                            return;
                        } else {
                            //在首页
                            List<AccessibilityNodeInfo> nodes = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/apr");
                            if ( nodes != null ) {
                                for ( AccessibilityNodeInfo node :
                                        nodes ) {
                                    if ( node.getText().toString().contains("[微信红包]") ) {
                                        //还要判断是否有未读消息
                                        AccessibilityNodeInfo parent = node.getParent();
                                        if ( parent != null ) {
                                            List<AccessibilityNodeInfo> numsNodes =
                                                    parent.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/iu");
                                            if ( !numsNodes.isEmpty() ) {
                                                CharSequence text = numsNodes.get(0).getText();
                                                if ( text != null ) {
                                                    if ( Integer.parseInt(text.toString()) != 0 ) {
                                                        //此时才能跳转
                                                        AccessibilityHelper.performClick(parent);
                                                    }
                                                }
                                            }
                                        }
                                        return;
                                    }
                                }
                            }
                        }
    
    • 聊天列表
      在这个页面中,我们需要判断遍历当前的listview的item项,找出当前页面中所有含有“领取红包”的listview的列表项,并判断当前的这个item是不是红包,如果是红包,则模拟用户点击点开这个页面,进到收红包的逻辑中。这里在判断是不是红包的过程中,我们使用双重保险的方式,先找到"领取红包"的控件节点,再查看该节点对应的id是不是正确的,两者同时满足才能保证他是红包。防止用户发送纯文字“领取红包”来影响程序的判断。
                        //在聊天页面
                        List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("领取红包");
                        if ( list == null )
                            return;
                        if ( list.isEmpty() ) {
                            //没有 直接返回
                            List<AccessibilityNodeInfo> backNodes =
                                        nodeInfo.findAccessibilityNodeInfosByViewId(VIEW_ID_CHATTING_TV_BACK);
                            if ( !backNodes.isEmpty() ) {
                                    AccessibilityHelper.performClick(backNodes.get(0));
                             }
                        } else {
                            //有 但是要检查是不是红包
                            for ( int i = list.size() - 1; i >= 0; i-- ) {
                                AccessibilityNodeInfo node = list.get(i);
                                AccessibilityNodeInfo parent = node.getParent();
                                if ( parent != null ) {
                                    List<AccessibilityNodeInfo> wxhbNodes =
                                            parent.findAccessibilityNodeInfosByViewId(VIEW_ID_HOME_LV_ITEM_LABEL_WXHB);
                                    if ( !wxhbNodes.isEmpty() ) {
                                        if ( TEXT_LV_ITEM_TIPS.equals(wxhbNodes.get(0).getText()) ) {
                                            //是的 没错  领取红包
                                            AccessibilityHelper.performClick(node);
                                            return;
                                        }
                                    }
                                }
                            }
                        }
    
    • 抢红包页面和详情页面的处理在上面已经说明了,在此就不再赘述了。

    结语

    基本上有了上面这些踩坑的经历,一个红包助手的架子基本也就齐全了。自己再加一些逻辑上的判断和功能上的私人订制,一个过年的工具就诞生了。

    由于微信每个版本对于同一个控件的id都会做改变,所以,我们需要对不同的微信版本做适配,否则在使用过程中可能会出现意想不到的问题。以下是我整理的微信不同版本的我们所需要的控件的id的汇总,您看着是密密麻麻,我整理起来也是很辛苦的,小小心意,祝大家新年快乐。

    微信版本 微信版本号 打开红包的CLASSNAME 点开红包的开按钮ID 红包详情的CLASSNAME 首页列表未读数ID 手慢了ID 聊天标题ID 聊天右下角添加ID 首页聊天内容ID 点开红包的返回按钮ID 聊天页面返回按钮ID 红包详情返回按钮ID
    v6.6.2 1240 com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI com.tencent.mm:id/c4j com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI com.tencent.mm:id/j4 com.tencent.mm:id/c4i com.tencent.mm:id/hj com.tencent.mm:id/aag com.tencent.mm:id/apt com.tencent.mm:id/c28 com.tencent.mm:id/hi com.tencent.mm:id/hy
    v6.6.1 1220 com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI com.tencent.mm:id/c2i com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI com.tencent.mm:id/iu com.tencent.mm:id/c2h com.tencent.mm:id/ha com.tencent.mm:id/aak com.tencent.mm:id/apv com.tencent.mm:id/c07 com.tencent.mm:id/h_ com.tencent.mm:id/hp
    v6.6.0 1200 com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI com.tencent.mm:id/c22 com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI com.tencent.mm:id/iu com.tencent.mm:id/c21 com.tencent.mm:id/ha com.tencent.mm:id/aa4 com.tencent.mm:id/apf com.tencent.mm:id/bzq com.tencent.mm:id/h_ com.tencent.mm:id/hp
    v6.5.23 1180 com.tencent.mm.plugin.luckymoney.ui.En_fba4b94f com.tencent.mm:id/bx4 com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI com.tencent.mm:id/io com.tencent.mm:id/bx3 com.tencent.mm:id/h5 com.tencent.mm:id/aa6 com.tencent.mm:id/aol com.tencent.mm:id/bus com.tencent.mm:id/h4 com.tencent.mm:id/hj
    v6.5.22 1160 com.tencent.mm.plugin.luckymoney.ui.En_fba4b94f com.tencent.mm:id/bwn com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI com.tencent.mm:id/io com.tencent.mm:id/bwm com.tencent.mm:id/h5 com.tencent.mm:id/aa6 com.tencent.mm:id/aol com.tencent.mm:id/bub com.tencent.mm:id/h4 com.tencent.mm:id/hj
    v6.5.19 1140 com.tencent.mm.plugin.luckymoney.ui.En_fba4b94f com.tencent.mm:id/bv8 com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI com.tencent.mm:id/il com.tencent.mm:id/bv7 com.tencent.mm:id/h2 com.tencent.mm:id/a9t com.tencent.mm:id/an9 com.tencent.mm:id/bsv com.tencent.mm:id/h1 com.tencent.mm:id/hg
    v6.5.16 1120 com.tencent.mm.plugin.luckymoney.ui.En_fba4b94f com.tencent.mm:id/brt com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI com.tencent.mm:id/il com.tencent.mm:id/brs com.tencent.mm:id/h2 com.tencent.mm:id/a76 com.tencent.mm:id/ak3 com.tencent.mm:id/bph com.tencent.mm:id/h1 com.tencent.mm:id/hg
    v6.5.13 1100 com.tencent.mm.plugin.luckymoney.ui.En_fba4b94f com.tencent.mm:id/bp6 com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI com.tencent.mm:id/ie com.tencent.mm:id/bp5 com.tencent.mm:id/gz com.tencent.mm:id/a6l com.tencent.mm:id/aje com.tencent.mm:id/bmu com.tencent.mm:id/gy com.tencent.mm:id/hd

    源码相关

    最上面提供的动态图是我给周围朋友做的一个全自动红包插件,由于项目中有后台接口,是为了动态加载一些配置文件,让app体验更好,免得每次微信有新版本都要更新app,而且加了很多其他方面的判断,比较复杂,所以源码就不再放出来了。相信经过上面的分析,自己撸一个也不困难。

    最后也为我上面的app打一个小广告,名称:电点红包助手,适配了大部分机型,我相信在过年的这段时间,有很多人也还是需要这么一款app的,毕竟在家要多陪陪家人,老玩手机不好,但又不想错过几百万的红包,那么这个助手正适合你,哈哈。

    下面是app的下载链接,微信扫一扫或者直接用浏览器扫一扫都行,不过用微信扫一扫记得点击右上角的在浏览器中打开才能下载,毕竟这种东西任何的应用市场都是不让上传的。

    最后,祝大家新的一年,赚大钱。

    我叫Cretin,一个可爱的小男孩。

    电点红包助手下载地址

    相关文章

      网友评论

      • Showdy:一天的试用时间,收费的
        Roll圈圈:@Showdy 毕竟服务器也要养 哈哈
      • Dr_:公司年会成功的被红包插件恶心了一把
        Roll圈圈:@Dr_ 再简陋也是年会呀 还有红包 就不简陋了
        Dr_:@电点mxn 😂年会很简陋
        Roll圈圈:@Dr_ 哈哈 真羡慕你们有年会

      本文标题:【为生活开发系列之二】Android微信新版全自动抢红包助手

      本文链接:https://www.haomeiwen.com/subject/voxqtftx.html