微信搶紅包插件

微信搶紅包插件

下載地址:https://pan.baidu.com/s/1HJSI...
提取碼:ntuwnode

這個Android插件能夠幫助你在微信羣聊搶紅包時望風披靡。當檢測到紅包時,插件會自動點擊屏幕,人工點擊的速度沒法比擬。android

實現原理

1. 搶紅包流程的邏輯控制git

這個插件經過一個Stage類來記錄當前對應的階段。Stage類被設計成單例並惰性實例化,由於一個Service不須要也不該該處在不一樣的階段。對外暴露階段常量和entering和getCurrentStage兩個方法,分別記錄和獲取當前的階段。github

public class Stage {正則表達式

private static Stage stageInstance;

public static final int FETCHING\_STAGE \= 0, OPENING\_STAGE \= 1, FETCHED\_STAGE \= 2, OPENED\_STAGE \= 3;

private int currentStage \= FETCHED\_STAGE;

private Stage() {}

public static Stage getInstance() {
    if (stageInstance \== null) stageInstance \= new Stage();
    return stageInstance;
}

public void entering(int \_stage) {
    stageInstance.currentStage \= \_stage;
}

public int getCurrentStage() {
    return stageInstance.currentStage;
}

}安全

1.1 階段說明微信

階段ide

說明函數

FETCHING_STAGE性能

正在讀取屏幕上的紅包,此時不該有別的操做

FETCHED_STAGE

已經結束一個FETCH階段,屏幕上的紅包都已加入待搶隊列

OPENING_STAGE

正在拆紅包,此時不該有別的操做

OPENED_STAGE

紅包成功搶到,進入紅包詳情頁面

1.程序以FETCHED_STAGE 開始,將屏幕上的紅包加入待搶隊列:

--> FETCHED_STAGE --> FETCHING_STAGE  --> FETCHED_STAGE -->

2.處理待搶隊列中的紅包:

--> [CLICK] --> OPENING_STAGE --> [CLICK] --> OPENED_STAGE --> [BACK] --> FETCHED_STAGE -->(搶到)

--> [CLICK] --> OPENING_STAGE --> [BACK] --> FETCHED_STAGE -->(沒搶到)

3.不斷重複流程1和2

1.2 根據階段選擇不一樣的入口

在每次窗體狀態發生變化後,根據當前所在的階段選擇入口。

switch (Stage.getInstance().getCurrentStage()) {

case Stage.OPENING\_STAGE:
    // .......
    Stage.getInstance().entering(Stage.FETCHED\_STAGE);
    performGlobalAction(GLOBAL\_ACTION\_BACK);
    break;
case Stage.OPENED\_STAGE:
    Stage.getInstance().entering(Stage.FETCHED\_STAGE);
    performGlobalAction(GLOBAL\_ACTION\_BACK);
    break;
case Stage.FETCHED\_STAGE:
    if (nodesToFetch.size() \> 0) {
        AccessibilityNodeInfo node \= nodesToFetch.remove(nodesToFetch.size() \- 1);
        if (node.getParent() != null) {
            // .......
            Stage.getInstance().entering(Stage.OPENING\_STAGE);
            node.getParent().performAction(AccessibilityNodeInfo.ACTION\_CLICK);
        }
        return;
    }
    Stage.getInstance().entering(Stage.FETCHING\_STAGE);
    fetchHongbao(nodeInfo);
    Stage.getInstance().entering(Stage.FETCHED\_STAGE);
    break;

}

2. 屏幕內容檢測和自動化點擊的實現

和其餘插件同樣,這裏使用的是Android API提供的AccessibilityService。這個類位於android.accessibilityservice包內,該包中的類用於開發無障礙服務,提供代替或加強的用戶反饋。

AccessibilityService 服務在後臺運行,等待系統在發生 AccessibilityEvent 事件時回調。這些事件指的是用戶界面上發生的狀態變化, 好比焦點變動、按鈕按下等等。服務能夠請求「查詢當前窗口中內容」的能力。 開發輔助服務須要繼承該類並實現其抽象方法。

2.1 配置AccessibilityService

