微信自动抢红包的实现(Demo已增加查看TopActivity功能)
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
?
先說(shuō)說(shuō)遇到的一些問(wèn)題
去年寫(xiě)過(guò)微信搶紅包插件的實(shí)現(xiàn),但是今年春節(jié)的時(shí)候發(fā)現(xiàn)微信更新之后自己寫(xiě)的插件竟然會(huì)停在開(kāi)紅包的頁(yè)面無(wú)法繼續(xù)向下執(zhí)行,debug之后發(fā)現(xiàn)問(wèn)題是微信團(tuán)隊(duì)把開(kāi)紅包按鈕的文本內(nèi)容現(xiàn)在改成了一張圖片,導(dǎo)致我使用findAccessibilityNodeInfosByText()找不到有效的子節(jié)點(diǎn),也就無(wú)法實(shí)現(xiàn)模擬點(diǎn)擊去打開(kāi)紅包。
于是乎我開(kāi)始嘗試通過(guò)獲取指定控件的ID去實(shí)現(xiàn),迅速打開(kāi)IDE,使用Android Device Monitor查看開(kāi)紅包按鈕的控件id,后面會(huì)附上使用方法,然后使用findAccessibilityNodeInfosByViewId()來(lái)獲取開(kāi)紅包按鈕的節(jié)點(diǎn),結(jié)果當(dāng)然是可以的。
但是這就帶來(lái)了另一個(gè)問(wèn)題:如果微信每次發(fā)版都修改這個(gè)按鈕的控件id,那我的插件也就只能每次都跟隨著修改代碼才能正常使用,事實(shí)也證明微信的確是每次發(fā)版都會(huì)修改此控件的id值。
針對(duì)這個(gè)問(wèn)題我目前的做法是開(kāi)一個(gè)ArrayList記錄微信開(kāi)紅包button所使用過(guò)的id值,然后去遍歷id值通過(guò)findAccessibilityNodeInfosByViewId()獲取節(jié)點(diǎn),當(dāng)然用map存儲(chǔ)id值及其對(duì)應(yīng)微信版本號(hào)用來(lái)做版本兼容會(huì)更好些,誰(shuí)讓我懶呢,懶得去獲取微信版本號(hào)。當(dāng)然,還有種暴力的方法,就是遍歷開(kāi)紅包頁(yè)面的節(jié)點(diǎn)樹(shù)并模擬點(diǎn)擊其下的每一個(gè)能點(diǎn)擊的button,因?yàn)槠鋵?shí)界面里能點(diǎn)擊的就只有關(guān)閉按鈕和開(kāi)紅包按鈕,但關(guān)閉按鈕其實(shí)是個(gè)imageView而不是button。
坑總是一個(gè)接一個(gè),在最近的微信版本更新后,我發(fā)現(xiàn)不僅僅是控件id會(huì)發(fā)生改變,就連某些activity的名稱都被修改了,以及聊天頁(yè)面對(duì)消息推送的處理方式也變了,適配的代碼我已經(jīng)更新到github。
以后微信紅包如果還有其他修改,我會(huì)把適配后的代碼直接更新到github的demo,所以下方的代碼片不一定是最新的,感興趣的同學(xué)可以上github去star一下,demo地址。當(dāng)然適配過(guò)程中遇到的問(wèn)題我還是會(huì)記錄在這里。
核心代碼片如下:
搶紅包:
if ("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI".equals(event.getClassName())) {//當(dāng)前在紅包待開(kāi)頁(yè)面,去拆紅包getLuckyMoney(); } else if ("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI".equals(event.getClassName())) {//拆完紅包后看詳細(xì)紀(jì)錄的界面openNext("查看我的紅包記錄"); } else if ("com.tencent.mm.ui.LauncherUI".equals(event.getClassName())) {//在聊天界面,去點(diǎn)中紅包openLuckyEnvelope(); }自動(dòng)加人:
if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && event.getClassName().equals("com.tencent.mm.ui.LauncherUI")) {//記錄打招呼人數(shù)置零i = 0;//當(dāng)前在微信聊天頁(yè)就點(diǎn)開(kāi)發(fā)現(xiàn)openNext("發(fā)現(xiàn)");//然后跳轉(zhuǎn)到附近的人openDelay(1000, "附近的人"); } else if (event.getClassName().equals("com.tencent.mm.plugin.nearby.ui.NearbyFriendsUI") && eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {prepos = 0;//當(dāng)前在附近的人界面就點(diǎn)選人打招呼AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("米以內(nèi)");Log.d("name", "附近的人列表人數(shù): " + list.size());if (i < (list.size() * page)) {list.get(i % list.size()).performAction(AccessibilityNodeInfo.ACTION_CLICK);list.get(i % list.size()).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);} else if (i == list.size() * page) {//本頁(yè)已全部打招呼,所以下滑列表加載下一頁(yè),每次下滑的距離是一屏for (int i = 0; i < nodeInfo.getChild(0).getChildCount(); i++) {if (nodeInfo.getChild(0).getChild(i).getClassName().equals("android.widget.ListView")) {AccessibilityNodeInfo node_lsv = nodeInfo.getChild(0).getChild(i);node_lsv.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);page++;new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException mE) {mE.printStackTrace();}AccessibilityNodeInfo nodeInfo_ = getRootInActiveWindow();List<AccessibilityNodeInfo> list_ = nodeInfo_.findAccessibilityNodeInfosByText("米以內(nèi)");Log.d("name", "列表人數(shù): " + list_.size());//滑動(dòng)之后,上一頁(yè)的最后一個(gè)item為當(dāng)前的第一個(gè)item,所以從第二個(gè)開(kāi)始打招呼list_.get(1).performAction(AccessibilityNodeInfo.ACTION_CLICK);list_.get(1).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);}}).start();}}} } else if (event.getClassName().equals("com.tencent.mm.plugin.profile.ui.ContactInfoUI") && eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {if (prepos == 1) {//從打招呼界面跳轉(zhuǎn)來(lái)的,則點(diǎn)擊返回到附近的人頁(yè)面performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);i++;} else if (prepos == 0) {//從附近的人跳轉(zhuǎn)來(lái)的,則點(diǎn)擊打招呼按鈕AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();if (nodeInfo == null) {Log.w(TAG, "rootWindow為空");return;}List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("打招呼");if (list.size() > 0) {list.get(list.size() - 1).performAction(AccessibilityNodeInfo.ACTION_CLICK);list.get(list.size() - 1).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);} else {//如果遇到已加為好友的則界面的“打招呼”變?yōu)椤鞍l(fā)消息",所以直接返回上一個(gè)界面并記錄打招呼人數(shù)+1performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);i++;}} } else if (event.getClassName().equals("com.tencent.mm.ui.contact.SayHiEditUI") && eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {//當(dāng)前在打招呼頁(yè)面prepos = 1;//輸入打招呼的內(nèi)容并發(fā)送inputHello(hello);openNext("發(fā)送"); }打開(kāi)通知欄消息:
private void openNotification(AccessibilityEvent event) {if (event.getParcelableData() == null || !(event.getParcelableData() instanceof Notification)) {return;}//將通知欄消息打開(kāi)Notification notification = (Notification) event.getParcelableData();PendingIntent pendingIntent = notification.contentIntent;try {pendingIntent.send();} catch (PendingIntent.CanceledException e) {e.printStackTrace();} }點(diǎn)擊匹配的nodeInfo @param str text關(guān)鍵字:
private void openNext(String str) {AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();if (nodeInfo == null) {Log.w(TAG, "rootWindow為空");Toast.makeText(this, "rootWindow為空", Toast.LENGTH_SHORT).show();return;}List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(str);Log.d("name", "匹配個(gè)數(shù): " + list.size());if (list.size() > 0) {list.get(list.size() - 1).performAction(AccessibilityNodeInfo.ACTION_CLICK);list.get(list.size() - 1).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);} else {Toast.makeText(this, "找不到有效的節(jié)點(diǎn)", Toast.LENGTH_SHORT).show();} }自動(dòng)輸入打招呼內(nèi)容:
private void inputHello(String hello) {AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();//找到當(dāng)前獲取焦點(diǎn)的viewAccessibilityNodeInfo target = nodeInfo.findFocus(AccessibilityNodeInfo.FOCUS_INPUT);if (target == null) {Log.d(TAG, "inputHello: null");return;}ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);ClipData clip = ClipData.newPlainText("label", hello);clipboard.setPrimaryClip(clip);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {target.performAction(AccessibilityNodeInfo.ACTION_PASTE);} }關(guān)于如何獲取app頁(yè)面中控件的id:
在Android Studio中開(kāi)啟Android Device Monitor,選擇設(shè)備后點(diǎn)擊Dump View Hierarchy for UI Automator即可查看
?
?
配置使用AccessibilityService:
在manifest中的配置:
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" /> <serviceandroid:enabled="true"android:exported="true"android:label="@string/app_name"android:name=".AutoService"android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"><intent-filter><action android:name="android.accessibilityservice.AccessibilityService"/></intent-filter><meta-dataandroid:name="android.accessibilityservice"android:resource="@xml/envelope_service_config"/> </service>meta-data中的xml資源文件:
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged"android:accessibilityFeedbackType="feedbackGeneric"android:accessibilityFlags=""android:canRetrieveWindowContent="true"android:description="@string/app_name"android:notificationTimeout="100"android:packageNames="com.tencent.mm,com.huawei.android.launcher" />其中packageName用于配置你想要監(jiān)測(cè)的包名,如果多個(gè)則用逗號(hào)隔開(kāi)
accessibilityEventTypes表示該服務(wù)可監(jiān)測(cè)界面中哪些事件類(lèi)型,如窗口打開(kāi),滑動(dòng)等,具體值可查看api
accessibilityFeedbackType:表示反饋方式,比如是語(yǔ)音播放,還是震動(dòng)
canRetrieveWindowContent:表示該服務(wù)能否訪問(wèn)活動(dòng)窗口中的內(nèi)容
notificationTimeout:接受事件的時(shí)間間隔
當(dāng)然,除了以meta-data的方式靜態(tài)配置,也可通過(guò)在服務(wù)啟動(dòng)時(shí)的onServiceConnected()方法中調(diào)用setServiceInfo(AccessibilityServiceInfo)進(jìn)行動(dòng)態(tài)配置。
補(bǔ)充:
幾種常用accessibilityEventType事件類(lèi)型:
TYPE_WINDOW_STATE_CHANGED: 窗口狀態(tài)改變事件類(lèi)型,打開(kāi)PopupWindow、dialog、menu等
TYPE_NOTIFICATION_STATE_CHANGED: 通知欄事件
TYPE_WINDOW_CONTENT_CHANGED: 窗口中內(nèi)容改變
TYPE_VIEW_SCROLLED: 控件滑動(dòng)事件
TYPE_WINDOWS_CHANGED: 顯示窗口改變
TYPE_VIEW_TEXT_CHANGED : editText控件的內(nèi)容發(fā)生改變
TYPE_TOUCH_INTERACTION_START: 用戶開(kāi)始觸摸屏幕
TYPE_TOUCH_INTERACTION_END: 用戶停止觸摸屏幕
其中TYPE_WINDOW_CONTENT_CHANGED 又可以細(xì)分為4個(gè)二級(jí)類(lèi)型:
1.CONTENT_CHANGE_TYPE_SUBTREE: 節(jié)點(diǎn)發(fā)生增減
2.CONTENT_CHANGE_TYPE_TEXT: 節(jié)點(diǎn)文本發(fā)生改變
3.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION: 節(jié)點(diǎn)的內(nèi)容描述發(fā)生改變,即控件的contentDescription屬性發(fā)生改變
4.CONTENT_CHANGE_TYPE_UNDEFINED: 未定義類(lèi)型,即除上面三種之外的類(lèi)型
接下來(lái),或許你可以自己嘗試下使用AccessibilityService實(shí)現(xiàn)app的自動(dòng)安裝/批量安裝,去學(xué)習(xí)吧,騷年!
轉(zhuǎn)載于:https://my.oschina.net/u/3729361/blog/1584214
總結(jié)
以上是生活随笔為你收集整理的微信自动抢红包的实现(Demo已增加查看TopActivity功能)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java jdbc gbase_Gbas
- 下一篇: Vs插件 VisualSvn破解