Android微信新版全自動搶紅包助手

Roll圈圈發表於2018-02-10

前言

新的一年又到了,又到了拼手速和網速的時候了,網速是硬體條件,沒有辦法了,不過手速這種東西,沒有還不能創造麼,哈哈。其實之前網上有很多老鐵已經分享過類似的外掛的實現方式,但是微信其實本身也是在做對第三方外掛的規避操作,所以,微信的每一個新版本都會修改相同控制元件的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,一個可愛的小男孩。

電點紅包助手下載地址

相關文章