前言
最近在研究Android輔助服務(wù),實現(xiàn)了這個小工具调炬,也算是對最近學(xué)習(xí)的一個總結(jié)。
原理
通過Android 無障礙輔助功能實現(xiàn)模擬點擊控件來實現(xiàn)
檢查被刪好友有兩種方法:
- 向好友發(fā)送一條消息,如果對方已經(jīng)把你刪除贝润,則消息發(fā)送失敗。
- 建群法:新建一個不大于40人的群铝宵,如果其中有好友已經(jīng)把你刪除打掘,微信會有條消息提示
- 整體執(zhí)行步驟:啟動微信->點擊+號->發(fā)起群聊->選擇35個聯(lián)系人->點擊確定->點擊群里詳情->刪除并退出,依次輪詢執(zhí)行,知道所有好友輪詢結(jié)束尊蚁。
本文采用建群的方式進(jìn)行檢查唯绍。
本人微信有300好友,全部檢測一遍只需3分鐘即可枝誊,親測已經(jīng)成功况芒,
但是建群沒有超過40人 會有個別好友會受到打擾消息,可能是微信哪里的bug叶撒,具體原因未知绝骚。
說明和app預(yù)覽
此軟件通過無障礙輔助進(jìn)行模擬點擊,無任何外掛木馬祠够,無封號風(fēng)險
使用方法
- Android 手機一部压汪,登錄微信賬號
- 安裝輔助軟件apk下載地址請點擊這里
- 打開輔助軟件-點擊打開輔助功能按鈕,跳轉(zhuǎn)到無障礙輔助設(shè)置把輔助開關(guān)打開古瓤。
- 點擊開始檢查按鈕止剖,開始一系列的模擬點擊,檢查完成后會跳轉(zhuǎn)到一個列表會把被刪好友列表展示出來落君。
實現(xiàn)步驟:
- 新建Android Studio 工程穿香,新建一個Services類集成AccessibilityService,實現(xiàn)對應(yīng)方法,詳細(xì)介紹見代碼注釋
/**
* Created by wanglj on 16/10/20.
*/
public class InspectWechatFriendService extends AccessibilityService{
@Override
protected void onServiceConnected() {//輔助服務(wù)被打開后 執(zhí)行此方法
super.onServiceConnected();
}
@Override
public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {//監(jiān)聽手機當(dāng)前窗口狀態(tài)改變 比如 Activity 跳轉(zhuǎn),內(nèi)容變化,按鈕點擊等事件
}
@Override
public void onInterrupt() {//輔助服務(wù)被關(guān)閉 執(zhí)行此方法
}
}
- 在manifests.xml文件中注冊此服務(wù):
<?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"
/>
- 實現(xiàn)對某個控件的點擊
通過getRootInActiveWindow方法獲取當(dāng)前窗口信息绎速,通過findAccessibilityNodeInfosByText方法找到當(dāng)前對應(yīng)控件進(jìn)行模擬點擊
public class PerformClickUtils {
/**
* 在當(dāng)前頁面查找文字內(nèi)容并點擊
*
* @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進(jìn)行點擊
*
* @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;
}
}
}
}
/**
* 在當(dāng)前頁面查找對話框文字內(nèi)容并點擊
*
* @param text1 默認(rèn)點擊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);
}
}
}
- 監(jiān)聽窗口事件
獲取當(dāng)前窗口的classname 通過classname進(jìn)行判斷當(dāng)前手機處于某個界面
下面代碼邏輯:
- 如果當(dāng)前為微信主頁面皮获,則點擊+號然后點擊發(fā)起群聊
- 如果當(dāng)前頁面為創(chuàng)建群聊選擇聯(lián)系人界面,則開啟一個while循環(huán)模擬滾動時間以及點擊選擇框纹冤,當(dāng)選擇用戶到39人時洒宝,則模擬點擊確定按鈕發(fā)起群聊。
- 發(fā)起群聊后萌京,微信會返回哪些用戶不是你的好友雁歌,這個時候,取到當(dāng)前控件的字符串并截取用戶列表保存到本地知残。
- 獲取到不是好友的用戶后靠瞎,點擊右上角進(jìn)入群聊詳情,點擊刪除并退出
- 退出后又回到微信主頁面橡庞,依次執(zhí)行1 2 3 4步驟较坛,直到滾動到聯(lián)系人最底部為止。
- 當(dāng)所有用戶執(zhí)行完成后扒最,則啟動檢查結(jié)果界面丑勤,列出所有被刪好友。
下面為對應(yīng)邏輯代碼:
@Override
public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {//監(jiān)聽手機當(dāng)前窗口狀態(tài)改變 比如 Activity 跳轉(zhuǎn),內(nèi)容變化,按鈕點擊等事件
//如果手機當(dāng)前界面的窗口發(fā)送變化
if (accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
//獲取當(dāng)前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,"發(fā)起群聊");
}
}else{
nickNameList.clear();
deleteList.clear();
sortItems.clear();
startActivity(new Intent(this, DeleteFriendListActivity.class));
}
}
}
/**
* 模擬創(chuàng)建群組步驟
*/
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("你無法邀請未添加你為好友的用戶進(jìn)去群聊吧趣,請先向")) {
String str = nodeInfo.getText().toString();
str = str.replace("你無法邀請未添加你為好友的用戶進(jìn)去群聊法竞,請先向", "");
str = str.replace("發(fā)送朋友驗證申請耙厚。對方通過驗證后,才能加入群聊岔霸。", "");
String[] arr = str.split("薛躬、");
deleteList.addAll(Arrays.asList(arr));
Preferences.saveDeleteFriends(this);
Log.e(TAG, "deleteList.size():" + deleteList.size());
Toast.makeText(this, "僵尸粉數(shù)量:" + 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可以檢查當(dāng)前手機的布局結(jié)構(gòu),如果想更精確的找到控件位置呆细,uiautomatorviewer必不可少型宝!
使用方法:
- 搭建Android開發(fā)環(huán)境,并設(shè)置環(huán)境變量絮爷,這里就不說了趴酣。
- 在Android Studio 中打開 terminal 窗口,或者在終端直接執(zhí)行命令
$uiautomatorviewer
整體效果圖:
項目源碼github地址:https://github.com/wlj32011/InspectWechatFriend
如果此文章幫助到了你,就打賞一下吧~~~
聲明:此項目請不要用于商業(yè)用途坑夯,若有侵權(quán)岖寞,均與本人無關(guān)~
本文已授權(quán)微信公眾號:鴻洋(hongyangAndroid)在微信公眾號平臺原創(chuàng)首發(fā)。