最近研究了自動化操作的相關事宜,輔助服務就是其中一項技術。下面介紹一下相關方面技術。這項技術可以用作搶紅包、App自動安裝解除安裝、頁面內容抓取,WX訊息的自動傳送、自動傳送朋友圈,H5頁面內容抓取也可以。
原理
對於那些由於視力、聽力或其它身體原因導致不能方便使用Android智慧手機的使用者,Android提供了Accessibility功能和服務輔助這些使用者更加簡單地操作裝置,包括文字轉語音(不支援中文)、觸覺反饋、手勢操作、軌跡球和手柄操作。開發者可以搭建自己的Accessibility服務,這可以加強可用性,例如聲音提示,物理反饋,和其他可選的操作模式。
AccessibilityService是一個執行在後臺的服務,有相關回撥方法onAccessibilityEvent(AccessibilityEvent event)能夠收到由系統發出的一些事件。介面中產生的任何變化都會產生一個事件,並由系統通知給AccessibilityService這就像監視器監視著介面的一舉一動,一旦介面發生變化,立刻進行相關操作。
實現
Api 4就已經加入了這項技術,但Android 4.1算是一個分水嶺,此版本以後,系統輔助服務增加了與視窗元素的雙向互動,可以通過輔助功能服務操作視窗元素,比如點選按鈕等
AccessibilityService 實現
import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;
public class AutoAccessibilityService extends AccessibilityService {
@Override protected void onServiceConnected() {
super.onServiceConnected();
//當我們當服務被系統服務連結成功後,這裡可以做一些初始化當工作
} @Override public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
int eventType = accessibilityEvent.getEventType();
//事件監聽回撥,開發者可以做很多事情 switch (eventType) {
//當視窗的狀態發生改變時 case AccessibilityEvent.TYPE_WINDOWS_CHANGED://視窗變化 break;
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED://對話方塊、popupwindow、選單 break;
case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED://通知 break;
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED://基於當前event.source中子view的內容變化 break;
..... .....
}
} @Override public void onInterrupt() {
//這個在系統想要中斷AccessibilityService返給的響應時會呼叫。在整個生命週期裡會被呼叫多次。
} @Override public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
//在系統將要關閉Service時被呼叫。在這個方法主要做釋放資源的工作。
}
}複製程式碼
AndroidMenifest.xml
<
service android:name=".AutoAccessibilityService" android:enabled="true" android:exported="true" android:canRetrieveWindowContent="true" 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>
複製程式碼
res/xml/accessibility_service_config
<
?xml version="1.0" encoding="utf-8"?>
<
accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged|typeWindowsChanged" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagDefault|flagRequestEnhancedWebAccessibility" android:canRetrieveWindowContent="true" android:description="@string/app_name" android:notificationTimeout="100" android:packageNames="com.weibo"//這是我們要監聽的包名 android:canRequestEnhancedWebAccessibility="true" />
複製程式碼
服務狀態監控回撥
added API level 14
AccessibilityManager accessibilityManager = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
accessibilityManager.addAccessibilityStateChangeListener(new AccessibilityManager.AccessibilityStateChangeListener() {
@Override public void onAccessibilityStateChanged(boolean isChange) {
Log.i(TAG,"onAccessibilityStateChanged isChange = " + isChange);
if(isChange){
//do something
}else{
//do something
}
}
});
複製程式碼
以上就是最基本的實現,但是這個輔助服務需要使用者手動開啟授權才能用,當然某些黑科技下也是可以不用手動開啟的,比如,電腦通過adb命令來控制已經root的手機。呵呵~
舉例
搶紅包的外掛、微信訊息的自動傳送、自動傳送朋友圈網上很多了,就不說了。下面說一下像H5頁面的內容怎麼抓取,直接上程式碼:
1、遍歷當前頁面每個節點
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
//獲取當前頁面的跟頁面的節點for (int i = 0;
i <
accessibilityNodeInfo.getChildCount();
i++) {
AccessibilityNodeInfo child = accessibilityNodeInfo.getChild(i);
findEveryViewNode(child);
}複製程式碼
2、找出每個節點的子節點的資訊
public void findEveryViewNode(AccessibilityNodeInfo node) {
if (null != node &
&
node.getChildCount() >
0) {
for (int i = 0;
i <
node.getChildCount();
i++) {
AccessibilityNodeInfo child = node.getChild(i);
if (child == null) {
continue;
} Log.i(TAG, "節點資料 ======= " + ",text = " + child.getText() + ", descript = " + child.getContentDescription() + ", className = " + child.getClassName() + ", resId = " + child.getViewIdResourceName());
// 遞迴呼叫 findEveryViewNode(child);
}
}
}複製程式碼
這裡需要注意的是,某些情況下,返回的節點資訊會是null。
有了神操作,在自動化的路上,無往不利。
方法舉例
/** * 模擬點選事件 */public void performViewClick(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null) {
return;
} while (nodeInfo != null) {
if (nodeInfo.isClickable()) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
break;
} nodeInfo = nodeInfo.getParent();
}
}/** * 模擬返回操作 */public void performBackClick() {
performGlobalAction(GLOBAL_ACTION_BACK);
}/** * 模擬Home操作 */public void performHomeClick() {
performGlobalAction(GLOBAL_ACTION_HOME);
}/** * 模擬下滑操作 */public void performScrollBackward() {
performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
}/** * 模擬上滑操作 */public void performScrollForward() {
performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
}複製程式碼
方法還有很多這裡就不一一列舉。
原始碼分析
以點選事件為例,AccessibilityService是如何做到監控捕捉使用者行為的
1、AccessibilityEvent產生:
View#performClick() ->
View#sendAccessibilityEventUncheckedInternal() ->
ViewGroup#requestSendAccessibilityEvent() ->
ViewRootImpl#requestSendAccessibilityEvent() ->
AccessibilityManager#sendAccessibilityEvent(event) ->
AccessibilityManagerService#sendAccessibilityEvent() ->
AccessibilityManagerService#notifyAccessibilityServicesDelayedLocked() ->
Service#notifyAccessibilityEvent(event)複製程式碼
2、AccessibilityEvent處理:
AccessibilityEvent ->
Binder ->
IAccessibilityServiceClientWrapper#onAccessibilityEvent(AccessibilityEvent) ->
HandlerCaller#sendMessage(message);
// message中包括AccessibilityEvent ->
IAccessibilityServiceClientWrapper#executeMessage() ->
Callbacks#onAccessibilityEvent(event) ->
AccessibilityService.this.onAccessibilityEvent(event)複製程式碼
總結
在研究這些技術時,深感系統的良心大作,用來給使用手機困難的人使用,同時也提供了使用手機方便的人更加方便。
開發人員的困難點就是對目標程式的研究,分析出各個頁面以及節點的資訊,並對目的操作流程細化抽象,最終完成目標。
工具推薦:UI Automator Viewer分析頁面元素