Android微信抢红包插件原理和实现 适配微信6.6.1版本

Android微信抢红包插件原理和实现 适配微信6.6.1版本

一、前言

年关将至,小伙伴们又聊起了春节长假的旅行安排和各自家乡的年味习俗,不一而足。其中在各个微信群聊中抢红包也是为大家所津津乐道的。有时,可能因为手动去抢红包速度慢了,产生分分钟与一个亿差肩而过的遗憾。而这个时候,有小伙伴建议可以写一个微信自动抢红包的插件,这引起了我的兴趣,也就有了今天这篇文章。

依稀记得,微信红包插件在几年前就有了。本人曾经在CSDN上看到四哥写的微信红包插件的文章介绍,大致了解其中的原理。实际上就是通过AccessibilityService来监听出现新的红包后页面内容的变化和模拟点击,实现拆开红包。

二、原理解析

关于AccessibilityService

官网是这样介绍的

Accessibility services should only be used to assist users with disabilities in using Android devices and apps. They run in the backgro und and receive callbacks by the system when AccessibilityEvents are fired. Such events denote some state transition in the user interface, for example, the focus has changed, a button has been clicked, etc.

上面大概的意思就是AccessibilityService是一个辅助服务,可用来帮助残障人士使用Android设备,对页面的内容变化做出相应的处理等。

AccessibilityService的内容描述

官网上对于AccessibilityService的内容介绍和API描述

AccessibilityService的示例代码

官网上关于AccessibilityService的示例代码

三、开发思路

  • 通过注册AccessibilityService来监听通知栏和页面内容变化事件
  • 通过系统提供的findAccessibilityNodeInfosByTextfindAccessibilityNodeInfosByViewId来获取页面具体的控件对象
  • 通过performAction(AccessibilityNodeInfo.ACTION_CLICK)方法来模拟点击事件

注意:

微信默认当有新的消息(包括红包)如果不在对应的聊天页面,那么新的消息将通过通知栏弹出,所以当我们在微信的消息列表页面或者对应的聊天页面时 可以直接通过当前页面内容的变化来判断是否出现新的红包或者改红包是否已经领取过;

新版微信红包页面无法直接通过findAccessibilityNodeInfosByText来获取拆开红包的按钮,所以我们可以通过遍历页面内容的节点树来获取“拆”这个控件(具体请参考代码实现),模拟触发点击事件,来领取红包;

关于手机锁屏问题,当触发微信红包消息事件时,我们可以通过添加解开屏幕来跳转;部分机型和添加密码的手机可以通过发送提醒声来告知用户出现新的红包啦!!!

四、逻辑实现

集成AccessibilityService类

1、 新建一个继承自AccessibilityService的类,然后在AndroidManifest文件中进行注册,如下:

        <service
            android:name="com.mm.plugin.LMPAccessibilityService"
            android:enabled="true"
            android:exported="true"
            android:label="@string/service_name"
            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/plugin_service_config"/>
        </service>

注意,在AndroidManifest文件声明配置文件是在4.0以后的写法,之前的SDK需要直接在代码中配置。

还需要声明一个权限:

android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"

2、 添加该服务的配置文件,位于res/xml文件夹下:

<?xml version="1.0" encoding="utf-8"?>
    <accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged|typeWindowsChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="true"
    android:description="@string/accessibility_description"
    android:notificationTimeout="100"
    android:packageNames="com.tencent.mm"/>

android:accessibilityEventTypes=”typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged|typeWindowsChanged”

服务接收的响应事件类型;

android:accessibilityFeedbackType=”feedbackGeneric”

响应的反馈方式;

android:description=”@string/accessibility_description”

在手机设置中开启服务的描述内容;

android:notificationTimeout=”100”

响应时间;

android:packageNames=”com.tencent.mm”

服务接收指定的应用时间,这里我们默认只接收微信

