快到年底了,又到了拼手速搶紅包的時候了;其實很早之前就做過搶紅包軟件了,包括QQ和微信;但是大家都懂的,自己一個月前寫的代碼現在看起來都像是一坨shit一樣;所以自己開始重新寫一個搶紅包的軟件(其實是因為實在是太簡單了),只做微信,因為QQ發紅包的確用的太少了,而且QQ紅包花樣也太多了,什么唱歌、畫畫、成語接龍...
目標
- 快,天下武功無堅不摧、唯快不破,肯定要比人的手速快
- 準,只要你手機解鎖了,在任意一個界面都可以快速搶到紅包
- 狠,其實狠不狠沒什么關系了,最重要的是全自動,自己不用任何操作,不然怎么解放雙手
- 穩,肯定要能一直搶紅包,來一個搶一個,來兩個搶兩個,搶紅包一時爽,一直搶一直爽;
手機配置要求
- Android系統 7.0及以上,輔助功能7.0以上支持模擬點擊,模擬點擊不是必須的,但是對于實現快很重要
- 手機不能太垃圾了,手機慢有外掛也發揮不出來呀
實現原理
實現方法就是利用Android輔助功能,開啟輔助功能相當于開啟了一個服務,在手機界面改變的時候,就能監聽到該頁面的一些信息并且能拿到界面的一些控件,然后可以對控件進行模擬點擊,從而實現我們想要的功能。
除此以外,不僅能夠對獲取到的控件進行模擬點擊,在Android7.0及以上的版本,我們可以模擬任意位置的點擊包括觸摸、滑動等等,就是說我們可以實現任何人能夠進行的操作,這個是很有用的,可以做出很多有意思的東西,如果再配上截圖、錄屏和圖像識別,就更有意思了。
模擬點擊,就是說我們的手機界面自己動,整個流程像是一只手在幫你操作一樣的;其實我見過更牛逼的方法,連解鎖都不需要直接就領了紅包,界面沒有任何變化的;感覺上是通過通信,發數據給微信服務器實現的,當然這種是需要root權限的,并且得去解析微信的通信協議,我自然沒時間去搞(其實有時間也不一定能搞出來)。
具體實現
輔助功能
首先是輔助功能,新建一個Service繼承AccessibilityService
public class LuckMoneyService extends AccessibilityService
然后去AndroidManifest文件里面去注冊一下這個Service
<service
android:name=".service.LuckMoneyService"
android: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/accessible_service_wx_config" />
</service>
在meta-data
節點下有個resource
值,這是個xml文件,里面配置了該輔助的一些信息,在res目錄下新建一個文件夾,名字叫xml,然后新建一個xml文件,名字和resource
配置的一樣就行了
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged|typeNotificationStateChanged"
android:accessibilityFeedbackType="feedbackAllMask"
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews|flagReportViewIds"
android:canRetrieveWindowContent="true"
android:canRequestFilterKeyEvents="true"
android:description="@string/wx_luck_money"
android:canRequestEnhancedWebAccessibility="true"
android:notificationTimeout="20"
android:packageNames="com.tencent.mm"
android:canPerformGestures="true" />
里面配置了一些參數,比如notificationTimeout
是指定多少毫秒監聽一次界面變化的,packageNames
是指定監聽哪個應用的,刪掉這個配置就是監聽全局,建議一定要刪除掉,我這里只是展示用,description
是對于該輔助的描述,其他配置不管也罷。
然后在LuckMoneyService里面重寫一下onAccessibilityEvent方法
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
//界面發生了變化
}
每當界面改變的時候就會回調這個方法,通過event
我們就可以獲取到界面的信息包括界面上的控件
簡單的用法
//獲取當前界面包名
String packageName = event.getPackageName().toString();
//獲取當前類名
String className = event.getClassName().toString();
//獲取當前界面父布局的控件
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
//在父布局里面根據子控件**顯示的文字**找到該子控件
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text);
//在父布局里面根據子控件的**id**找到該子控件
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);
//點擊該控件
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
上面的操作都比較基礎,根據控件顯示的文字查找控件,找出來的肯定是TextView和Button了,根據ID查找控件,ID就是指的寫布局文件的時候設置的控件的ID
模擬觸摸
模擬觸摸就是可以模擬人的觸摸動作,也比較簡單
protected void gestureOnScreen(Path path, long startTime, long duration,
AccessibilityService.GestureResultCallback callback) {
GestureDescription.Builder builde = new GestureDescription.Builder();
builde.addStroke(new GestureDescription.StrokeDescription(path, startTime, duration));
GestureDescription gestureDescription = builde.build();
dispatchGesture(gestureDescription, callback, null);
}
可以看到需要傳入path
就是一個路徑嘛,模擬滑動的路徑,用canvas畫過畫的都知道這東西還是比較簡單的,不清楚也沒關系,繼續看,startTime
就是多久后開始模擬事件,duration
就是該滑動的時間,其他回調什么的為空就可以了;
輔助功能能做的東西大概就上面這些了,接下來看看
微信應用外的紅包處理
首先實現在微信界面外怎么搶紅包,在微信界面外有紅包出現必然會在通知欄會顯示微信紅包(如果沒開通知消息,那你自己開一下不就完事了嗎),只需要在回調方法里面判斷一下是不是通知消息,如果是通知消息,獲取里面的信息,判斷是不是微信紅包通知消息,是就點擊該消息,會自動跳轉到聊天界面;
因為我們是監聽界面變化來實現功能的,所以在一個界面觸發了界面變化的時候,接下來的處理就應該交給下一個界面的方法了,所以微信界面外的操作就是這些了
/**
* 紅包標識字段
*/
public static final String HONG_BAO_TXT = "[微信紅包]";
//通知欄消息,判斷是不是紅包消息
if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) {
Notification notification = (Notification) event.getParcelableData();
//獲取通知消息詳情
String content = notification.tickerText.toString();
//解析消息
String[] msg = content.split(":");
String text = msg[1].trim();
if (text.contains(HONG_BAO_TXT)) {
PendingIntent pendingIntent = notification.contentIntent;
try {
//點擊消息,進入聊天界面
pendingIntent.send();
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
}
}
其中PendingIntent這個東西寫過通知欄的都知道,這個是設置跳轉到哪個界面的,所以直接調用它的方法就完成了界面跳轉了
聊天界面的紅包處理
界面外的紅包點擊通知欄消息就來到了聊天界面,其實所有的界面都必須經過這個界面才能領取到紅包,所以這個界面很重要;
實現
思路是這樣的,聊天消息肯定是一個列表控件,其實是個ListView,而且肯定有控件ID,我們獲取到這個ListView,然后遍歷它的每個消息(只能遍歷到當前界面顯示的),判斷這個消息是不是微信紅包,如果是,并且未被領取,而且這個紅包還得是別人發的,不是自己發的,我們才去點擊這個消息,觸發界面變化,然后丟給下一個界面處理;
//獲取聊天消息列表List控件
AccessibilityNodeInfo nodeInfo = findViewByID(DETAIL_CHAT_LIST_ID);
//這個消息列表不為空,那么肯定在聊天詳情頁
if (nodeInfo != null) {
//判斷有沒有未領取紅包并進行點擊
clickItem(nodeInfo);
return;
}
/**
* 進行消息列表未領取紅包的點擊
*
* @param nodeInfo
*/
private void clickItem(AccessibilityNodeInfo nodeInfo) {
//遍歷消息列表的每個消息
for (int i = 0; i < nodeInfo.getChildCount(); i++) {
//獲取到子控件
AccessibilityNodeInfo nodeInfoChild = nodeInfo.getChild(i);
//獲取紅包控件
AccessibilityNodeInfo target = findViewByID(nodeInfoChild, AUM_ID);
//獲取頭像的控件
AccessibilityNodeInfo avatar = findViewByID(nodeInfoChild, AVATAR_ID);
boolean selfLuckMoney = false;
//獲取頭像的位置,判斷紅包是否是自己發的,自己發的不搶
if (avatar != null) {
Rect rect = new Rect();
avatar.getBoundsInScreen(rect);
if (rect.left > screenWidth / 2) {
selfLuckMoney = true;
}
}
//如果不是自己發的紅包,并且獲取到的微信紅包這個控件不為空
if (target != null && !selfLuckMoney) {
//已領取這個控件為空,紅包還沒有被領取
if (findViewByID(nodeInfoChild, AUL_ID) == null) {
//點擊紅包控件
performViewClick(target);
return;
}
}
}
}
里面每個細節都注釋了,獲取ListView控件,獲取到了說明是在消息界面,獲取到消息列表的每一個控件,根據 是否是紅包消息,是否是別人發的,是否是未領取的三點,去判斷是否是可以領取的紅包,然后點擊可領取的紅包,到達彈出開的這個彈窗的界面;
monitor
如何獲取這個ListView控件的ID呢,而我又是如何知道是ListView的呢,可以通過一個工具來實現,就是在sdk工具下面的一個叫monitor的工具,其實之前的AndroidStudio是帶這個工具的,但是后來界面上是沒有了,但是其實還在的
/Users/Tyhj/Library/Android/sdk/tools/monitor
連上手機,打開這個工具,手機上打開你要查看的界面,點擊工具手機的小手機的圖標,就會截屏,顯示出這個界面的信息
紅包彈窗界面處理
同樣的紅包彈窗這個界面也是必須經過的,十分重要;你要說這個彈窗界面也比較簡單,我們判斷一下是不是這個界面,然后點擊開不就完事兒了;測試可以發現,這個彈窗出現的時候,當前的界面className是這個
**
* 紅包彈出的class的名字
*/
private static final String ACTIVITY_DIALOG_LUCKYMONEY = "com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyNotHookReceiveUI";
事情沒有這么容易,當我去獲取這個開的這個控件的時候,發現為空,獲取不到,其實整個彈窗都獲取不到,遇到這個問題的人肯定不少;
//獲取開的控件布局
AccessibilityNodeInfo target = findViewByID("com.tencent.mm:id/dan");
其實深究下去,發現獲取根布局都為空了,測試發現必須等待一段時間再去獲取這個彈窗才行,但是等多久呢,大概幾百毫秒吧,不定時的,不同手機也不一定,那么隨便設一個就不行,因為你時間設置小了,程序可能會卡在這里搶不了紅包了,肯定不行;設置大了,行,但是影響速度呀。那么開個循環去獲取直到獲取到不為空行嗎?不行,奇怪的就是你一次去獲取為空了,之后獲取都為空了;只有等待一段時間后第一次去獲取才不為空,這TMD就很奇怪了,看了一下的確沒法解決;這個問題其實和手機有關,在三星s9上的確有問題,在華為nova5 pro上沒問題
//獲取根布局
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
如果獲取不到,那其實還有個辦法就是模擬點擊,在紅包彈窗彈出來的時候,我瘋狂點擊這個開字的位置,就行了;開字的位置可以通過屏幕比例來計算出來,這個就算不同的手機屏幕都可以點擊到這個開字;但是其實還是有個問題,彈窗彈出其實有個動畫,不同的手機其實彈出的時間也不不一樣,華為nova5 pro都不用等待,可以直接執行點擊操作,三星s9得等待200ms左右
//當前為紅包彈出窗(那個開的那個彈窗)
if (className.equals(ACTIVITY_DIALOG_LUCKYMONEY)) {
//進行紅包開點擊
clickOpen();
return;
}
/**
* 點擊開紅包按鈕
*/
private void clickOpen() {
//等待紅包彈窗完成,直接使用模擬點擊比較快,根據手機性能等待響應的時長
SystemClock.sleep(100);
for (int i = 0; i < 20; i++) {
SystemClock.sleep(10);
//計算了一下這個開字在屏幕中的位置,按照屏幕比例計算
clickOnScreen(screenWidth / 2, screenHeight * POINT_Y_SCAL, 1, null);
}
/*AccessibilityNodeInfo target = findViewByID("com.tencent.mm:id/dan");
if (target != null) {
performViewClick(target);
return;
} else {
//如果沒有找到按鈕,再進行模擬點擊
for (int i = 0; i < 20; i++) {
SystemClock.sleep(10);
clickOnScreen(screenWidth / 2, screenHeight * POINT_Y_SCAL, 1, null);
}
}*/
}
點擊了這個開字后,進入了紅包詳情頁,進行下一步處理。
紅包詳情頁處理
進入了紅包詳情頁,紅包已經到手了,想要繼續搶紅包,肯定需要退出去,這個簡單,有返回鍵的方法;這時候你可以返回聊天界面繼續搶這個群的紅包(如果專搶一個群的,這樣效率高),也可以返回到最近消息列表(微信主頁面第一個界面),可以搶其他群的紅包(搶其多個群的紅包,這樣效率高),也可以退回手機主界面(搶紅包效率低,因為還需要點擊通知欄消息進去);可以設置一下,如果開啟專搶一個群,就退回該群聊天界面,否則退回最近消息列表界面。
//紅包領取后的詳情頁面,自動返回
if (className.equals(LUCKY_MONEY_DETAIL)) {
//返回聊天界面
performGlobalAction(GLOBAL_ACTION_BACK);
//如果不是專搶一個群
if (!isSingle) {
SystemClock.sleep(50);
performGlobalAction(GLOBAL_ACTION_BACK);
}
return;
}
最近消息列表界面處理
當領完紅包后,退出到最近消息列表界面是比較好的選擇;這個界面上當收到紅包消息通知欄是不會有提醒的;我們需要根據界面的顯示去判斷有沒有紅包;其實也是特別簡單,它也是一個ListView,同樣的遍歷一下每個item,判斷有沒有微信紅包消息,然后點擊進入聊天消息界面
//在最近聊天列表,檢測有沒有紅包消息出現
nodeInfo = findViewByID(HUMAN_LIST);
//聯系人列表
if (nodeInfo != null) {
//判斷最近聊天列表有沒有未領取紅包
clickHumanItem(nodeInfo);
return;
}
/**
* 進行聯系人列表的紅包消息點擊
*
* @param nodeInfo
*/
private void clickHumanItem(AccessibilityNodeInfo nodeInfo) {
for (int i = 0; i < nodeInfo.getChildCount(); i++) {
AccessibilityNodeInfo nodeInfoChild = nodeInfo.getChild(i);
AccessibilityNodeInfo target = findViewByID(nodeInfoChild, HUMAN_LIST_TXT_ID);
if (target != null && target.getText() != null && target.getText().toString().contains(HONG_BAO_TXT)) {
performViewClick(target);
return;
}
}
}
看似沒有問題,實則有一個問題,就是在這個聊天列表里面,沒法判斷這個紅包是別人發的還是你自己發的,如果是你自己發的那肯定有問題的,這是一個坑,當然可以通過保存一些數據,比如說第一次進去后發現是自己發的紅包就退出來,如果界面沒變化第二次就不再進行點擊了;但是其實問題也不大吧,最多就是你發完紅包后自己再發個消息就可以避免了。
測試總結
其實到這里就全完成了,實際效果也不錯,測了一下,4個人和一個輔助比,發了20次紅包,輔助大概能搶到18次吧,并不是百分百搶到,主要是人有準備的話瘋狂點屏幕其實也挺快的(單身20年的同學的手速不得不服,畢竟有個地方我還是sleep了100毫秒的,其實去掉應該更快的),一般情況下輔助還是有絕對優勢的。
一般情況下感覺用到的這些控件ID、布局、界面所在的類、包名什么的是不太會改變的,當然如果微信版本升級比較大,估計布局什么的有變化,還得根據新的布局去重新實現,但是思路其實都是一樣的。
更新
更新前搶一次紅包時間大概為1300毫秒左右,更新了彈窗等待那一步,不等待直接模擬點擊一次也是可以的(自己手機測試通過),更新后時間減少到1000毫秒左右;自己測試手速搶紅包,時間大概是1400毫秒以上,感覺真的比人手速快了
項目地址
里面有一些方法是封裝了的,方便調用,具體實現可以看代碼
原文地址:Android微信搶紅包輔助
github地址:Android微信搶紅包輔助
軟件下載地址(老版本):https://github.com/tyhjh/LuckMoney/raw/master/%E6%8A%A2%E7%BA%A2%E5%8C%85.apk