妙用 AccessibilityService 黑科技实现微信自动加好友拉人进群聊
| TYPE_VIEW_FOCUSED | View 获得焦点 || TYPE_VIEW_TEXT_CHANGED | View 文本变化 || TYPE_WINDOW_STATE_CHANGED | 打开了一个 PopupWindow,Menu 或 Dialog || TYPE_NOTIFICATION_STATE_CHANGED | Notification 变化 || TYPE_VIEW_HOVER_ENTER | 一个 View 进入悬停 || TYPE_VIEW_HOVER_EXIT | 一个 View 退出悬停 || TYPE_TOUCH_EXPLORATION_GESTURE_START | 触摸浏览事件开始 || TYPE_TOUCH_EXPLORATION_GESTURE_END | 触摸浏览事件完成 || TYPE_WINDOW_CONTENT_CHANGED | 窗口的内容发生变化,或子树根布局发生变化 || TYPE_VIEW_SCROLLED | View 滚动 || TYPE_VIEW_TEXT_SELECTION_CHANGED | Edittext 文字选中发生改变事件 || TYPE_ANNOUNCEMENT | 应用产生一个通知事件 || TYPE_VIEW_ACCESSIBILITY_FOCUSED | 获得无障碍焦点事件 || TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED | 无障碍焦点事件清除 || TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY | 在给定的移动粒度下遍历视图文本的事件 || TYPE_GESTURE_DETECTION_START | 开始手势监测 || TYPE_GESTURE_DETECTION_END | 结束手势监测 || TYPE_TOUCH_INTERACTION_START | 触摸屏幕事件开始 || TYPE_TOUCH_INTERACTION_END | 触摸屏幕事件结束 || TYPE_WINDOWS_CHANGED | 屏幕上的窗口变化事件,需要 API 21+ || TYPE_VIEW_CONTEXT_CLICKED | View 中的上下文点击事件 || TYPE_ASSIST_READING_CONTEXT | 辅助用户读取当前屏幕事件 |
好吧,上面的表其实并没什么大用,我还是习惯直接把 event.toString()给打印出来, 然后自行去判断~
如图就可以拿到 event 类型,以及产生对应事件的类名,核心是这两个, 除此之外还有 Text 和 ContentDescription 等。
比如我那个监听 Notification 跳转到添加好友页的:
这里就是对事件类型做了下判断,然后获取 contentIntent,跳转而已。 简单点讲就是:
你在这个方法里,去判断一波事件类型和 className, 然后再获取控件,做一些点击,滚动,填充文本等。
2.服务的配置
自定义完这个服务要想让他启用你还得执行下面的操作:
Step 1:在 res 文件夹下创建 xml 文件夹,新建一个配置的 xml 文件(名字自己定)
<?xml version="1.0" encoding="utf-8"?><accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged"android:accessibilityFeedbackType="feedbackGeneric"android:accessibilityFlags="flagDefault"android:canRetrieveWindowContent="true"android:notificationTimeout="100"android:packageNames="com.tencent.mm"android:settingsActivity="com.coderpig.wechathelper.MainActivity" />
属性简介如下:
accessibilityEventTypes:设置监听的事件种类,用|隔开,监听所有可以用 typeAllMask;
accessibilityFeedbackType:服务提供的反馈类型,feedbackGeneric 通用反馈;
accessibilityFlags:辅助功能附加的标志,flagDefault 默认的配置
canRetrieveWindowContent:辅助功能服务是否能够取回活动窗口内容的属性
notificationTimeout:响应时间
packageNames:监听的应用包名,不填,默认监听所有应用的事件
settingsActivity:允许用户修改辅助功能的 activity 类名
Step 2:接着 AndroidManifest.xml 文件中对该 Service 进行配置
先是添加一个权限:
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
接着是 Service 的配置:
这里是你那个配置文件 xml 文件的文件名,其他照抄。
Step 3:安装到手机后,需要在手机设置的无障碍处开启服务
一般在设置的辅助功能处能找到:
如果 Logcat 那里能看到打印的 LOG,说明服务正常运行,接下来要找控件节点
3.找控件
这里可以用到神器 UI Automator 来查看布局层次,打开 Android Studio, Ctrl + alt + A,输入 monitor
依次点击:选中设备 -> Dump View Hierarchy for UI Automator
稍等一会,右侧就会出现当前页面的布局层次图,如图随手选中一个邀请的节点:
右侧可以拿到对应的信息,一般比较常用的是这几个,有一点要注意!!! resource-id 不一定是唯一的
获得控件基本都会通过下述这个方法:
getRootInActiveWindow
( ):获取当前整个活动窗口的根节点 返回的是一个**AccessibilityNodeInfo
**类,代表 View 的状态信息, 提供了下述几个非常实用的方法:
getParent:获取父节点。
getChild:获取子节点。
performAction:在节点上执行一个动作。
findAccessibilityNodeInfosByText:通过字符串查找节点元素。
findAccessibilityNodeInfosByViewId:通过视图 id 查找节点元素。
后面的这两个方法会返回一个 AccessibilityNodeInfo 列表,一般操作是 遍历,然后筛选特定节点,比如我程序里的,获得底部 Tab 节点为"通讯录", 然后点击,跳转后遍历,筛选"群聊"的节点,点击。
另外,UI Automator 有时并不可靠(实时问题),我建议写多一个遍历节点 的方法,可以更清楚里面的控件情况:
拿到控件,接着就到触发事件了。
4.触发事件
通过调用**performAction
**()传入一个时间类型即可触发相应时间,比如点击,长按等 事件就多了,自己点开 AccessibilityNodeInfo 类查看吧,这里介绍下最常用的几个事件:
//点击 performAction(AccessibilityNodeInfo.ACTION_CLICK);
//长按 performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
//滚动 performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); //向下滚一下 performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); //向上滚一下
//填充 EditText(API 版本需要>18 可用方法 1,API>21 两种方法都可以使用)
//方法 1:ClipboardManager clipboard = (ClipboardManager)this.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("text", "填充内容");
clipboard.setPrimaryClip(clip);
//获得焦点 info.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
////粘贴进入内容 info.performAction(AccessibilityNodeInfo.ACTION_PASTE);
//方法 2:Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_
CHARSEQUENCE, "填充内容");
info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
除了控件触发事件外,AccessibilityService 提供了一个**performGlobalAction
(),用于执行 一些通用的事件**:
GLOBAL_ACTION_BACK 点击返回按钮 GLOBAL_ACTION_HOME 点击 homeGLOBAL_ACTION_NOTIFICATIONS 打开通知 GLOBAL_ACTION_RECENTS 打开最近应用 GLOBAL_ACTION_QUICK_SETTINGS 打开快速设置 GLOBAL_ACTION_POWER_DIALOG 打开长按电源键的弹框
另外在实际开发中,直接调用这些全局方法又是并没有生效, 我在调 GLOBAL_ACTION_BACK 的时候就发现有时不会回退, 个人的解决方案是使用**handler.postDelay()**延时执行:
除了这样玩以外,我还利用时间差,串行去执行几个任务,比如:
上面的步骤是:
进入群聊聊天信息页后,列表滚动两次,接着依次:
1.延时 1s 后,找到添加成员按钮并点击;
2.延时 2.3s 后,把名字填充到 EditText 里
3.延时 3s 后,点击确定按钮
就不用过于依赖 onAccessibilityEvent 方法,除了用 handler.postDelay 外, 还可以用 Thread.sleep(休眠时长),用到的点大概就这么多,其余的自行探究吧。
小结
本节讲解一波如何通过 AccessibilityService 来实现自动加好友以及拉人进群, 之前是打算用 xposed 来写的,后面发现没我想像中简单,而且很多用安卓机的都 不会搞机(基),root 也不会,后来还是选择了 AccessibilityService,简单易用, 当然后面还是会研究一波 xposed 实现的,敬请期待~ 对了,还有,之前那个网页端的机器人被封原因估计是信息秒回,如果有还用 itchat 那个做机器人的,建议回复的时间可以稍微延长些;
关于 AccessibilityService 更多内容可见:
Android 辅助功能:[blog.csdn.net/qq_24800377…](
)
Building Accessibility Services:[developer.android.com/guide/topic…](
)
Developing an Accessibility Service:[developer.android.com/training/ac…](
)
附:关键代码(都可以在:[github.com/coder-pig/W…](
) 找到): 代码有 Bug 的话正常,后续会优化下逻辑,感觉写得有点杂~
package com.coderpig.wechathelper;
import android.accessibilityservice.AccessibilityService;import android.app.Notification;import android.app.PendingIntent;import android.os.Bundle;import android.os.Handler;import android.util.Log;import android.view.accessibility.AccessibilityEvent;import android.view.accessibility.AccessibilityNodeInfo;
import java.util.List;
/**
描述:微信监控服务类
@author CoderPig on 2018/04/04 13:46.*/
public class HelperService extends AccessibilityService {
private static final String TAG = "HelperService";private Handler handler = new Handler();private String userName = "123";
@Overridepublic void onAccessibilityEvent(AccessibilityEvent event) {int eventType = event.getEventType();CharSequence classNameChr = event.getClassName();String className = classNameChr.toString();Log.d(TAG, event.toString());switch (eventType) {case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) {Notification notification = (Notification) event.getParcelableData();String content = notification.tickerText.toString();if (content.contains("请求添加你为朋友")) {PendingIntent pendingIntent = notification.contentIntent;try {pendingIntent.send();} catch (PendingIntent.CanceledException e) {e.printStackTrace();}}}break;case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:switch (className) {case "com.tencent.mm.plugin.subapp.ui.friend.FMessageConversationUI":addFriend();break;case "com.tencent.mm.plugin.profile.ui.SayHiWithSnsPermissionUI":verifyFriend();break;case "com.tencent.mm.plugin.profile.ui.ContactInfoUI":performBackClick();break;case "com.tencent.mm.ui.LauncherUI":if (!userName.equals("123")) {openGroup();}break;case "com.tencent.mm.ui.contact.ChatroomContactUI":if (!userName.equals("123")) {inviteGroup();}break;case "com.tencent.mm.ui.chatting.ChattingUI":if (!userName.equals("123")) {openGroupSetting();}break;case "com.tencent.mm.plugin.chatroom.ui.ChatroomInfoUI":if (userName.equals("123")) {performBackClick();} else {addToGroup();}break;case "com.tencent.mm.ui.base.i":dialogClick();break;}break;case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
}}
private void addFriend() {AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();if (nodeInfo != null) {List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("接受");if (list != null && list.size() > 0) {for (AccessibilityNodeInfo n : list) {n.performAction(AccessibilityNodeInfo.ACTION_CLICK);}} else {performBackClick();}}}
private void verifyFriend() {AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();//获得用户名 if (nodeInfo != null) {userName = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/d0n").get(0).getText().toString();AccessibilityNodeInfo finishNode = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/hd").get(0);finishNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);}}
private void openGroup() {AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();if (nodeInfo != null) {List<AccessibilityNodeInfo> nodes = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/ca5");for (AccessibilityNodeInfo info : nodes) {
评论