新的一年又到了,又到了拼手速和網速的時候了,網速是硬件條件,沒有辦法了,不過手速這種東西,沒有還不能創造麼,哈哈。其實以前網上有不少老鐵已經分享過相似的插件的實現方式,可是微信其實自己也是在作對第三方插件的規避操做,因此,微信的每個新版本都會修改相同控件的id,因此以前的不少插件都不能再使用了,並且以前的有些判斷方法也不能再適用新版本的微信,因此我研究了幾天,新版全自動微信搶紅包助手就應運而生了,老規矩,給你們看下效果。php
全自動搶紅包無非也就是寫個邏輯代替你手動點擊的過程,要實現這個功能,就要用到Android提供的無障礙服務(AccessibilityService)的功能。輔助功能能夠獲得系統級別的事件和服務,經過這些事件和服務,咱們就能監控微信的紅包消息,不過第三方應用的輔助功能都須要手動開啓。java
關於AccessibilityService的使用,簡單的介紹下,不作過多的介紹,簡單的分紅三部:node
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:輔助服務的描述信息,會顯示在無障礙服務的描述那裏。android
<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只判斷是不是通知欄事件比較靠譜。app
if ( event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED ) {
//通知欄事件
} else {
//非通知欄事件 處理其餘事件
}
複製代碼
由於通知欄事件比較簡單,直接點擊通知欄就行了,點擊通知欄後會本身跳轉到聊天頁面,剩下的事情也是交給對非通知欄事件來處理。ide
那麼如今須要考慮的事情有如下幾點: 第一:如何獲取咱們但願處理的控件並操控它。 第二:如何判斷當前在哪一個頁面,是聊天列表頁面,是聊天頁面,是打開紅包的頁面仍是打開紅包後的詳情頁面。 第三:在不一樣的頁面咱們須要作什麼事情,點擊哪一個控件。工具
獲取一個有文本的控件有兩種方式,一種是根據文本找控件,一種是根據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後點擊運行。
就目前來看,咱們須要區分聊天列表頁面(就是微信的首頁),聊天頁面(包括私信和羣聊天),點擊紅包後的紅包頁面(這裏包括兩種狀況,一種是紅包尚未被別人搶,點「開」按鈕會進入到詳情頁面,還有一種是紅包被別人搶了,此時點擊「開」出現的是「手慢了,紅包派完了」的頁面)和開紅包後的詳情頁面。
看過以前老鐵判斷首頁的方式是判斷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 {
//在首頁
}
複製代碼
其實判斷在哪一個頁面,最主要的就是找其餘頁面沒有的特徵控件,好比在聊天頁面中,右下角那個「+」按鈕纔是最獨特的,因此能夠根據是否有這個按鈕來判斷是不是聊天頁面。可是這個只能判斷是不是聊天頁面,不能判斷是私信頁面仍是羣聊頁面。在對比了私信和羣聊的頁面以後,沒有找到特別穩的方式來判斷聊天類型,只能根據標題來判斷,羣聊的標題後面必定會有一個括號,括號裏面是羣成員人數。因此咱們只須要來判斷標題最後是否有一個括號裏面是數字,固然這種方式不是特別準,不過夠用了,通常用戶也不會這個起暱稱,萬一這樣起了也只是判斷類型出錯,也不會影響搶紅包的功能。
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 {
//私聊 默認私聊
}
}
}
}
複製代碼
還記得以前提到過的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));
}
}
複製代碼
這個頁面是最簡單的,根據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());
}
}
複製代碼
其實上面已經捎帶分析了一些。咱們從首頁開始分析
//獲取首頁的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;
}
}
}
}
複製代碼
//在聊天頁面
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,並且加了不少其餘方面的判斷,比較複雜,因此源碼就再也不放出來了。相信通過上面的分析,本身擼一個也不困難。