https://developer.android.google.cn/guide/topics/ui/accessibility/services.html 无障碍服务,可以监听界面的操作,比如:点击、拖动、界面更新等信息。更为强大的是可以获取屏幕信息,同时具备普通Service的能力。(在别人手机中植入一个无障碍服务并开启,可以监听他的手机操作和屏幕信息,eg:获取微信、QQ当前聊天文字并上传) 因为无障碍服务相比一般Service过于强大,安装后还需要在设置->辅助功能中手动开启。
https://developer.android.google.cn/reference/android/accessibilityservice/AccessibilityService.html
AccessibilityService继承自普通的Service,因而具备普通Service的生存周期,同时具有自己的一些生命周期。
函数名 | 描述 |
---|---|
onServiceConnected() | (可选)当系统成功连接到该AccessibilityService时,将调用此方法。主要用与一次性配置或调整的代码。 |
onAccessibilityEvent() | (必要)当系统监测到相匹配的AccessibilityEvent事件时,将调用此方法,在整个Service的生命周期中,该方法将被多次调用。 |
onInterrupt() | (必要)系统需要中断AccessibilityService反馈时,将调用此方法。AccessibilityService反馈包括服务发起的震动、音频等行为。 |
onUnbind() | (可选)系统要关闭该服务是,将调用此方法。主要用来释放资源。 |
和普通Service一样,AccessibilityService同样需要在Manifest.xml中注册。
<service android:name=".MyAccessibilityService" android:label="@string/accessibility_service_label" 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/accessibility_service_config" /></service>android.permission.BIND_ACCESSIBILITY_SERVICE权限和action是必须的。 同时AccessibilityService需要提供设置列表(meta-data),该设置也可以在运行时通过AccessibilityService.setServiceInfo (AccessibilityServiceInfo info)进行动态设置。不过该方式并不能设置所有的参数,因而推荐的方法为(meta-data)。 android:resource中的地址为:/res/xml/accessibility_service_config.xml。
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/accessibility_service_description" android:packageNames="com.example.android.apis" android:accessibilityEventTypes="typeAllMask" android:accessibilityFlags="flagDefault" android:accessibilityFeedbackType="feedbackSpoken" android:notificationTimeout="100" android:canRetrieveWindowContent="true" android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity"/>各项参数参考AccessibilityServiceInfo。
https://developer.android.google.cn/reference/android/accessibilityservice/AccessibilityServiceInfo.html AccessibilityService的配置类,可使用setService进行动态设置,同时和上述的accessibility_service_config.xml相对应。 以下参数可以使用“|”表示使用多个选项。
AccessibilityService服务响应的事件类型,只有声明了的类型,系统才会调用该服务的onAccessibilityEvent。
常量 | 描述 |
---|---|
typeViewClicked | 点击事件 |
typeViewSelected | view被选择 |
typeViewScrolled | 滑动事件 |
typeWindowContentChanged | 窗口内容该表 |
typeAllMask | 所有事件 |
完整列表如下: typeViewClicked typeViewLongClicked typeViewSelected typeViewFocused typeViewTextChanged typeWindowStateChanged typeNotificationStateChanged typeViewHoverEnter typeViewHoverExit typeTouchExplorationGestureStart typeTouchExplorationGestureEnd typeWindowContentChanged typeViewScrolled typeViewTextSelectionChanged typeAnnouncement typeViewAccessibilityFocused typeViewAccessibilityFocusCleared typeViewTextTraversedAtMovementGranularity typeGestureDetectionStart typeGestureDetectionEnd typeTouchInteractionStart typeTouchInteractionEnd typeWindowsChanged typeContextClicked typeAssistReadingContext typeAllMask
AccessibilityService服务的反馈类型。
常量 | 描述 |
---|---|
feedbackSpoken | 语音反馈 |
feedbackHaptic | 触觉(震动)反馈 |
feedbackAudible | 音频反馈 |
feedbackVisual | 视频反馈 |
feedbackGeneric | 通用反馈 |
feedbackAllMask | 以上都具有 |
一些格外的参数。
常量 | 描述 |
---|---|
flagDefault | 默认 |
flagIncludeNotImportantViews | |
flagRequestTouchExplorationMode | |
flagRequestEnhancedWebAccessibility | |
flagReportViewIds | 允许获得view id,需要获取viewid的时候需要该参数,开始没声明导致nodeInfo. getViewIdResourceName()返回的为null |
flagRequestFilterKeyEvents | |
flagRetrieveInteractiveWindows | 允许获得windows,使用getWindows时需要该参数,否则会返回空列表 |
参数名 | 描述 |
---|---|
android:canRetrieveWindowContent | 设置为“true”表示允许获取屏幕信息,使用getWindows、getRootInActiveWindow等函数时需要为“true” |
android:packageNames | 服务响应的事件来源,若设置,则服务只能获取该package发出的事件,不设置可获得所有的事件源 |
android:notificationTimeout | 同一种事件类型触发的最短时间间隔(毫秒) |
android:description | 服务和行为的简短描述 |
… | … |
https://developer.android.google.cn/reference/android/view/accessibility/AccessibilityEvent.html 常用实例化途径:在MyAccessibilityService 中 onAccessibilityEvent(AccessibilityEvent event) 获得该实例,表示监听事件触发。
返回值 | 方法 | 描述 |
---|---|---|
int | getEventType() | 获取事件类型(点击等) |
CharSequence | getPackageName() | 时间来源包名 |
String | toString() | 打印事件 |
https://developer.android.google.cn/reference/android/view/accessibility/AccessibilityNodeInfo.html 常用实例化途径:AccessibilityService.getRootInActiveWindow()获得(当前活动窗口的根节点)。AccessibilityWindowInfo类也能获取该window下的node。 该类与下图有对应关系(通过uiautomatorviewer工具获得): 常用方法:
返回值 | 方法 | 描述 |
---|---|---|
List | findAccessibilityNodeInfosByText(String text) | 通过text寻找子节点 |
List | findAccessibilityNodeInfosByViewId(String viewId) | 通过id查找子节点 |
CharSequence | getPackageName() | Gets the package this node comes from. |
AccessibilityNodeInfo | getParent() | Gets the parent. |
AccessibilityNodeInfo | getChild(int index) | Get the child at given index. |
int | getChildCount() | Gets the number of children. |
CharSequence | getText() | Gets the text of this node. |
String | getViewIdResourceName() | Gets the fully qualified resource name of the source view’s id. |
AccessibilityWindowInfo | getWindow() | Gets the window to which this node belongs. |
boolean | isChecked() | Gets whether this node is checked. |
String | toString() | Returns a string representation of the object. |
比如: 遍历打印当前布局信息可以使用一下代码(布局节点树)
@Override public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) { if(accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED){ AccessibilityNodeInfo nodeInfo = getRootInActiveWindow(); dfsnode(nodeInfo,0); } } public void dfsnode(AccessibilityNodeInfo node , int num){ StringBuilder stringBuilder = new StringBuilder(); for(int i = 0 ;i < num ; i++){ stringBuilder.append("__ "); //父子节点之间的缩进 } Log.i("####",stringBuilder.toString() + node.toString()); //打印 for(int i = 0 ; i < node.getChildCount() ; i++){ //遍历子节点 dfsnode(node.getChild(i),num+1); } }https://developer.android.google.cn/reference/android/view/accessibility/AccessibilityWindowInfo.html 常用实例化途径:AccessibilityService.getWindows()获得,返回值是一个list列表。
返回值 | 方法 | 描述 |
---|---|---|
AccessibilityNodeInfo | getRoot() | 获得该窗口的根节点信息 |
AccessibilityWindowInfo | getChild(int index) | Gets the child window at a given index. |
int | getChildCount() | Gets the number of child windows. |
int | getId() | Gets the unique window id. |
boolean | isActive() | Gets if this window is active. |
boolean | isFocused() | Gets if this window has input focus. |
代码来自涅槃1992:http://www.jianshu.com/p/4cd8c109cdfb 配置代码:
<?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" />Service代码:
public class RobService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { int eventType = event.getEventType(); switch (eventType) { case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED: handleNotification(event); break; case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: String className = event.getClassName().toString(); if (className.equals("com.tencent.mm.ui.LauncherUI")) { getPacket(); } else if (className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI")) { openPacket(); } else if (className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI")) { close(); } break; } } /** * 处理通知栏信息 * * 如果是微信红包的提示信息,则模拟点击 * * @param event */ private void handleNotification(AccessibilityEvent event) { List<CharSequence> texts = event.getText(); if (!texts.isEmpty()) { for (CharSequence text : texts) { String content = text.toString(); //如果微信红包的提示信息,则模拟点击进入相应的聊天窗口 if (content.contains("[微信红包]")) { if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) { Notification notification = (Notification) event.getParcelableData(); PendingIntent pendingIntent = notification.contentIntent; try { pendingIntent.send(); } catch (PendingIntent.CanceledException e) { e.printStackTrace(); } } } } } } /** * 关闭红包详情界面,实现自动返回聊天窗口 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) private void close() { AccessibilityNodeInfo nodeInfo = getRootInActiveWindow(); if (nodeInfo != null) { //为了演示,直接查看了关闭按钮的id List<AccessibilityNodeInfo> infos = nodeInfo.findAccessibilityNodeInfosByViewId("@id/ez"); nodeInfo.recycle(); for (AccessibilityNodeInfo item : infos) { item.performAction(AccessibilityNodeInfo.ACTION_CLICK); } } } /** * 模拟点击,拆开红包 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) private void openPacket() { AccessibilityNodeInfo nodeInfo = getRootInActiveWindow(); if (nodeInfo != null) { //为了演示,直接查看了红包控件的id List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId("@id/b9m"); nodeInfo.recycle(); for (AccessibilityNodeInfo item : list) { item.performAction(AccessibilityNodeInfo.ACTION_CLICK); } } } /** * 模拟点击,打开抢红包界面 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void getPacket() { AccessibilityNodeInfo rootNode = getRootInActiveWindow(); AccessibilityNodeInfo node = recycle(rootNode); node.performAction(AccessibilityNodeInfo.ACTION_CLICK); AccessibilityNodeInfo parent = node.getParent(); while (parent != null) { if (parent.isClickable()) { parent.performAction(AccessibilityNodeInfo.ACTION_CLICK); break; } parent = parent.getParent(); } } /** * 递归查找当前聊天窗口中的红包信息 * * 聊天窗口中的红包都存在"领取红包"一词,因此可根据该词查找红包 * * @param node */ public AccessibilityNodeInfo recycle(AccessibilityNodeInfo node) { if (node.getChildCount() == 0) { if (node.getText() != null) { if ("领取红包".equals(node.getText().toString())) { return node; } } } else { for (int i = 0; i < node.getChildCount(); i++) { if (node.getChild(i) != null) { recycle(node.getChild(i)); } } } return node; } @Override public void onInterrupt() { } @Override protected void onServiceConnected() { super.onServiceConnected(); }}新闻热点
疑难解答