AccessibilityService重新整理:微信自动抢红包、微信自动向附近的人打招呼
先說說遇到的一些問題
?去年寫過微信搶紅包插件的實現,但春節的時候發現微信更新之后我的插件竟然會停在開紅包的頁面無法繼續向下執行,debug之后發現問題是微信的開紅包按鈕現在被改成了圖片,導致我使用findAccessibilityNodeInfosByText()找不到有效的子節點,也就無法實現模擬點擊去打開紅包。 于是乎我開始嘗試通過獲取控件ID去實現,迅速打開IDE,使用Android Device Monitor查看開紅包按鈕的控件id,后面會附上使用方法,然后使用findAccessibilityNodeInfosByViewId()來獲取開紅包按鈕的節點,結果當然是可以的。 但是這就帶來了另一個問題:如果每次微信發版后這個按鈕控件的id發生變化,那插件也就只能跟隨著修改代碼才能正常使用。 針對這個問題我目前的做法是開一個ArrayList記錄微信開紅包button所使用過的id值,然后去遍歷id值通過findAccessibilityNodeInfosByViewId()獲取節點,當然用map存儲id值及其對應微信版本號用來做版本兼容會更好些,誰讓我懶呢,懶得去獲取微信版本號。當然,還有種暴力的方法,就是遍歷開紅包頁面的節點樹并模擬點擊其下的每一個能點擊的button,因為其實界面里能點擊的就只有關閉按鈕和開紅包按鈕,但關閉按鈕其實是個imageView而不是button。
?坑總是一個接一個,在最近的微信版本更新后,我發現不僅僅是控件id會發生改變,插件關注的某些activity類名也開始被引入混淆,以及聊天頁面對消息推送的處理方式也變了,適配的代碼我已經更新到github。 以后微信紅包如果還有其他修改,我會把適配后的代碼直接更新到github的demo,所以下方的代碼片不一定是最新的,感興趣的同學可以上github去star一下,demo地址。當然適配過程中遇到的問題我還是會記錄在這里。
核心代碼片如下:
搶紅包:
if ("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI".equals(event.getClassName())) {//當前在紅包待開頁面,去拆紅包getLuckyMoney(); } else if ("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI".equals(event.getClassName())) {//拆完紅包后看詳細紀錄的界面openNext("查看我的紅包記錄"); } else if ("com.tencent.mm.ui.LauncherUI".equals(event.getClassName())) {//在聊天界面,去點中紅包openLuckyEnvelope(); } 復制代碼自動加人:
if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && event.getClassName().equals("com.tencent.mm.ui.LauncherUI")) {//記錄打招呼人數置零i = 0;//當前在微信聊天頁就點開發現openNext("發現");//然后跳轉到附近的人openDelay(1000, "附近的人"); } else if (event.getClassName().equals("com.tencent.mm.plugin.nearby.ui.NearbyFriendsUI") && eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {prepos = 0;//當前在附近的人界面就點選人打招呼AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("米以內");Log.d("name", "附近的人列表人數: " + 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) {//本頁已全部打招呼,所以下滑列表加載下一頁,每次下滑的距離是一屏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("米以內");Log.d("name", "列表人數: " + list_.size());//滑動之后,上一頁的最后一個item為當前的第一個item,所以從第二個開始打招呼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) {//從打招呼界面跳轉來的,則點擊返回到附近的人頁面performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);i++;} else if (prepos == 0) {//從附近的人跳轉來的,則點擊打招呼按鈕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 {//如果遇到已加為好友的則界面的“打招呼”變為“發消息",所以直接返回上一個界面并記錄打招呼人數+1performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);i++;}} } else if (event.getClassName().equals("com.tencent.mm.ui.contact.SayHiEditUI") && eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {//當前在打招呼頁面prepos = 1;//輸入打招呼的內容并發送inputHello(hello);openNext("發送"); } 復制代碼打開通知欄消息:
private void openNotification(AccessibilityEvent event) {if (event.getParcelableData() == null || !(event.getParcelableData() instanceof Notification)) {return;}//將通知欄消息打開Notification notification = (Notification) event.getParcelableData();PendingIntent pendingIntent = notification.contentIntent;try {pendingIntent.send();} catch (PendingIntent.CanceledException e) {e.printStackTrace();} } 復制代碼點擊匹配的nodeInfo @param str text關鍵字:
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", "匹配個數: " + 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, "找不到有效的節點", Toast.LENGTH_SHORT).show();} } 復制代碼自動輸入打招呼內容:
private void inputHello(String hello) {AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();//找到當前獲取焦點的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);} } 復制代碼關于如何獲取app頁面中控件的id:
在Android Studio中開啟Android Device Monitor,選擇設備后點擊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用于配置你想要監測的包名,如果多個則用逗號隔開 accessibilityEventTypes表示該服務可監測界面中哪些事件類型,如窗口打開,滑動等,具體值可查看api accessibilityFeedbackType:表示反饋方式,比如是語音播放,還是震動 canRetrieveWindowContent:表示該服務能否訪問活動窗口中的內容 notificationTimeout:接受事件的時間間隔
當然,除了以meta-data的方式靜態配置,也可通過在服務啟動時的onServiceConnected()方法中調用setServiceInfo(AccessibilityServiceInfo)進行動態配置。
補充:
幾種常用accessibilityEventType事件類型: TYPE_WINDOW_STATE_CHANGED: 窗口狀態改變事件類型,打開PopupWindow、dialog、menu等 TYPE_NOTIFICATION_STATE_CHANGED: 通知欄事件 TYPE_WINDOW_CONTENT_CHANGED: 窗口中內容改變 TYPE_VIEW_SCROLLED: 控件滑動事件 TYPE_WINDOWS_CHANGED: 顯示窗口改變 TYPE_VIEW_TEXT_CHANGED : editText控件的內容發生改變 TYPE_TOUCH_INTERACTION_START: 用戶開始觸摸屏幕 TYPE_TOUCH_INTERACTION_END: 用戶停止觸摸屏幕
其中TYPE_WINDOW_CONTENT_CHANGED 又可以細分為4個二級類型: 1.CONTENT_CHANGE_TYPE_SUBTREE: 節點發生增減 2.CONTENT_CHANGE_TYPE_TEXT: 節點文本發生改變 3.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION: 節點的內容描述發生改變,即控件的contentDescription屬性發生改變 4.CONTENT_CHANGE_TYPE_UNDEFINED: 未定義類型,即除上面三種之外的類型
接下來,或許你可以自己嘗試下使用AccessibilityService實現app的自動安裝/批量安裝,去學習吧,騷年! demo地址
總結
以上是生活随笔為你收集整理的AccessibilityService重新整理:微信自动抢红包、微信自动向附近的人打招呼的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux管道相关命令
- 下一篇: 快乐随心情