2021-03-19

微信抢红包神器红包助手原理介绍,其实真的很简单

    有些人觉得,抢红包神器有多牛,瞬间对IT男产生膜拜,比如我这样的,哈哈,低调低调。其实,稍微了解一些Android相关技术的人,就会了解,其实抢红包神器没有那么难。会写一点代码,so easy。

    下面从原理的角度,分析一下红包助手的设计。有些地方会涉及到代码,觉得难的多看看文字,技术宅可以多看代码。毕竟代码更能说明一些东西。

    市面上的抢红包软件有两种,一种是云平台的,一种是需要安装在客户端的一个APP。作为技术人员来说,云平台这个不安全,它能监控你的一些信息。客户端安装的软件助手相对安全一些,因为他感知不到你写的内容。只是捕获含有关键字信息的微信消息进行处理。

以下以第二种作为介绍:

    对于安装在客户端的APP,都是采用系统提供的无障碍服务服务AccessibilityService。

    这个服务我们看一下官网介绍:Accessibility services should only be used to assist users with disabilities in using Android devices and apps. They run in the background 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设备,对页面的内容变化做出相应的处理等。各种抢红包神器就是巧妙的借用了这个服务,模拟用户的动作,从而完成了抢红包的事情。

   

    好了 准备上干货,下载安装体验。

微信抢红包神器,支持息屏抢红包

 

1、怎么支持息屏抢红包

大部分的红包软件都不支持息屏抢,那么怎么实现息屏抢红包?

// 如果设置了开屏抢红包,则开屏、抢红包
if (LocalizationHelper.isSupportScreenOnOffPacket()) {
                        // 锁屏抢红包开启
                        mPowerUtil?.handleWakeLock(true)
                    }

                    var userStr: String = RpApplication.sp().getString(Constants.USER_WEICHAT_INFO, "");
                    if (!TextUtils.isEmpty(userStr)) {
                        var backdata: HongbaoUserDTO = JsonUtils.toBean(userStr, HongbaoUserDTO::class.java)

                        if (null != backdata.expireTime && backdata.expireTime.after(Calendar.getInstance().time)) {
                            // 正常的会员
                        } else {
                            // 非会员
                            if (null == backdata.expireTime) {
                                if (backdata.freeTimesLeft <= 0) {
                                    // 提示用户开启会员服务,免费体验次数已经使用完毕
                                    EventBus.getDefault().post(FreeTimeEvent())
                                    return
                                } else {
                                    // 还有体验次数
                                    backdata.freeTimesLeft -= 1
                                    RpApplication.sp().edit().putString(Constants.USER_WEICHAT_INFO, JsonUtils.toString(backdata)).commit()
                                    reportFreeTimes()
                                }
                            } else {
                                // 过期会员
                                EventBus.getDefault().post(FreeTimeEvent())
                                return
                            }
                        }
                    }

2、频繁升级,怎么匹配微信

    这里存在一个问题,那么微信频繁升级,软件需要频繁升级吗? 答案是当然需要。所以有些红包软件已经停止更新了,因为没有那么多精力去维护。那其他的怎么做的呢?根据版本穷举所有特性,适配特定微信版本。进行特性适配,以微信8.0.1为例,进行匹配如下:

class Version801 : VersionInfo {

