微信抢红包辅助工具 及AccessibilityService介绍

更新于2016.12.9

测试:微信版本:6.3.31,手机型号:华为p8, android版本:23

网上有很多抢红包工具的代码解析,但基本全部是基于通知的。我对通知没有好感,所以我的群都是设置为消息免打扰模式,这就导致了红包工具在我这基本失灵。所以只能自己动手,研究了一下代码,又顺便研究了下AccessibilityService这个类,然后就搞出了现在的东西。这个工具可以使用通知也可以关闭通知,但是关闭通知必须保证屏幕亮着并且在微信主页或者群聊天页面才可以。

主要功能

  • 可以通过通知进入程序抢
  • 可以在微信主页面监控"[微信红包]"进入聊天页面抢
  • 可以在聊天页面直接抢
  • 抢完之后会返回到聊天页面

上代码了

QiangHongBaoService

public class QiangHongBaoService extends AccessibilityService {

    static final String TAG                   = "QiangHongBao";
    /**
     * 微信的包名
     */
    static final String WECHAT_PACKAGENAME    = "com.tencent.mm";
    /**
     * 拆红包类
     */
    static final String WECHAT_RECEIVER_CALSS = "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI";
    /**
     * 红包详情类
     */
    static final String WECHAT_DETAIL         = "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI";
    /**
     * 微信主界面或者是聊天界面
     */
    static final String WECHAT_LAUNCHER       = "com.tencent.mm.ui.LauncherUI";

    /**
     * 存放上次红包布局资源的Id, 因为ListView采用ViewHolder重用,所以只能保证当前屏幕内的id是唯一的,
     * 所以不能使用集合存储已经点过的资源Id,只能当前屏幕内不重复点击
     */
    private long lastSourceId;

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        final int eventType = event.getEventType();
        if (eventType == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {//通知状态改变
            onNotifyStateChanged(event);
        } else if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {//屏幕改变,如弹窗框,转换页面
            onWindowStateChange(event);
        } else if (eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {//屏幕内容改变,如拖动,新消息滚动
            onWindowContentChanged(event);
        }
    }

    @Override
    public void onInterrupt() {
        Toast.makeText(this, "中断抢红包服务", Toast.LENGTH_SHORT)
             .show();
    }

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        Toast.makeText(this, "连接抢红包服务", Toast.LENGTH_SHORT)
             .show();
    }

    /**
     * 监听通知信息处理
     */
    private void onNotifyStateChanged(AccessibilityEvent event) {
        List<CharSequence> texts = event.getText();
        for (CharSequence t : texts) {
            String text = String.valueOf(t);
            if (text.contains("[微信红包]")) {
                openNotify(event);
                break;
            }
        }
    }

    /**
     * 处理通知信息数据
     */
    private void openNotify(AccessibilityEvent event) {
        if (event.getParcelableData() == null || !(event.getParcelableData() instanceof Notification)) {
            return;
        }

        try {
            Notification notification = (Notification) event.getParcelableData();
            PendingIntent pendingIntent = notification.contentIntent;
            pendingIntent.send();
        } catch (PendingIntent.CanceledException e) {
            e.printStackTrace();
        }
    }

    /**
     * 监听窗口改变状态信息处理
     */
    private void onWindowStateChange(AccessibilityEvent event) {
        //查看是需要拆红包、红包已经被抢完
        if (WECHAT_RECEIVER_CALSS.equals(event.getClassName())) {
            processInHongBaoDialog();
        }
        //拆完红包后看详细的纪录界面
        else if (WECHAT_DETAIL.equals(event.getClassName())) {
            performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
        }
        //在聊天界面,去点中红包
        else if (WECHAT_LAUNCHER.equals(event.getClassName())) {
            openHongBaoInChatPage(event);
        }
    }

    /**
     * 监听窗口内容数据信息处理
     */
    private void onWindowContentChanged(AccessibilityEvent event) {
        AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
        if (!openHongBaoInMainPage(event)) {
            openHongBaoInChatPage(event);
        }
    }

