前言
最近在研究Android輔助服務,實現了這個小工具,也算是對最近學習的一個總結。
原理
通過Android 無障礙輔助功能實現模擬點擊控件來實現
檢查被刪好友有兩種方法:
- 向好友發送一條消息,如果對方已經把你刪除,則消息發送失敗。
- 建群法:新建一個不大于40人的群,如果其中有好友已經把你刪除,微信會有條消息提示
- 整體執行步驟:啟動微信->點擊+號->發起群聊->選擇35個聯系人->點擊確定->點擊群里詳情->刪除并退出,依次輪詢執行,知道所有好友輪詢結束。
Paste_Image.png
本文采用建群的方式進行檢查。
本人微信有300好友,全部檢測一遍只需3分鐘即可,親測已經成功,
但是建群沒有超過40人 會有個別好友會受到打擾消息,可能是微信哪里的bug,具體原因未知。
說明和app預覽
此軟件通過無障礙輔助進行模擬點擊,無任何外掛木馬,無封號風險
Paste_Image.png
Paste_Image.png
使用方法
- Android 手機一部,登錄微信賬號
- 安裝輔助軟件apk下載地址請點擊這里
- 打開輔助軟件-點擊打開輔助功能按鈕,跳轉到無障礙輔助設置把輔助開關打開。
- 點擊開始檢查按鈕,開始一系列的模擬點擊,檢查完成后會跳轉到一個列表會把被刪好友列表展示出來。
實現步驟:
- 新建Android Studio 工程,新建一個Services類集成AccessibilityService,實現對應方法,詳細介紹見代碼注釋
/**
* Created by wanglj on 16/10/20.
*/
public class InspectWechatFriendService extends AccessibilityService{
@Override
protected void onServiceConnected() {//輔助服務被打開后 執行此方法
super.onServiceConnected();
}
@Override
public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {//監聽手機當前窗口狀態改變 比如 Activity 跳轉,內容變化,按鈕點擊等事件
}
@Override
public void onInterrupt() {//輔助服務被關閉 執行此方法
}
}
- 在manifests.xml文件中注冊此服務:
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.wanglj.inspectwechatfriend"
xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service
android:name=".accessibility.InspectWechatFriendService"
android:enabled="true"
android:exported="true"
android:label="@string/app_name"
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/inspect_wechat_friend"/>
</service>
</application>
</manifest>
- 新建res/xml/inspect_wechat_friend.xml文件
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeWindowStateChanged"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags=""
android:canRetrieveWindowContent="true"
android:notificationTimeout="100"
android:description="@string/accessibility"
/>
- 實現對某個控件的點擊
通過getRootInActiveWindow方法獲取當前窗口信息,通過findAccessibilityNodeInfosByText方法找到當前對應控件進行模擬點擊
public class PerformClickUtils {
/**
* 在當前頁面查找文字內容并點擊
*
* @param text
*/
public static void findTextAndClick(AccessibilityService accessibilityService,String text) {
AccessibilityNodeInfo accessibilityNodeInfo = accessibilityService.getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
return;
}
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
if (nodeInfo != null && (text.equals(nodeInfo.getText()) || text.equals(nodeInfo.getContentDescription()))) {
performClick(nodeInfo);
break;
}
}
}
}
/**
* 檢查viewId進行點擊
*
* @param accessibilityService
* @param id
*/
public static void findViewIdAndClick(AccessibilityService accessibilityService,String id) {
AccessibilityNodeInfo accessibilityNodeInfo = accessibilityService.getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
return;
}
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
if (nodeInfo != null) {
performClick(nodeInfo);
break;
}
}
}
}
/**
* 在當前頁面查找對話框文字內容并點擊
*
* @param text1 默認點擊text1
* @param text2
*/
public static void findDialogAndClick(AccessibilityService accessibilityService,String text1, String text2) {
AccessibilityNodeInfo accessibilityNodeInfo = accessibilityService.getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
return;
}
List<AccessibilityNodeInfo> dialogWait = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text1);
List<AccessibilityNodeInfo> dialogConfirm = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text2);
if (!dialogWait.isEmpty() && !dialogConfirm.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : dialogWait) {
if (nodeInfo != null && text1.equals(nodeInfo.getText())) {
performClick(nodeInfo);
break;
}
}
}
}
//模擬點擊事件
public static void performClick(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null) {
return;
}
if (nodeInfo.isClickable()) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
} else {
performClick(nodeInfo.getParent());
}
}
//模擬返回事件
public static void performBack(AccessibilityService service) {
if (service == null) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
}
}
}
- 監聽窗口事件
獲取當前窗口的classname 通過classname進行判斷當前手機處于某個界面
下面代碼邏輯:
- 如果當前為微信主頁面,則點擊+號然后點擊發起群聊
- 如果當前頁面為創建群聊選擇聯系人界面,則開啟一個while循環模擬滾動時間以及點擊選擇框,當選擇用戶到39人時,則模擬點擊確定按鈕發起群聊。
- 發起群聊后,微信會返回哪些用戶不是你的好友,這個時候,取到當前控件的字符串并截取用戶列表保存到本地。
- 獲取到不是好友的用戶后,點擊右上角進入群聊詳情,點擊刪除并退出
- 退出后又回到微信主頁面,依次執行1 2 3 4步驟,直到滾動到聯系人最底部為止。
- 當所有用戶執行完成后,則啟動檢查結果界面,列出所有被刪好友。
下面為對應邏輯代碼:
@Override
public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {//監聽手機當前窗口狀態改變 比如 Activity 跳轉,內容變化,按鈕點擊等事件
//如果手機當前界面的窗口發送變化
if (accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
//獲取當前activity的類名:
String currentWindowActivity = accessibilityEvent.getClassName().toString();
if(!hasComplete){
if ("com.tencent.mm.ui.contact.SelectContactUI".equals(currentWindowActivity)) {
canChecked = true;
createGroup();
} else if ("com.tencent.mm.ui.chatting.ChattingUI".equals(currentWindowActivity)) {
getDeleteFriend();
} else if ("com.tencent.mm.plugin.chatroom.ui.ChatroomInfoUI".equals(currentWindowActivity)) {
deleteGroup();
}else if("com.tencent.mm.ui.LauncherUI".equals(currentWindowActivity)){
PerformClickUtils.findTextAndClick(this,"更多功能按鈕");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
PerformClickUtils.findTextAndClick(this,"發起群聊");
}
}else{
nickNameList.clear();
deleteList.clear();
sortItems.clear();
startActivity(new Intent(this, DeleteFriendListActivity.class));
}
}
}
/**
* 模擬創建群組步驟
*/
private void createGroup() {
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
return;
}
List<AccessibilityNodeInfo> listview = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(selectUI_listview_id);
int count = 0;
if (!listview.isEmpty()) {
while (canChecked) {
List<AccessibilityNodeInfo> checkboxList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(selectUI_checkbox_id);
List<AccessibilityNodeInfo> sortList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(selectUI_sortitem_id);
for(AccessibilityNodeInfo nodeInfo:sortList){
if(nodeInfo != null && nodeInfo.getText()!= null){
sortItems.add(nodeInfo.getText().toString());
}
}
for (AccessibilityNodeInfo nodeInfo : checkboxList) {
String nickname = nodeInfo.getParent().findAccessibilityNodeInfosByViewId(selectUI_nickname_id).get(0).getText().toString();
Log.e(TAG, "nickname = " + nickname);
if (!nickNameList.contains(nickname)) {
nickNameList.add(nickname);
performClick(nodeInfo);
count++;
if (count >= GROUP_COUNT || nickNameList.size() >= listview.get(0).getCollectionInfo().getRowCount() - sortItems.size() - 2) {
List<AccessibilityNodeInfo> createButtons = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(selectUI_create_button_id);
if (!createButtons.isEmpty()) {
performClick(createButtons.get(0));
}
if(nickNameList.size() >= listview.get(0).getCollectionInfo().getRowCount() - sortItems.size() - 2){
hasComplete = true;
}
return;
}
}
}
listview.get(0).performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 模擬獲取被刪好友列表步驟
*/
private void getDeleteFriend() {
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
return;
}
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(chattingUI_message_id);
for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
if (nodeInfo != null && nodeInfo.getText() != null && nodeInfo.getText().toString().contains("你無法邀請未添加你為好友的用戶進去群聊,請先向")) {
String str = nodeInfo.getText().toString();
str = str.replace("你無法邀請未添加你為好友的用戶進去群聊,請先向", "");
str = str.replace("發送朋友驗證申請。對方通過驗證后,才能加入群聊。", "");
String[] arr = str.split("、");
deleteList.addAll(Arrays.asList(arr));
Preferences.saveDeleteFriends(this);
Log.e(TAG, "deleteList.size():" + deleteList.size());
Toast.makeText(this, "僵尸粉數量:" + deleteList.size(), Toast.LENGTH_SHORT).show();
break;
}
}
PerformClickUtils.findTextAndClick(this,"聊天信息");
}
/**
* 退出群組步驟
*/
private void deleteGroup(){
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
return;
}
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(groupinfoUI_listview_id);
if (!nodeInfoList.isEmpty()) {
nodeInfoList.get(0).performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
nodeInfoList.get(0).performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
PerformClickUtils.findTextAndClick(this,"刪除并退出");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
PerformClickUtils.findTextAndClick(this,"刪除并退出");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// if(Utils.getVersion(this).equals(WECHAT_VERSION_27)){
// PerformClickUtils.findTextAndClick(this,"確定");
// }else{
PerformClickUtils.findTextAndClick(this,"離開群聊");
PerformClickUtils.findTextAndClick(this,"確定");
// }
}
}
ui automator viewer的使用
uiautomatorviewer可以檢查當前手機的布局結構,如果想更精確的找到控件位置,uiautomatorviewer必不可少!
使用方法:
- 搭建Android開發環境,并設置環境變量,這里就不說了。
- 在Android Studio 中打開 terminal 窗口,或者在終端直接執行命令
$uiautomatorviewer
整體效果圖:
Paste_Image.png
項目源碼github地址:https://github.com/wlj32011/InspectWechatFriend
如果此文章幫助到了你,就打賞一下吧~~~
聲明:此項目請不要用于商業用途,若有侵權,均與本人無關~
本文已授權微信公眾號:鴻洋(hongyangAndroid)在微信公眾號平臺原創首發。