    companion object {

        const val VERSION = "8.0.1"


        /**
         * 类名
         */
        const val CLASS_LAUNCHER = "com.tencent.mm.ui.LauncherUI"                                             // 微信 聊天列表、聊天窗口(单聊私聊都是)
        const val CLASS_PACKET_RECEIVE = "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI"     // 微信 红包“開”的窗口
        const val CLASS_PACKET_SEND = "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyPrepareUI"               // 微信 自己发红包的窗口
        const val CLASS_PACKET_PAY = "com.tencent.mm.plugin.wallet_core.ui.l"                                 // 微信 自己发红包输入密码的界面
        const val CLASS_PACKET_DETAIL = "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI"              // 微信 红包详情


        /**
         * 红包“開”的窗口 id
         */
        const val ID_PACKET_DIALOG_OPEN = "com.tencent.mm:id/f4f"               // 聊天页面 - 红包对话框 - 开

        /**
         * 红包详情页 id
         */
        const val ID_PACKET_DETAIL_POST_USER = "com.tencent.mm:id/f03"          // 红包详情页 - 发送者信息 - "xxxx的红包"
        const val ID_PACKET_DETAIL_POST_NUM = "com.tencent.mm:id/eyq"           // 红包详情页 - 红包金额


        /**
         * 聊天页面 id
         */
        const val ID_CHAT_PAGER_ITEM = "com.tencent.mm:id/aui"                     // 聊天界面 - 聊天列表 - item
        const val ID_CHAT_PAGER_ITEM_AVATAR = "com.tencent.mm:id/au2"              // 聊天页面 - 聊天列表 - item - 头像
        const val ID_CHAT_PAGER_ITEM_PACKET = "com.tencent.mm:id/auf"             // 聊天页面 - 聊天列表 - 红包布局
        const val ID_CHAT_PAGER_ITEM_PACKET_MESSAGE = "com.tencent.mm:id/u1"     // 聊天页面 - 聊天列表 - 红包布局 - 祝福语 "恭喜发财" 等,用户自己编辑的
        const val ID_CHAT_PAGER_ITEM_PACKET_FLAG = "com.tencent.mm:id/u5"        // 聊天页面 - 聊天列表 - 红包布局 - 底部 "微信红包"
        const val ID_CHAT_PAGER_ITEM_PACKET_TIP = "com.tencent.mm:id/tt"         // 聊天页面 - 聊天列表 - 红包布局 - 红包状态 "已领取"
        const val ID_CHAT_PAGER_TITLE = "com.tencent.mm:id/ipt"                    // 聊天页面 - 聊天列表 - 个人聊天则是昵称,群聊天则是群昵称

        /**
         * 首页-微信tab 聊天会话 id
         */
        const val ID_HOME_CHAT_LIST_ITEM = "com.tencent.mm:id/bg1"                // 首页列表 - 聊天会话 - item id
        const val ID_HOME_CHAT_LIST_ITEM_MESSAGE = "com.tencent.mm:id/e7t"        // 微信列表 每一个item中的文本id
        const val ID_WID_CHAT_LIST_TITLE_TEXT = "com.tencent.mm:id/fzg"           // 微信列表 每一个item中的会话名字

        /**
         * 首页-我tab
         */
        const val ID_HOME_USER_PAGER_AVATAT = "com.tencent.mm:id/x1"                 // 我的页面,微信头像
        const val ID_HOME_USER_PAGER_NICK = "com.tencent.mm:id/fz_"                  // 我的页面,微信昵称
        const val ID_HOME_USER_PAGER_WEICHATNUM = "com.tencent.mm:id/j1l"            // 我的页面,微信号
        const val ID_HOME_TAB_TITLE = "android:id/text1"                                // 首页4个tab的标题,存在与微信tab,通讯录tab,发现tab,用于过滤没有必要的轮训,8.0.0整体结构大概,改成底部LauncherUIBottomTabView

    }

}

3、关键屏幕事件捕获

     首选我们创建服务,实现我们自己想要捕获分析的动作。我们主要分析两个动作:“窗口状态改变”和“窗口内容变化”。

override fun onAccessibilityEvent(accessibilityEvent: AccessibilityEvent) {

        if (!VersionManager.runningPlus()) {
            Logger.i(TAG, "onAccessibilityEvent - $statusByPluginError")
            return
        }

        if (!LocalizationHelper.isSupportPlugin()) {
            Logger.i(TAG, "onAccessibilityEvent - $statusBySettingDoNotSupportPlugin")
            return
        }

        val eventType = accessibilityEvent.eventType
        val className = accessibilityEvent.className.toString()
        val rootNode = rootInActiveWindow

        Logger.i(TAG, "onAccessibilityEvent eventType =  $eventType  className = $className")

        when (eventType) {
            AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> {
                Logger.i(TAG, "窗口状态改变 className = $className")
                accessibilityManager?.dealWindowStateChanged(className, rootNode)
            }
            AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> {
                Logger.i(TAG, "窗口内容变化")
                accessibilityManager?.dealWindowContentChanged(rootNode)
//                accessibilityManager?.checkSendAutoReply(rootNode)
            }
        }
        rootNode?.recycle()
    }

2)窗口事件处理
 