3、在LMPAccessibilityService类中部分逻辑实现

 @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        //接收事件,如触发了通知栏变化、界面变化等  
    }

    @Override
    protected boolean onKeyEvent(KeyEvent event) {
        //接收按键事件
        return super.onKeyEvent(event);
    }

    @Override
    public void onInterrupt() {
      //服务中断,如授权关闭或者将服务杀死
    }

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        //连接服务后,一般是在授权成功后会接收到
    }

4、关于界面内容变化和抢红包核心业务逻辑

在辅助服务中AccessibilityEvent事件中的处理:

        //通知栏事件
        if (eventType == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
            Parcelable data = event.getParcelableData();
            if (data == null || !(data instanceof Notification)) {
                return;
            }
            if (LMPAccessibilityService.isNotificationServiceRunning() && getConfig()
                    .isEnableNotificationService()) { //开启快速模式,不处理
                return;
            }
            List<CharSequence> texts = event.getText();
            if (!texts.isEmpty()) {
                String text = String.valueOf(texts.get(0));
                notificationEvent(text, (Notification) data);
            }
        } else if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            // 这里同一个应用内 如果页面内容没有变化的话是没有执行的
            Log.e(TAG, "TYPE_WINDOW_STATE_CHANGED");
            onWindowStateChanged(event);
        } else if (eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
            // 这里只要页面内容变化 就会执行 例如不同应用之间的跳转
            Log.e(TAG, "TYPE_WINDOW_CONTENT_CHANGED");
            if (mCurrentWindow != WINDOW_LAUNCHER) { //不在聊天界面或聊天列表,不处理
                return;
            }
            handleChatListHongBao();
        }

在通知栏中过滤微信红包消息:

    /**
     * 通知栏事件
     */
    private void notificationEvent(String ticker, Notification nf) {
        String text = ticker;
        int index = text.indexOf(":");
        if (index != -1) {
            text = text.substring(index + 1);
        }
        text = text.trim();
        if (text.contains(HONGBAO_TEXT_KEY)) { //红包消息
            handleNewHongBaoNotification(nf);
        }
    }

对通知栏和页面变化的逻辑处理:

private void onWindowStateChanged(AccessibilityEvent event) {
        Log.d(TAG, event.getClassName() + "");
        if (wxHongbaoPageName.equals(event.getClassName())) {
            mCurrentWindow = WINDOW_LUCKYMONEY_RECEIVEUI;
            //点中了红包,下一步就是去拆红包
            openHongbao();
        } else if (wxHongbaoDetailsPageName.equals(event.getClassName())) {
            mCurrentWindow = WINDOW_LUCKYMONEY_DETAIL;

            if (curHongbaoNotificationList.size() == 0) {
                // 设置当前是否需要领取红包状态为否 后续重新接收新的领取红包事件
                isReceivingHongbao = false;
                //拆完红包后看详细的纪录界面 在这里可以返回到当前的上一个页面
                if (getConfig().getWechatAfterGetHongBaoEvent() == Config.WX_AFTER_GET_GOHOME) {
                    //返回主界面,以便收到下一次的红包通知
                    AccessibilityHelper.performHome(getService());
                } else {
                    // 返回到上个页面
                    AccessibilityHelper.performBack(getService());
                }
            } else {
                for (int i = 0; i < curHongbaoNotificationList.size(); i++) {
                    openHongbaoPage4Notification(curHongbaoNotificationList.get(i));
                }
            }

        } else if (wxChatPageName.equals(event.getClassName())) {
            mCurrentWindow = WINDOW_LAUNCHER;
            //在聊天界面,去点中红包
            handleChatListHongBao();
        } else {
            mCurrentWindow = WINDOW_OTHER;
        }
    }

