前言
新的一年又到了,又到了拼手速和網速的時候了,網速是硬體條件,沒有辦法了,不過手速這種東西,沒有還不能創造麼,哈哈。其實之前網上有很多老鐵已經分享過類似的外掛的實現方式,但是微信其實本身也是在做對第三方外掛的規避操作,所以,微信的每一個新版本都會修改相同控制元件的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後點選執行。
按如下操作就可以獲取到控制元件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 {
//在首頁
}
複製程式碼
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,而且加了很多其他方面的判斷,比較複雜,所以原始碼就不再放出來了。相信經過上面的分析,自己擼一個也不困難。