https://github.com/zhaokidd/TanTanAutoSwipe
核心類: AccessibiliiiiiiiityService
簡單的說,AccessibilityService 為了方便殘障人士實現操作,可以監控app的運行,在界面上實現一些自動操作.
而 AccessibilityService 的原理簡單的來說就是: 安卓中 View 組件會主動發出各種狀態改變的廣播,Service收到后可以判斷狀態也可以獲取發出廣播的組件的view和view的子組件.
而實現一個 AccessibilityerService 需要三步
- 在xml文件中進行配置
- 在 AndroidManifest 文件中進行配置
- 實現AccessibilityService,監控app界面中的操作.
配置 xml 文件
在 res -> xml -> 路徑下新建一個 autoservice_config 文件
<?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=""
android:canRetrieveWindowContent="true"
android:description="@string/app_name"
android:notificationTimeout="100">
</accessibility-service>
這個配置文件中有兩個選項比較重要:
android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged"
eventTypes 這個選項決定了你寫的Service能收到什么類型的事件。
android:canRetrieveWindowContent 這個選項決定了能否通過 service提供的方法獲取到app中的view.
在AndroidManifest文件中配置 AccessibilityService
<service
android:name=".service.MyAutoService"
android:enabled="true"
android:label="@string/app_label"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:process="@string/isolated_process">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/autoservice_config" />
</service>
接著在manifest中配置 AccessibilityService,這里按配置規則來就可以了.
實現自己的自定義 AccessibilityService
接著就是最重要的部分,我們要根據要監控的app實現自己的自定義AccessibilityService , 要做的并不復雜,實現要清楚幾個概念
AccessibilityNodeInfo
界面中所有的view元素都會作為這么一個類出現.
AccessibilityEvent
Event事件,當app進出前后臺或者有點擊滑動等事件的時候,都會發出這么一個event
onAccessibilityEvent()
接收 event的回調方法,我們就在這個回調中實現自己的邏輯.
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
String packageName = event.getPackageName().toString();
if (PACKAGE_NAME_TANTAN.equals(packageName)) {
Log.d(TAG, event.getPackageName().toString() + event.getEventType());
switch (event.getEventType()) {
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
AccessibilityNodeInfo mNodeInfo = getRootInActiveWindow();
Rect rect = new Rect();
mNodeInfo.getBoundsInScreen(rect);
boolean isPerformed = mNodeInfo.performAction(GESTURE_SWIPE_RIGHT);
accessibilityNodeInfo = mNodeInfo;
traversalNodeInfo(mNodeInfo);
performLoveButtonClick(mLoveButton);
// performSwipeRight(mNodeInfo);
Log.d(TAG, "child counts is :" + mNodeInfo.getChildCount() + "\nrect is :" + rect.toString() + "\n performed is :" + isPerformed);
break;
}
}
}
private void performLoveButtonClick(final AccessibilityNodeInfo mLoveButton) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
boolean isClicked = mLoveButton.performAction(AccessibilityNodeInfo.ACTION_CLICK);
Log.d(TAG, "love Button is clicked = " + isClicked);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
private void traversalNodeInfo(AccessibilityNodeInfo mNodeInfo) {
int mChildCount = mNodeInfo.getChildCount();
Log.d(TAG, "class name:" + mNodeInfo.getClassName().toString() + " child count is :" + mChildCount + "\n");
mLoveButton = mNodeInfo;
if (mChildCount > 0) {
for (int i = 0; i < mChildCount; i++) {
traversalNodeInfo(mNodeInfo.getChild(i));
}
}
}
思路很簡單:在 onAccessibilityEvent() 方法中收到 event后,判斷事件類型,如果要監控的app出現在前臺(接收到TYPE_WINDOW_STATE_CHANGED事件),此時遍歷所有view找到探探軟件中的喜歡按鈕,然后模擬點擊事件就OK了。
如何找到指定的view呢?
之前的話,可以通過 android自帶的 uiautomatorviewer 這個工具來獲取到界面上的控件的id,然后通過 findAccessibilityNodeInfosByViewId() 這個方法來獲取到制定的view,但是現在的很多app已經把id隱藏掉了,所以這個方法行不通了.
現在除了遍歷所有的view也沒有更好的解決辦法了.
好在探探里的這個喜歡按鈕在view樹的最后一個節點,我們只要前序遍歷這棵view樹,保留最后一個view就能獲取的這個button。
private void traversalNodeInfo(AccessibilityNodeInfo mNodeInfo) {
int mChildCount = mNodeInfo.getChildCount();
Log.d(TAG, "class name:" + mNodeInfo.getClassName().toString() + " child count is :" + mChildCount + "\n");
mLoveButton = mNodeInfo;
if (mChildCount > 0) {
for (int i = 0; i < mChildCount; i++) {
traversalNodeInfo(mNodeInfo.getChild(i));
}
}
}
boolean isClicked = mLoveButton.performAction(AccessibilityNodeInfo.ACTION_CLICK);
最后一步開啟一個工作線程,定時點擊button就ok了.
需要注意的問題
所有的自動操作都需要app保持在與用戶交互的狀態下才能進行,目前沒有找到在后臺也能讓監控服務正常運行的方法.