在這個例子中,咱們須要監聽的事件是當紅包來或者滑動屏幕時引發的屏幕內容變化,和點開紅包時窗體狀態的變化,所以咱們只須要在配置XML的accessibility-service標籤中加入一條

android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"

或在onAccessibilityEvent回調函數中對事件進行一次類型判斷

final int eventType = event.getEventType();
if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED

|| eventType \== AccessibilityEvent.TYPE\_WINDOW\_CONTENT\_CHANGED) {
     // ...

}

除此以外,因爲咱們只監聽微信,還須要指定微信的包名

android:packageNames="com.tencent.mm"

爲了獲取窗口內容,咱們還須要指定

android:canRetrieveWindowContent="true"

其餘配置請看代碼。

2.2 獲取紅包所在的節點

首先,咱們要獲取當前屏幕的根節點,下面兩種方式效果是相同的:

AccessibilityNodeInfo nodeInfo = event.getSource();

AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();

這裏返回的AccessibilityNodeInfo是窗體內容的節點,包含節點在屏幕上的位置、文本描述、子節點id、可否點擊等信息。從AccessibilityService的角度來看,窗體上的內容表示爲輔助節點樹,雖然和視圖的結構不必定一一對應。換句話說,自定義的視圖能夠本身描述上面的輔助節點信息。當輔助節點傳遞給AccessibilityService以後就不可更改了,若是強行調用引發狀態變化的方法會報錯。

在聊天頁面,每一個紅包上面都有「領取紅包」這幾個字,咱們把它做爲識別紅包的依據。若是你收到了這四個字的文本消息,可能其餘的插件會作出誤判。由於咱們加入了階段的概念,所以不會出現這個問題。

AccessibilityNodeInfo的API中有一個findAccessibilityNodeInfosByText方法容許咱們經過文原本搜索界面中的節點。匹配是大小寫敏感的,它會從遍歷樹的根節點開始查找。API文檔中特別指出,爲了防止建立大量實例,節點回收是調用者的責任,這一點會在接下來的部分中講到。

List<AccessibilityNodeInfo> node1 = nodeInfo.findAccessibilityNodeInfosByText("領取紅包");

2.3 對節點進行操做

AccessibilityNodeInfo一樣暴露了一個API——performAction來對節點進行點擊或者其餘操做。出於安全性考慮,只有這個操做來自AccessibilityService時纔會被執行。

nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);

不過,咱們在調試時發現"領取紅包"的mClickable屬性爲false,說明點擊的監聽加在它父輩的節點上。經過getParent獲取父節點,這個節點是能夠點擊的。

咱們還須要全局的返回操做,最方便的辦法就是performGlobalAction,不過注意這個方法是API 16後纔有的。

performGlobalAction(GLOBAL_ACTION_BACK);

3. 獲取屏幕上的全部紅包

和其餘插件最大的區別是,這個插件的邏輯是獲取屏幕上全部的紅包節點,去掉已經獲取過的以後,將待搶紅包加入隊列,再將隊列中的紅包一個個打開。

3.1 判斷紅包節點是否已被搶過

實現這一點是編寫時最大的障礙。對於通常的Java對象實例來講,除非被GC回收,實例的Id都不會變化。我最初的想法是經過正則表達式匹配下面的十六進制對象id來表示一個紅包。

android.view.accessibility.AccessibilityNodeInfo@2a5a7c; .......

但在測試中,隊列中的部分成包沒有被戳開。進一步觀察發現,新的紅包節點和舊的紅包節點id出現了重複,且出現機率較大。因爲GC日誌正常,我推測AccessibilityNode可能有一個實例池的設計。獲取當前窗體節點樹的時候,從一個可重用的實例池中獲取一個輔助節點信息 (AccessibilityNodeInfo)實例。在接下來的獲取時,仍然從實例池中獲取節點實例,這時可能會重用以前的實例。這樣的設計是有好處的,能夠防止每次返回都建立大量的實例,影響性能。AccessibilityNodeProvider的源碼代表了這樣的設計。