fun dealWindowStateChanged(className: String, rootNode: AccessibilityNodeInfo?) {
        if (rootNode == null) {
            Logger.d(TAG, "dealWindowStateChanged-rootNode : null")
            return
        }
        when (className) {
            //如果当前是首页
            VersionManager.launcherClass() -> {
                Logger.d(TAG, "当前在微信首页")

                //如果是首页 "微信" tab
                if (NodeUtil.isNotWeChatHomePage(rootNode)) {
                    if (LocalizationHelper.isSupportGettingSelfPacket() && VersionManager.currentSelfPacketStatus == VersionManager.W_openedPayStatus) {
                        VersionManager.setCurrentSelfPacketStatusData(VersionManager.W_intoChatDialogStatus)
                        getPacket(rootNode, true)
                    } else {
                        getPacket(rootNode, false)
                    }
                    return
                }

                //如果首页 "我"tab,尝试更新微信昵称
                if (NodeUtil.isWeChatUserInfoPage(rootNode)) {
                    return
                }
            }

            //红包页面
            VersionManager.packetReceiveClass() -> {
                Logger.d(TAG, "当前已打开红包")
                if (LocalizationHelper.isSupportGettingSelfPacket() && VersionManager.currentSelfPacketStatus == VersionManager.W_intoChatDialogStatus) {
                    if (openPacket(rootNode)) {
                        VersionManager.setCurrentSelfPacketStatusData(VersionManager.W_gotSelfPacketStatus)
                    }
                } else {
                    if (openPacket(rootNode)) {
                        isGotPacket = true
                    }
                }
                VersionManager.isClickedNewMessageList = false
                VersionManager.isGotPacket = false
            }

            //红包发送页面
            VersionManager.packetSendClass() -> {
                Logger.d(TAG, "dealWindowStateChanged : 当前在红包发送页面")
                if (VersionManager.currentSelfPacketStatus <= VersionManager.W_otherStatus) {
                    VersionManager.setCurrentSelfPacketStatusData(VersionManager.W_openedPacketSendStatus)
                }
            }


            //红包支付页面
            VersionManager.packetPayClass() -> {
                Logger.d(TAG, "dealWindowStateChanged : 当前在红包支付页面")
                if (VersionManager.currentSelfPacketStatus == VersionManager.W_openedPacketSendStatus) {
                    VersionManager.setCurrentSelfPacketStatusData(VersionManager.W_openedPayStatus)
                }
            }


            //红包详情页
            VersionManager.packetDetailClass() -> {
                Logger.d(TAG, "dealWindowStateChanged : 当前在红包详情页")
                if (VersionManager.currentSelfPacketStatus != VersionManager.W_otherStatus) {
                    AccessibilityUtil.performBack(WxAccessibilityService.getService())
                    VersionManager.setCurrentSelfPacketStatusData(VersionManager.W_otherStatus)
                }
                if (isGotPacket) {
                    sendPacketRecordMsg(getPacketRecord(rootNode))
                    //写入所抢的服务
                    AccessibilityUtil.performBack(WxAccessibilityService.getService())
                    isGotPacket = false
                }
            }

        }
    }

4、内容事件处理

/**
     * 页面内容变动回调
     */
    fun dealWindowContentChanged(rootNode: AccessibilityNodeInfo?) {
        if (rootNode == null) {
//            Logger.d(TAG, "dealWindowStateChanged-rootNode : null")
            return
        }

        //只有聊天详情页才需要查询红包
        if (NodeUtil.isNotWeChatHomePage(rootNode)) {
            Log.d(TAG, "现在在聊天详情页")
            checkSendAutoReply(rootNode)
            //如果是红包页面,则尝试点击 "开"
            if (openPacket(rootNode)) {
                isGotPacket = true
                VersionManager.isClickedNewMessageList = false
                VersionManager.isGotPacket = false
                return
            }


            //如果是聊天详情页,则尝试点击 "红包" item
            if (getPacket(rootNode, false)) {
                VersionManager.isGotPacket = true
                sendResetGotPacketMsg()
                return
            }

        } else {

            //如果是首页 "微信" tab,则尝试获取有效红包会话
            if (NodeUtil.isClickableConversationPage(rootNode)) {
                if (tryClickConversation(rootNode)) {
                    VersionManager.isClickedNewMessageList = true
                    sendClickedNewMessageMsg()
                    return
                }
            }

            //如果首页 "我"tab,尝试更新微信昵称
            if (NodeUtil.isWeChatUserInfoPage(rootNode)) {
                return
            }
        }
    }

5、捕获内容识别

    由于用户进行各种各样的操作,助手不可能进行各种捕获。否则手机会卡死的。

所以我们对目标APP进行设置。

 <service
            android:name=".accessibility.WxAccessibilityService"
            android:enabled="true"
            android:exported="false"
            android:label="@string/app_name"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
            android:priority="1000">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessibilityservice" />
        </service>

6、模拟点击动作,抢红包

Constants.MSG_OPEN_PACKET -> {
                        // 点击"开"字
                        val node = msg.obj
                        if (node != null && node is AccessibilityNodeInfo) {
                            if (openPacketList.isEmpty()) {
                                openPacketList.add(node)
                                AccessibilityUtil.performClick(openPacketList.last())
                                openPacketList.removeAt(openPacketList.lastIndex)
                            } else {
                                if (!NodeUtil.containNode(node, openPacketList)) {
                                    openPacketList.add(node)
                                    AccessibilityUtil.performClick(openPacketList.last())
                                    openPacketList.removeAt(openPacketList.lastIndex)
                                }
                            }

//                            // 红包已经抢完了自动回复
//                            RpApplication.sp().edit().putBoolean(Constants.SP_HAS_GOT_REDPACKAGE, true).commit()
                        }
                    }

7、模拟点击位置分析

    这里需要用到Android Device Monitor(AMD),用户分析APP设计时,每一个元素的坐标位置。

8、问题分析

A、由于android手机厂商千差万别,各种自己定制的东西都有,所以有些手机在开启无障碍服务的时候,会有一些差异。

B、长时间开启会耗电

C、开启通知栏抢红包的时候,需要单独申请系统授权。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值