android微信的抢红包插件

来源:互联网 发布:mac终端编辑保存退出 编辑:程序博客网 时间:2024/04/30 16:52

前言

  之前看到了微信抢红包的插件,觉得这个功能实在强大了,这和之前我想实现的模拟点击事件基本相似,可以完美的触发一个view的点击事件,当然静默安装的原理也和抢红包的原理是一样的。

  小米有开源小米的抢红包的demo,之后有童鞋在此之上增加了自己的逻辑,使其完善。现在为了学习,我也加入抢红包的demo大军哈哈,主要在于学习。

原理

  原理主要是通过辅助功能AccessibilityService来完成的,AccessibilityService是Google用来帮助肢体不便的人所开发的一个功能,能够触发相应的用户事件比如点击,滑动等等。

  抢红包的功能也是基于此能力之上,去进行模拟点击事件。当然,AccessibilityService的功能需要得到用户的许可,通过它的功能的介绍,已经可以知道它的强大,其实这个的能力和root相当,甚至更强,毕竟可以模拟一切的用户事件。

  所以,在实现的方法前,需要了解
  1. 如何获取特定能够点击的view的节点
  2. 触发点击事件,进行判断

逻辑

需要注意:保持屏幕常亮,需要把每个群的消息免打扰取消,主要为了能够接收到通知Notification,在一开始请回到Home

  1. 接收到Notification之后,存入List中作为待处理红包.

  2. 逐个处理List中的PendingIntent,开启微信的聊天界面,获取可以点击的红包列表,逐个点击。

  3. 进入红包处理,两种情况,
    一、进入接收红包的界面,获取抢红包的按钮,触发点击。进入红包detail界面,结束。
    二、已经抢过,进入红包detail界面,或者被抢完了,停留在receive界面,结束,回到主界面。

  4. 回到步骤1

工程源码

  1. 首先新建一个PluginService继承AccessibilityService,然后再Manifest.xml中进行注册:
<service            android:name=".PluginService"            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/accessible_service_config" />        </service>

  其中的android:resource=…的是对于Service的参数配置,文件的位置在于res下的xml文件下,名字定为accessible_service_config.xml
各个参数的定义:

  android:accessibilityEventTypes:允许接收的事件,这里定义为notification变化事件,窗口切换变化事件,窗口内容变化事件。

  android:accessibilityFeedbackType:设置返回给用户的形式

  android:packageNames:指定响应某个指定应用的事件。需要知道某个应用的包名的时候可以通过DDMS中的hierarchy view进行查看。

  android:notificationTimeout:设置响应的时间

<accessibility-service    xmlns:android="http://schemas.android.com/apk/res/android"    android:description="@string/app_name"    android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowContentChanged|typeWindowStateChanged"    android:accessibilityFeedbackType="feedbackAllMask"    android:packageNames="com.tencent.mm"    android:notificationTimeout="0"    android:accessibilityFlags=""    android:canRetrieveWindowContent="true"/>

2.在PluginService中进行逻辑的处理
在onAccessibilityEvent中进行事件的监听,监听包括三种类型的,一种是通知栏的变化,一种是窗口内容的变化,一种是窗口切换的变化。

