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:descr各項參數參考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(); }}新聞熱點
疑難解答