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