@Overridepublic void onAccessibilityEvent(AccessibilityEvent event) {    int eventType = event.getEventType();    switch (eventType) {        // 监听通知栏消息        case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:            handlerNotification(event);            break;        // 监听窗口变化消息        case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:            handlerWindowStateChange(event);            break;        // 监听窗口内容的动态变化        case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:            handlerWindowContentChange(event);            break;        default:            break;    }}

3.我们回到了主界面,有群发了红包,那么进入notification变化的事件中去,在其中判断是否是含有红包的消息,如果是的话则模拟打开notification,这里多做了一个步奏就是将多个notification存储到一个list里面,进行足一的打开。

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)public void handlerNotification(AccessibilityEvent event) {    if (event == null) return;    List<CharSequence> contents = event.getText();    if (contents != null && !contents.isEmpty()) {        for (CharSequence content : contents) {            String text = content.toString();            if (!text.contains("[微信红包]")) continue;            if (event.getParcelableData() != null &&                    event.getParcelableData() instanceof Notification) {                Notification notification = (Notification) event.getParcelableData();                PendingIntent pendingIntent = notification.contentIntent;                Log.e("pendingintent", pendingIntent.getCreatorPackage().toString());                if (pendingIntent.getCreatorPackage().toString().equals("com.tencent.mm")) {                    if ((curStatus != Status.ON_CHAT_ROOM || curStatus != Status.ON_LUCKY_MONEY_RECEIVED) &&                            untreatedLuckyMoneyList.size() == 0) {                        openNotification(pendingIntent);                    } else {                        untreatedLuckyMoneyList.add(0, pendingIntent);                    }                }            }        }    }}/** * 开启Notification中所指向的微信聊天页面 * * @param pendingIntent */public void openNotification(PendingIntent pendingIntent) {    if (pendingIntent == null) return;    // 模拟点击了notification    try {        pendingIntent.send();        curStatus = Status.ON_CHAT_ROOM;    } catch (PendingIntent.CanceledException e) {        e.printStackTrace();    }}

4.一下方法通过判定窗口变化的具体的类来判定进入了哪一个界面。从而进行对应的操作。
  a 进入了聊天界面,则进行领取红包的字样遍历,同时进行模拟点击开启。
  b 进入领取红包界面,则遍历得到拆红包字样的节点,同时模拟点击
  c 进入红包详情界面,则模拟返回键退出。

要知道具体的类是什么的时候,可以通过测试输出消息,通过event.getClassName().toString()的输出查看具体的类名。

public void handlerWindowStateChange(AccessibilityEvent event) {        if (event == null || event.getSource() == null) return;        String className = event.getClassName().toString();        // 微信主界面 com.tencent.mm.ui.LauncherUI        if (className.equals("com.tencent.mm.ui.LauncherUI")) {            curStatus = Status.ON_CHAT_ROOM;            if (canOpenNode == null) {                canOpenNode = event.getSource().findAccessibilityNodeInfosByText("领取红包");                trashOpenNode = new ArrayList<AccessibilityNodeInfo>();            }            openLuckyMoney();            // 红包接收界面 com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI        } else if (className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI")) {            curStatus = Status.ON_LUCKY_MONEY_RECEIVED;            unpackLuckyMoney(event.getSource());            // 红包的detail界面 com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI        } else if (className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI")) {            curStatus = Status.ON_LUCKY_MONEY_DETAIL;            back();        }    }

5.进入到微信的聊天界面,这时候会触发窗口变化消息,这时候通过view找到具有领取红包字样的节点,如果有,则进行模拟点击,如果没有回到主界面。这里其中一个步奏也是将多个具有领取红包的节点存储到list中,等待开启。

/** * 在聊天界面中打开红包 */public void openLuckyMoney() {    if (canOpenNode != null && canOpenNode.size() == 0) {        backToHome();        disposeLuckyMoneyList();        canOpenNode = null;        trashOpenNode = null;    }    if (canOpenNode == null) {        backToHome();        return;    }    AccessibilityNodeInfo node = canOpenNode.remove(0);    trashOpenNode.add(node);    if (node.getParent() != null && node.getParent().isClickable()) {        node.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);    }}

6.假如进入到了红包领取界面,则需要先判断是否被领取完了,或者是过期了,如果都不是,则找到拆红包字样的节点进行模拟点击

public void unpackLuckyMoney(AccessibilityNodeInfo nodeInfo) {        if (nodeInfo == null) {            back();            return;        }        // 打开红包,如果红包已被抢完,遍历节点, 如果匹配“红包详情”、“手慢了”和“过期”,则返回        // 继续打开其他红包        List<AccessibilityNodeInfo> packedLists = new ArrayList<>();        packedLists.addAll(nodeInfo.findAccessibilityNodeInfosByText("红包详情"));        packedLists.addAll(nodeInfo.findAccessibilityNodeInfosByText("手慢了"));        packedLists.addAll(nodeInfo.findAccessibilityNodeInfosByText("过期"));        if (!packedLists.isEmpty()) {            back();            return;        }        List<AccessibilityNodeInfo> unPackedLists = nodeInfo.findAccessibilityNodeInfosByText("拆红包");        if (unPackedLists.isEmpty()) {            back();        } else {            AccessibilityNodeInfo node = unPackedLists.get(0);            node.performAction(AccessibilityNodeInfo.ACTION_CLICK);        }    }

7.当进入到了红包详情页面时候,则进行返回,返回到聊天界面之后,会继续进行红包列表进行拆红包,知道拆完了所有的红包之后,就会退回到主界面,然后继续拆开notification list中的消息,如果没有消息,则等待。

改进

通过遍历节点去找到能点击的事件是比较浪费效率的,其实通过DDMS中的hierarchy view可以看到每个控件的ID,通过ID可以直接获取该控件,然后模拟点击,就不用进行遍历了。

项目源码,github网址

参考文档网址
参考工程

6 0
原创粉丝点击