上文提到的,新版微信已经不支持通过获取文本内容来定位到拆开按钮,所以我们直接通过遍历节点树来获取具体的控件模拟点击”开“按钮:

     /**
     * 点击聊天里的红包后,显示的界面
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void openHongbao() {
        AccessibilityNodeInfo nodeInfo = getService().getRootInActiveWindow();
        if (nodeInfo == null) {
            Log.w(TAG, "rootWindow为空");
            return;
        }

        AccessibilityNodeInfo targetNode = null;

        int event = getConfig().getWechatAfterOpenHongBaoEvent();
        int wechatVersion = getWechatVersion();
        if (event == Config.WX_AFTER_OPEN_HONGBAO) { //拆红包
            // 通过遍历当前页面的节点树,获取到红包页面的“开”按钮
            if (wechatVersion == wx523VersionCode || wechatVersion == wx661VersionCode) {
                targetNode = AccessibilityHelper.findNodeInfosByClassName(nodeInfo, BUTTON_CLASS_NAME);
            } else {
                Log.d(TAG, "抱歉,暂时不支持该微信版本!");
                Toast.makeText(getContext(), "抱歉,暂时不支持该微信版本!", Toast.LENGTH_SHORT).show();
            }

        } else if (event == Config.WX_AFTER_OPEN_NONE) {
            return;
        }

        if (targetNode != null) {
            final AccessibilityNodeInfo n = targetNode;
            long sDelayTime = getConfig().getWechatOpenDelayTime();
            if (sDelayTime != 0) {
                getHandler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        // 模拟拆开红包
                        AccessibilityHelper.performClick(n);
                    }
                }, sDelayTime);
            } else {
                // 模拟拆开红包
                AccessibilityHelper.performClick(n);
            }
        }
    }

至此,我们就完成了微信抢红包插件开发实现,以上代码适配了微信6.5.23和6.6.1版本。
另外我们还可以添加延时抢红包功能来防止封号和过滤群聊等功能~~。
亲测在小米、vivo等机型上可以完美运行,实现自动抢红包功能。

最后附上相关截图:

关于控件ID的获取,我们可以借助Android的DDMS工具里的Dump View Hierarchy For UI Automator 去分析微信UI结构。
这里写图片描述
这个ID就是我们上文提到的findAccessibilityNodeInfosByViewId方法中的参数。
这里写图片描述
最终效果:
Markdown
这里因为测试用的微信号没有实名认证,所以打开红包后默认会进入微信的实名认证页面~~~

源码地址:
https://github.com/chenzhi-ls/WeChatLuckyMoneyPlugin


五、延展总结

关于AccessibilityService这个类,我们来可以用来实现比如免root自动安装、虚拟按键操作等。

最后声明:本项目中用到的内容仅用作学习研究使用,对技术的可行性做相关探索,如果利用本文案例或者技术进行其他非法操作,带来的一切法律责任与本人无关!!!

一个帮助你在微信抢红包时战无不胜的Android应用。自动检测并且拆开红包,速度超乎你的想象。 支持中英文。前往Release下载最新版本。已下载用户可直接在设置里面更新。 特性 监视选项任意组合,满足多样化的使用需求[?] 提供了系统通知/聊天列表/聊天页面三档选项,无论是想要谨慎不被察觉,还是想要高效志在必得,这个插件如你所愿。 不仅快人一步,红包识别更加智能 多种特征标识,聊天时不再重复点击红包。智能过滤红包关键字[?],避免落入“专属红包”、“抢到翻倍”的陷阱。还可以设置延时抢红包自动回复感谢语[?]。 紧跟微信更新 第一时间适配最新版本微信,应用内即可一键更新。 轻量、安全、值得信赖 安装包仅1M,无需ROOT,下载即用。代码公开透明,活跃的社区讨论,数万用户下载,值得你的信赖。 使用方法 打开『微信红包』应用,开启插件。 做你想做的事。 坐等红包进账。 实现原理 请见技术文档,注意文档描述的是dev分支(已弃用)的具体实现,而不是stable分支。若有疑问,请在ISSUES中提出。 更新日志 完整的更新日志请见CHANGELOG。 版权及免责声明 本项目源自小米去年秋季发布会时演示的MIUI 7抢红包测试代码。 插件可能会在一定程度上改变微信的交互方式。使用本项目中包含的代码及其生成物时,使用者自行承担随之而来的各种风险,包括但不限于“禁用红包功能”、“微信封号”。 本项目使用MIT许可证。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值