    /**
     * 在微信主页面的信息处理
     */
    private boolean openHongBaoInMainPage(AccessibilityEvent event) {
        boolean isFlag = false;
        AccessibilityNodeInfo rootNodeInfo = getRootInActiveWindow();
        /*在聊天界面才会有"更多功能按钮,已折叠",主页面不会有, 由此判断是不是在主页面*/
        if (rootNodeInfo != null && rootNodeInfo.findAccessibilityNodeInfosByText("更多功能按钮,已折叠")
                                                .isEmpty()
                && rootNodeInfo.findAccessibilityNodeInfosByText("更多功能按钮")
                               .size() > 0) {
            isFlag = true;
            lastSourceId = 0; //从聊天页面出来了,清除保存的sourceId;
            AccessibilityNodeInfo nodeInfo = event.getSource();
            if (nodeInfo != null) {
                /**
                 * TODO 直接使用findAccessibilityNodeInfosByText获取不到,这个Id没验证在不同的手机会不会改变
                 */
                List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/aef");
                for (AccessibilityNodeInfo accessibilityNodeInfo : list) {
                    if ("[微信红包]".equals(String.valueOf(accessibilityNodeInfo.getText()))) {
                        accessibilityNodeInfo.getParent()
                                             .performAction(AccessibilityNodeInfo.ACTION_CLICK);//进入聊天页面
                        break;
                    }
                }
            }
            rootNodeInfo.recycle();
        }
        return isFlag;
    }


    /**
     * 在微信(群)聊天页面的信息处理
     */
    private void openHongBaoInChatPage(AccessibilityEvent event) {
        AccessibilityNodeInfo nodeInfo = event.getSource();
        List<AccessibilityNodeInfo> list = new ArrayList<>();
        if (nodeInfo != null && nodeInfo.getChildCount() > 0) {
            List<AccessibilityNodeInfo> list1 = nodeInfo.findAccessibilityNodeInfosByText("查看红包");//自己发的红包
            List<AccessibilityNodeInfo> list2 = nodeInfo.findAccessibilityNodeInfosByText("领取红包");//别人发的红包
            if (list1.size() > 0) {
                list.addAll(list1);
            }
            if (list2.size() > 0) {
                list.addAll(list2);
            }

            //只抢最后一个
            for (int i = list.size() - 1; i >= 0; i--) {
                AccessibilityNodeInfo parent = list.get(i)
                                                   .getParent();
                if (parent != null) {
                    long sourceId = getSourdeId(parent);//在listview中会循环出现,所以只能保证在一屏下是唯一的,而不是全局唯一
                    if ("android.widget.LinearLayout".equals(parent.getClassName()) && lastSourceId != sourceId) {
                        lastSourceId = sourceId;
                        parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                    }
                    break;
                }
            }
            nodeInfo.recycle();
        }
    }

    /*
     * 红包Dialog界面的处理
     * 如果还没有被拆则进行拆红包,拆完之后返回
     * 如果已经被抢完,则直接返回
     */
    private void processInHongBaoDialog() {
        AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
        if (nodeInfo != null) {
            boolean isHave = false;
            int count = nodeInfo.getChildCount();
            for (int i = 0; i < count; i++) {
                AccessibilityNodeInfo childNode = nodeInfo.getChild(i);
                if ("android.widget.Button".equals(childNode.getClassName())) {
                    isHave = true;
                    childNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                }
            }

            if (!isHave) {
                List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("红包派完了");
                if (list != null && list.size() > 0) {
                    performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
                }
            }
            nodeInfo.recycle();
        }
    }

    /**
     * 获取sourdeId,当前屏幕唯一
     */
    private long getSourdeId(AccessibilityNodeInfo info) {
        long sourceId = 0;
        try {
            Method method = AccessibilityNodeInfo.class.getMethod("getSourceNodeId");
            method.setAccessible(true);
            Object obj = method.invoke(info);
            sourceId = (Long) obj;
        } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return sourceId;
    }


    /**
     * 5.0之后不支持获取topActiivty的名字,只支持获取包名
     *
     * @deprecated 不再检测栈顶的app是不是微信
     */
    private String getTopApp(Context context) {
        String topActivity = null;
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
            UsageStatsManager m = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
            if (m != null) {
                long now = System.currentTimeMillis();
                //获取60秒之内的应用数据  
                List<UsageStats> stats = m.queryUsageStats(UsageStatsManager.INTERVAL_BEST, now - 60 * 1000, now);

                //取得最近运行的一个app,即当前运行的app  
                if ((stats != null) && (!stats.isEmpty())) {
                    int j = 0;
                    for (int i = 0; i < stats.size(); i++) {
                        if (stats.get(i)
                                 .getLastTimeUsed() > stats.get(j)
                                                           .getLastTimeUsed()) {
                            j = i;
                        }
                    }
                    topActivity = stats.get(j)
                                       .getPackageName();
                }
                Log.i(TAG, "top running app is : " + topActivity);
            }
        }
        return topActivity;
    }
}

qianghongbao_service_config.xml

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

全部代码在我的Github上 (https://github.com/zcolin/WeChatLuckyMoney)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容