阅读这篇文章前,你需要先了解AccessibilityService,可以先阅读我上一篇文章 微信自动回复和自动抢红包实现原理(一):AccessibilityService的介绍和配置 已经了解的朋友可以直接阅读该文章
完成AccessibilityService的配置后,好像无从下手。先别急,先打印一些log看看吧。把下面的方法放在onAccessibilityEvent()里:
private void printEventLog(AccessibilityEvent event) { Log.i(TAG, "-------------------------------------------------------------"); int eventType = event.getEventType(); //事件类型 Log.i(TAG, "PackageName:" + event.getPackageName() + ""); // 响应事件的包名 Log.i(TAG, "Source Class:" + event.getClassName() + ""); // 事件源的类名 Log.i(TAG, "Description:" + event.getContentDescription()+ ""); // 事件源描述 Log.i(TAG, "Event Type(int):" + eventType + ""); switch (eventType) { case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:// 通知栏事件 Log.i(TAG, "event type:TYPE_NOTIFICATION_STATE_CHANGED"); break; case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED://窗体状态改变 Log.i(TAG, "event type:TYPE_WINDOW_STATE_CHANGED"); break; case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED://View获取到焦点 Log.i(TAG, "event type:TYPE_VIEW_ACCESSIBILITY_FOCUSED"); break; case AccessibilityEvent.TYPE_GESTURE_DETECTION_START: Log.i(TAG, "event type:TYPE_VIEW_ACCESSIBILITY_FOCUSED"); break; case AccessibilityEvent.TYPE_GESTURE_DETECTION_END: Log.i(TAG, "event type:TYPE_GESTURE_DETECTION_END"); break; case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: Log.i(TAG, "event type:TYPE_WINDOW_CONTENT_CHANGED"); break; case AccessibilityEvent.TYPE_VIEW_CLICKED: Log.i(TAG, "event type:TYPE_VIEW_CLICKED"); break; case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: Log.i(TAG, "event type:TYPE_VIEW_TEXT_CHANGED"); break; case AccessibilityEvent.TYPE_VIEW_SCROLLED: Log.i(TAG, "event type:TYPE_VIEW_SCROLLED"); break; case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: Log.i(TAG, "event type:TYPE_VIEW_TEXT_SELECTION_CHANGED"); break; default: Log.i(TAG, "no listen event"); } for (CharSequence txt : event.getText()) { Log.i(TAG, "text:" + txt); } Log.i(TAG, "-------------------------------------------------------------"); }向安装了服务的手机发微信信息,查看打印的log:
非锁屏(在后台): ------------------------------------------------------------- packageName:com.tencent.mm source:null source class:android.app.Notification event type(int):64 event type:TYPE_NOTIFICATION_STATE_CHANGED text:[联系人]: 哦 ------------------------------------------------------------- 非锁屏(在前台主界面): ------------------------------------------------------------- packageName:com.tencent.mm source:android.view.accessibility.AccessibilityNodeInfo@8009b539; boundsInParent: Rect(0, 0 - 38, 38); boundsInScreen: Rect(103, 1181 - 141, 1219); packageName: com.tencent.mm; className: android.widget.TextView; text: 1; error: null; maxTextLength: -1; contentDescription: null; viewIdResName: null; checkable: false; checked: false; focusable: false; focused: false; selected: false; clickable: false; longClickable: false; enabled: true; password: false; scrollable: false; actions: [AccessibilityAction: ACTION_SELECT - null, AccessibilityAction: ACTION_CLEAR_SELECTION - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_NEXT_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_SET_SELECTION - null] source class:android.widget.TextView event type(int):2048 event type:TYPE_WINDOW_CONTENT_CHANGED ------------------------------------------------------------- ------------------------------------------------------------- packageName:com.tencent.mm source:null source class:android.app.Notification event type(int):64 event type:TYPE_NOTIFICATION_STATE_CHANGED text:[联系人]: 呵呵 ------------------------------------------------------------- ------------------------------------------------------------- packageName:com.tencent.mm source:android.view.accessibility.AccessibilityNodeInfo@80043582; boundsInParent: Rect(0, 0 - 38, 38); boundsInScreen: Rect(96, 153 - 134, 191); packageName: com.tencent.mm; className: android.widget.TextView; text: 1; error: null; maxTextLength: -1; contentDescription: null; viewIdResName: null; checkable: false; checked: false; focusable: false; focused: false; selected: false; clickable: false; longClickable: false; enabled: true; password: false; scrollable: false; actions: [AccessibilityAction: ACTION_SELECT - null, AccessibilityAction: ACTION_CLEAR_SELECTION - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_NEXT_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_SET_SELECTION - null] source class:android.widget.TextView event type(int):2048 event type:TYPE_WINDOW_CONTENT_CHANGED ------------------------------------------------------------- 非锁屏(在前台打开会话人的界面)(没Notification) ------------------------------------------------------------- packageName:com.tencent.mm source:android.view.accessibility.AccessibilityNodeInfo@8009cbbf; boundsInParent: Rect(0, 0 - 720, 1038); boundsInScreen: Rect(0, 146 - 720, 1184); packageName: com.tencent.mm; className: android.widget.ListView; text: null; error: null; maxTextLength: -1; contentDescription: null; viewIdResName: null; checkable: false; checked: false; focusable: true; focused: false; selected: false; clickable: true; longClickable: true; enabled: true; password: false; scrollable: true; actions: [AccessibilityAction: ACTION_FOCUS - null, AccessibilityAction: ACTION_SELECT - null, AccessibilityAction: ACTION_CLEAR_SELECTION - null, AccessibilityAction: ACTION_CLICK - null, AccessibilityAction: ACTION_LONG_CLICK - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_SCROLL_BACKWARD - null] source class:android.widget.ListView event type(int):2048 event type:TYPE_WINDOW_CONTENT_CHANGED ------------------------------------------------------------- ------------------------------------------------------------- packageName:com.tencent.mm source:android.view.accessibility.AccessibilityNodeInfo@801086ca; boundsInParent: Rect(0, 0 - 415, 80); boundsInScreen: Rect(201, 146 - 616, 150); packageName: com.tencent.mm; className: android.widget.TextView; text: 我在敲代码,稍后回复哈~; error: null; maxTextLength: -1; contentDescription: null; viewIdResName: null; checkable: false; checked: false; focusable: false; focused: false; selected: false; clickable: true; longClickable: true; enabled: true; password: false; scrollable: false; actions: [AccessibilityAction: ACTION_SELECT - null, AccessibilityAction: ACTION_CLEAR_SELECTION - null, AccessibilityAction: ACTION_CLICK - null, AccessibilityAction: ACTION_LONG_CLICK - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_NEXT_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_SET_SELECTION - null] source class:android.widget.TextView event type(int):2048 event type:TYPE_WINDOW_CONTENT_CHANGED ------------------------------------------------------------- ------------------------------------------------------------- packageName:com.tencent.mm source:android.view.accessibility.AccessibilityNodeInfo@8009cbbf; boundsInParent: Rect(0, 0 - 720, 1038); boundsInScreen: Rect(0, 146 - 720, 1184); packageName: com.tencent.mm; className: android.widget.ListView; text: null; error: null; maxTextLength: -1; contentDescription: null; viewIdResName: null; checkable: false; checked: false; focusable: true; focused: false; selected: false; clickable: true; longClickable: true; enabled: true; password: false; scrollable: true; actions: [AccessibilityAction: ACTION_FOCUS - null, AccessibilityAction: ACTION_SELECT - null, AccessibilityAction: ACTION_CLEAR_SELECTION - null, AccessibilityAction: ACTION_CLICK - null, AccessibilityAction: ACTION_LONG_CLICK - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_SCROLL_BACKWARD - null] source class:android.widget.ListView event type(int):4096 event type:TYPE_VIEW_SCROLLED ------------------------------------------------------------- 非锁屏(在前台打开非会话人的界面): ------------------------------------------------------------- packageName:com.tencent.mm source:android.view.accessibility.AccessibilityNodeInfo@800ce011; boundsInParent: Rect(0, 0 - 95, 80); boundsInScreen: Rect(104, 851 - 199, 931); packageName: com.tencent.mm; className: android.widget.TextView; text: [白眼]; error: null; maxTextLength: -1; contentDescription: null; viewIdResName: null; checkable: false; checked: false; focusable: false; focused: false; selected: false; clickable: true; longClickable: true; enabled: true; password: false; scrollable: false; actions: [AccessibilityAction: ACTION_SELECT - null, AccessibilityAction: ACTION_CLEAR_SELECTION - null, AccessibilityAction: ACTION_CLICK - null, AccessibilityAction: ACTION_LONG_CLICK - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_NEXT_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_SET_SELECTION - null] source class:android.widget.TextView event type(int):2048 event type:TYPE_WINDOW_CONTENT_CHANGED ------------------------------------------------------------- ------------------------------------------------------------- packageName:com.tencent.mm source:null source class:android.app.Notification event type(int):64 event type:TYPE_NOTIFICATION_STATE_CHANGED text:[联系人]: 呵呵 ------------------------------------------------------------- ------------------------------------------------------------- packageName:com.tencent.mm source:android.view.accessibility.AccessibilityNodeInfo@8012f5f0; boundsInParent: Rect(0, 0 - 371, 60); boundsInScreen: Rect(174, 591 - 545, 651); packageName: com.tencent.mm; className: android.widget.TextView; text: "?彣????" 撤回了一条消息; error: null; maxTextLength: -1; contentDescription: null; viewIdResName: null; checkable: false; checked: false; focusable: false; focused: false; selected: false; clickable: true; longClickable: false; enabled: true; password: false; scrollable: false; actions: [AccessibilityAction: ACTION_SELECT - null, AccessibilityAction: ACTION_CLEAR_SELECTION - null, AccessibilityAction: ACTION_CLICK - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_NEXT_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_SET_SELECTION - null] source class:android.widget.TextView event type(int):2048 event type:TYPE_WINDOW_CONTENT_CHANGED -------------------------------------------------------------从打印的log和我们平时使用微信应该就知道的了,除了在打开了会话人的聊天界面,否则都会有Notification的,可以以此作为切入点,那么接下来我们的工作就简单了。步骤如下: 1. 监听TYPE_NOTIFICATION_STATE_CHANGED事件 2. 根据Notification打开会话人聊天界面 3. 搜索输入框控件 4. 在输入框输入回复文本 5. 点击发送按钮 6. 返回微信主界面
思路很清晰了,难点是如何找到相应的控件。放心,Android也为我们提供了一个类来帮助我们——AccessibilityNodeInfo ,其包含一些控件的信息,可用其找到相应的控件,并做出相应的操作。常用方法:
- CharSequence getClassName () // 获取控件类名,如按钮会返回android.widget.Button - CharSequence getText () // 获取控件的文本,如微信的发送按钮会返回“发送” - String getViewIdResourceName () // 获取控件的id代码注释很详细了,就不一一解释。源码后面有贴。
/** * 自动回复服务 */ public class AutoReplyService extends AccessibilityService{ private static final String TAG = AutoReplyService.class.getSimpleName(); private Handler handler = new Handler(); private boolean hasNotify = false; /** * 必须重写的方法,响应各种事件。 */ @Override public void onAccessibilityEvent(final AccessibilityEvent event) { int eventType = event.getEventType(); // 事件类型 switch (eventType) { case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED: // 通知栏事件 Log.i(TAG, "TYPE_NOTIFICATION_STATE_CHANGED"); if(PhoneController.isLockScreen(this)) { // 锁屏 PhoneController.wakeAndUnlockScreen(this); // 唤醒点亮屏幕 } openAppByNotification(event); hasNotify = true; break; default: Log.i(TAG, "DEFAULT"); if (hasNotify) { // 如果有通知 try { Thread.sleep(1000); // 停1秒, 否则在微信主界面没进入聊天界面就执行了fillInputBar } catch (InterruptedException e) { e.printStackTrace(); } if (fillInputBar("我在敲代码,稍后回复哈~")) { // 找到输入框,即EditText findAndPerformAction(UI.BUTTON, "发送"); // 点击发送 handler.postDelayed(new Runnable() { // 返回主界面,这里延迟执行,为了有更好的交互 @Override public void run() { performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); // 返回 } }, 1500); } hasNotify = false; } break; } } @Override public void onInterrupt() { } /** * 查找UI控件并点击 * @param widget 控件完整名称, 如android.widget.Button, android.widget.TextView * @param text 控件文本 */ private void findAndPerformAction(String widget, String text) { // 取得当前激活窗体的根节点 if (getRootInActiveWindow() == null) { return; } // 通过文本找到当前的节点 List<AccessibilityNodeInfo> nodes = getRootInActiveWindow().findAccessibilityNodeInfosByText(text); if(nodes != null) { for (AccessibilityNodeInfo node : nodes) { if (node.getClassName().equals(widget) && node.isEnabled()) { node.performAction(AccessibilityNodeInfo.ACTION_CLICK); // 执行点击 break; } } } } /** * 打开微信 * @param event 事件 */ private void openAppByNotification(AccessibilityEvent event) { if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) { Notification notification = (Notification) event.getParcelableData(); try { PendingIntent pendingIntent = notification.contentIntent; pendingIntent.send(); } catch (PendingIntent.CanceledException e) { e.printStackTrace(); } } } /** * 填充输入框 */ private boolean fillInputBar(String reply) { AccessibilityNodeInfo rootNode = getRootInActiveWindow(); if (rootNode != null) { return findInputBar(rootNode, reply); } return false; } /** * 查找EditText控件 * @param rootNode 根结点 * @param reply 回复内容 * @return 找到返回true, 否则返回false */ private boolean findInputBar(AccessibilityNodeInfo rootNode, String reply) { int count = rootNode.getChildCount(); for (int i = 0; i < count; i++) { AccessibilityNodeInfo node = rootNode.getChild(i); if (UI.EDITTEXT.equals(node.getClassName())) { // 找到输入框并输入文本 setText(node, reply); return true; } if (findInputBar(node, reply)) { // 递归查找 return true; } } return false; } /** * 设置文本 */ private void setText(AccessibilityNodeInfo node, String reply) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Bundle args = new Bundle(); args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, reply); node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args); } else { ClipData data = ClipData.newPlainText("reply", reply); ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); clipboardManager.setPrimaryClip(data); node.performAction(AccessibilityNodeInfo.ACTION_FOCUS); // 获取焦点 node.performAction(AccessibilityNodeInfo.ACTION_PASTE); // 执行粘贴 } } }这里我没有处理打开了会话人聊天界面的情况,我觉得你都打开了会话人的聊天界面,就证明你想与他聊天,也就不需要自动回复了。当然,如果你遍要(不带你这样敷衍朋友的),只要加点简单逻辑就可以实现了~到这里微信自动回复功能就完成了,怎样,是不是很简单!感兴趣的朋友可以继续看我下一篇文章: 微信自动回复和自动抢红包实现原理(三):自动抢红包
源码下载