AccesslibityService - 掃地僧Service

一、內容簡介

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


參考文章:
AccessibilityService從入門到出軌

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章