一、內容簡介
AccesslibityService輔助功能服務目的是幫助那些具有視覺、身體或年齡相關限制的用戶而設計的,主要功能是控制屏幕視圖的響應,可以模擬點擊,後退,滾動等事件,可用於自動化處理。因此可用來開發自動搶紅包等功能,驚奇死我了,開篇第一彈就讓我大有收穫,迫不及待的分享給我的猿友們。
- 官方API:AccessibilityService
二、代碼示例(通過自動搶紅包來講解)
(1)註冊方式
(a) 代碼註冊,繼承AccessibilityService,重寫onServiceConnected()方法,如下:
@Override
protected void onServiceConnected() {
AccessibilityServiceInfo info = getServiceInfo();
info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
info.feedbackType = AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
info.notificationTimeout = 1000;
info.packageNames = packageNames;
setServiceInfo(info);
super.onServiceConnected();
}
(b) Android4.0以後,可以通過meta-data標籤引用xml註冊
- (i) 新建accessibility.xml,在res/xml/accessibility.xml,如下:
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackAllMask"
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows
|flagReportViewIds|flagRequestFilterKeyEvents"
android:canRequestEnhancedWebAccessibility="true"
android:canRequestTouchExplorationMode="true"
android:canRetrieveWindowContent="true"
android:description="@string/lbl_auto_open_red_packet_desc"
android:notificationTimeout="1000" />
(ii) 在AndroidManifest.xml中註冊service,如下
<service
android:name=".service.AutoOpenRedPacketService"
android:label="@string/lbl_auto_open_red_packet"
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>
(2)功能代碼(搶紅包功能使用的是方式註冊)
(a) 簡單的封裝了一下,AccessibilityService提供的一些Action操作,定義到接口IAccessbilityAction中,如下:
package com.ronindong.meet.android.dao;
import android.view.accessibility.AccessibilityNodeInfo;
public interface IAccessbilityAction {
/**
* 模擬後退事件
*/
void performBack();
/**
* 模擬向上滾動
*/
void performScrollUp();
/**
* 模擬向下滾動
*/
void performScrollDown();
/**
* 模擬view點擊
*
* @param nodeInfo
*/
void performViewClick(AccessibilityNodeInfo nodeInfo);
/**
* 根據文本獲取view控件
*
* @param text
* @return
*/
AccessibilityNodeInfo findViewByText(String text);
/**
* 根據文本獲取view控件
*
* @param text
* @param clickable
* @return
*/
AccessibilityNodeInfo findViewByText(String text, boolean clickable);
/**
* 點擊指定text的view
*
* @param text
*/
void clickTextViewByText(String text);
/**
* 點擊指定viewId的view
*
* @param viewId
*/
void clickTextViewByViewId(String viewId);
/**
* 模擬文本框輸入
*
* @param info
* @param text
*/
void performInputText(AccessibilityNodeInfo info, String text);
/**
*
* @param serviceName
* @return
*/
boolean checkAccessbilityEnabled(String serviceName);
}
(b) 編寫基類 BaseAccessibilityService,代碼如下:
package com.ronindong.meet.android.service;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import com.ronindong.meet.android.dao.IAccessbilityAction;
import java.util.List;
/**
* @author donghailong
*/
public abstract class BaseAccessibilityService extends AccessibilityService
implements IAccessbilityAction {
private static final String TAG = BaseAccessibilityService.class.getSimpleName();
/**
*
*/
private AccessibilityManager mManager;
public BaseAccessibilityService() {
}
@Override
public void performBack() {
performGlobalAction(GLOBAL_ACTION_BACK);
}
@Override
public void performScrollUp() {
performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
}
@Override
public void performScrollDown() {
performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
}
@Override
public void performViewClick(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null) {
return;
}
while (nodeInfo != null) {
if (nodeInfo.isClickable()) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
break;
}
nodeInfo = nodeInfo.getParent();
}
}
@Override
public AccessibilityNodeInfo findViewByText(String text) {
return findViewByText(text, false);
}
@Override
public AccessibilityNodeInfo findViewByText(String text, boolean clickable) {
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
return null;
}
List<AccessibilityNodeInfo> nodeInfoList =
accessibilityNodeInfo.findAccessibilityNodeInfosByText(text);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
if (nodeInfo != null && (nodeInfo.isClickable() == clickable)) {
return nodeInfo;
}
}
}
return null;
}
@Override
public void clickTextViewByText(String text) {
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
Log.i(TAG, "accessibilityNodeInfo is null");
return;
}
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo
.findAccessibilityNodeInfosByText(text);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
if (nodeInfo != null) {
performViewClick(nodeInfo);
break;
}
}
}
}
@Override
public void clickTextViewByViewId(String viewId) {
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
Log.i(TAG, "accessibilityNodeInfo is null");
return;
}
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo
.findAccessibilityNodeInfosByViewId(viewId);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
if (nodeInfo != null) {
performViewClick(nodeInfo);
break;
}
}
}
}
@Override
public void performInputText(AccessibilityNodeInfo info, String text) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("label", text);
clipboard.setPrimaryClip(clip);
info.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
info.performAction(AccessibilityNodeInfo.ACTION_PASTE);
}
}
@Override
public boolean checkAccessbilityEnabled(String serviceName) {
List<AccessibilityServiceInfo> accessibilityServices =
mManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);
for (AccessibilityServiceInfo info : accessibilityServices) {
if (info.getId().equals(serviceName)) {
return true;
}
}
return false;
}
}
(c) 搶紅包功能實現類AutoOpenRedPacketService,代碼如下:
package com.ronindong.meet.android.service;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
/**
* @author donghailong
*/
public class AutoOpenRedPacketService extends BaseAccessibilityService {
private static final String TAG = AutoOpenRedPacketService.class.getSimpleName();
/**
* wx包名
*/
public static final String WX_PACKAGE_NAME = "com.tencent.mm";
/**
* text
*/
public static final String WX_RED_PACKAGE_TEXT = "微信紅包";
/**
* 開 對應的viewId(隨着微信的更新,可能會變)
*/
public static final String WX_OPEN_RED_PACKAGE_VIEW_ID = "com.tencent.mm:id/c31";
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.i(TAG, "event type:" + event.getEventType());
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
&& event.getPackageName().equals(WX_PACKAGE_NAME)) {
clickTextViewByText(WX_RED_PACKAGE_TEXT);
clickTextViewByViewId(WX_OPEN_RED_PACKAGE_VIEW_ID);
}
}
@Override
public void onInterrupt() {
}
}
(d) 由於AccessibilityService是系統級別的服務,需要用戶主動打開。代碼如下:
package com.ronindong.meet.android.helper;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
/**
* @author donghailong
*/
public class CxHelper {
private Context context;
public void init(Context cx) {
if (null != cx) {
this.context = cx.getApplicationContext();
}
}
/**
* 打開輔助功能設置
*/
public void openAccessSetting() {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
(e)最後一步,只需要在MainActivity中,請求權限即可,如下:
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.openAccess:
SingletonManager.get(CxHelper.class).openAccessSetting();
break;
default:
break;
}
}
大功告成!目前只是實現了簡單的搶紅包功能,還有需要待完善的地方。
注:以上代碼親測可用,若有問題,歡迎隨時聯繫我。
三、用途展望
- 可實現自動安裝APP;模擬各種事件,如點擊,滾動等
- 自動搶紅包;
- 自動打電話功能
- 解放用戶,實現一些自動化操作
- 你的想象不應該被限制
四、爬坑實錄
- AccessbiliryService輔助服務,屬於系統級別的服務,需要用戶主動開啓.
- 繼承AccessbiliryService,需要實現的邏輯代碼,不可在外部調用,否則沒有效果;
- AccessbiliryService的子類服務,和普通service不同,不需要主動啓動。用戶主動開啓權限後,服務就運行了。
- 輔助服務不容易捕捉沒有text或沒有設置Id的控件;還有EditText和ImageView也不容易獲取。
Github地址:MeetAndroidDemo