也就是說,爲了標識一個惟一的紅包,只用實例id是不充分的。這個插件採用的是紅包內容+節點實例id的hash來標記。由於同一屏下,同一個節點樹下的節點id是必定不會重複的,滑動屏幕後新紅包的內容和節點id同時重複的機率已經大大減少。更改標識策略後,實測中幾乎沒有出現誤判。

3.2 將新出現的紅包加入待搶隊列

咱們維護了兩個列表,分別記錄待搶紅包和搶過的紅包標識。

private List<AccessibilityNodeInfo> nodesToFetch = new ArrayList<>();

private List<String> fetchedIdentifiers = new ArrayList<>();

在每次讀取聊天屏幕的時候,會檢查這個紅包是否在fetchedIdentifiers隊列中,若是沒有,則加入nodesToFetch隊列。

for (AccessibilityNodeInfo cellNode : fetchNodes) {

String id \= getHongbaoHash(cellNode);
/\* 若是節點沒有被回收且該紅包沒有搶過 \*/
if (id != null && !fetchedIdentifiers.contains(id)) {
    nodesToFetch.add(cellNode);
}

}

4. 打開隊列中的紅包

經過紅包打開後顯示的文本判斷這個紅包是否能夠搶,進行接下來的操做。

4.1 判斷紅包節點是否被重用

這也是實現時的一個坑。前面提到了實例池的設計,當咱們把紅包們加入待搶隊列,戳完一個紅包再回來時,隊列中的其餘紅包節點可能已被回收重用,若是再去點擊這個節點,顯然沒有什麼卵用。

爲了解決這個問題,咱們只能退而求其次,在點開前作一次檢查。若是發現被重用了,就捨棄這個節點,在下一輪fetch的階段從新加入待搶隊列。確認沒有重用當即打開,並把節點hash加入fetchedIdentifiers隊列。這裏若是node失效getHongbaoHash會返回null。

AccessibilityNodeInfo node = nodesToFetch.remove(nodesToFetch.size() - 1);
if (node.getParent() != null) {

String id \= getHongbaoHash(node);
if (id \== null) return;
fetchedIdentifiers.add(id);
Stage.getInstance().entering(Stage.OPENING\_STAGE);
node.getParent().performAction(AccessibilityNodeInfo.ACTION\_CLICK);

}

能夠看出這並非頗有效率的解決方案。在實測中,有時隊列中中紅包失效後被捨棄,但沒有新的AccessibilityEvent發生,接下來的操做都被掛起了。在戳過較多紅包以後,這種狀況表現得尤其明顯,必需要顯式地改變窗體內容才能解決。

4.2 根據紅包類型選擇操做

紅包被戳開前會進行查重。戳開後若是界面上出現了「拆紅包」幾個字,說明紅包尚未被別人搶走,馬上點擊「拆紅包」並將stage標記爲OPENED_STAGE。

此時,另三種狀況代表搶紅包失敗了,直接返回,接下來狀態會被標記爲FETCHED_STAGE。

  1. 「過時」,說明紅包超過有效時間
  2. 「手慢了」,說明紅包發完但沒搶到
  3. 「紅包詳情」,說明你已經搶到過

4.3 防止加載紅包時返回

戳開紅包和紅包加載完之間有一個「正在加載」的過渡動畫,會觸發onAccessibilityEvent回調方法。若是在加載完以前判斷,上述文本還沒出現,會被默認標記爲FETCHED_STAGE並觸發返回。所以,咱們要在返回前特殊斷定這種情形。

咱們引入了TTL來記錄嘗試次數,並返回錯誤值-1。若是到達MAX_TTL時紅包尚未加載出來就捨棄這個紅包。

Stage.getInstance().entering(Stage.OPENING_STAGE);
ttl += 1;
return -1;

轉自:https://github.com/geeeeeeeee...

注:基於https://github.com/geeeeeeeee... 開發,因爲原做者已經兩年沒有維護了,本插件幾乎重寫了全部邏輯,以上技術分析只是大體實現思路,與本項目有部分出入。同時支持了企業微信紅包。因爲項目剛剛完成,尚未在github發佈代碼,後續會將項目開源。

相關文章
相關標籤/搜索