1. 程式人生 > >Android輔助功能AccessibilityService的使用

Android輔助功能AccessibilityService的使用

AccessibilityService官方簡介:

The classes in this package are used for development of accessibility service that provide alternative or augmented feedback to the user.
使用這個類可以開發用於給使用者提供替換或者是增強反饋的輔助功能服務。

An AccessibilityService runs in the background and receives callbacks by the system when AccessibilityEvents are fired. Such events denote some state transition in the user interface, for example, the focus has changed, a button has been clicked, etc. Such a service can optionally request the capability for querying the content of the active window. Development of an accessibility service requires extends this class and implements its abstract methods.
一個AccessibilityService在後臺執行並接收系統AccessibilityEvents事件的回撥,當用戶介面的狀態發生改變時會觸發AccessibilityEvents事件,例如焦點的變化,點選一個按鈕。這個服務可以獲取到活動視窗的內容,開發一個輔助功能服務需要繼承AccessibilityService並實現其中的抽象方法。

An AccessibilityServiceInfo describes an AccessibilityServiceInfo. The system notifies an AccessibilityService for AccessibilityEvents according to the information encapsulated in this class.
一個AccessibilityService有一個用於描述AccessibilityService的AccessibilityServiceInfo物件,系統會通知AccessibilityService根據AccessibilityServiceInfo把資訊裝進AccessibilityEvents中。

繼承AccessibilityService並實現其中的抽象方法。

下面是我Service類:

public class MyService extends AccessibilityService {
    private int code = INSTALL;
    private static final int INSTALL = 0;
    private static final int NEXT = 1;
    private static final int FINISH = 2;
    /**
     * 頁面變化回撥事件
     * @param
event event.getEventType() 當前事件的型別; * event.getClassName() 當前類的名稱; * event.getSource() 當前頁面中的節點資訊; * event.getPackageName() 事件源所在的包名 */
@Override public void onAccessibilityEvent(AccessibilityEvent event) { // 事件頁面節點資訊不為空 if (event.getSource() != null) { // 判斷事件頁面所在的包名,這裡是自己 if (event.getPackageName().equals(getApplicationContext().getPackageName())) { switch (code) { case INSTALL: click(event, "安裝", TextView.class.getName()); Log.d("test=======", "安裝"); code = NEXT; break; case NEXT: click(event, "下一步", Button.class.getName()); Log.d("test=======", "下一步"); code = FINISH; break; case FINISH: click(event, "完成", TextView.class.getName()); Log.d("test=======", "完成"); code = INSTALL; break; default: break; } } } else { Log.d("test=====", "the source = null"); } } /** * 模擬點選 * @param event 事件 * @param text 按鈕文字 * @param widgetType 按鈕型別,如android.widget.Button,android.widget.TextView */ private void click(AccessibilityEvent event, String text, String widgetType) { // 事件頁面節點資訊不為空 if (event.getSource() != null) { // 根據Text搜尋所有符合條件的節點, 模糊搜尋方式; 還可以通過ID來精確搜尋findAccessibilityNodeInfosByViewId List<AccessibilityNodeInfo> stop_nodes = event.getSource().findAccessibilityNodeInfosByText(text); // 遍歷節點 if (stop_nodes != null && !stop_nodes.isEmpty()) { AccessibilityNodeInfo node; for (int i = 0; i < stop_nodes.size(); i++) { node = stop_nodes.get(i); // 判斷按鈕型別 if (node.getClassName().equals(widgetType)) { // 可用則模擬點選 if (node.isEnabled()) { node.performAction(AccessibilityNodeInfo.ACTION_CLICK); } } } } } } /** * 中斷AccessibilityService的反饋時呼叫 */ @Override public void onInterrupt() { Log.d("test=====", "Interrupt"); } }

AccessibilityService裡幾個重要的方法:

  • onServiceConnected() - 可選。系統會在成功連線上你的服務的時候呼叫這個方法,在這個方法裡你可以做一下初始化工作,例如裝置的聲音震動管理,也可以呼叫setServiceInfo()進行配置AccessibilityServiceInfo。
  • onAccessibilityEvent() - 必須。通過這個函式可以接收系統傳送來的AccessibilityEvent,接收來的AccessibilityEvent是經過過濾的,過濾是在配置工作時設定的。
  • onInterrupt() - 必須。這個在系統想要中斷AccessibilityService返給的響應時會呼叫。在整個生命週期裡會被呼叫多次。
  • onUnbind() - 可選。在系統將要關閉這個AccessibilityService會被呼叫。在這個方法中進行一些釋放資源的工作。

之後在AndroidManifest檔案裡註冊並新增相應的許可權:

<service
    android:name=".MyService"
    android:label="輔助功能"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
    </intent-filter>
    ...
</service>

配置AccessibilityService

  1. 可以在onServiceConnected()方法裡進行,建立一個AccessibilityServiceInfo物件,通過這個物件設定監聽系統事件型別,服務的反饋型別(震動,語音,聲音),事件時間間隔,你想要監聽的App的包名。最後呼叫setServiceInfo()進行設定,如:
 @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        AccessibilityServiceInfo info = new AccessibilityServiceInfo();
        info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
        info.packageNames = PACKAGE_NAMES; 
        ...配置
        setServiceInfo(info);
    }

  1. 從Android4.0開始,開發者可以通過在AndroidManifest裡新增標籤配置AccessibilityService,在標籤裡指出配置檔案的位置,如:
res/xml/accessibility_service_info.xml
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/accessibility_service_description"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:notificationTimeout="100"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="true"
    android:packageNames="top.cokernut.sample"
    android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity"  />

    <!--
         android:description="@string/accessibility_service_description" 描述
         android:accessibilityEventTypes="typeAllMask"  事件型別
         android:accessibilityFeedbackType="feedbackGeneric" 反饋型別,聲音、震動等
         android:canRetrieveWindowContent="true", 允許獲取手機頁面中的資訊
         android:packageNames="top.cokernut.sample" 要監聽的包名,過濾作用
         android:settingsActivity="packname.android.accessibility.ServiceSettingsActivity" packname寫自己App的包名
    -->

事件型別(EventType):

        #TYPES_ALL_MASK:所有型別
        #TYPE_VIEW_CLICKED :單擊
        #TYPE_VIEW_LONG_CLICKED :長按
        #TYPE_VIEW_SELECTED :選中
        #TYPE_VIEW_FOCUSED :獲取焦點
        #TYPE_VIEW_TEXT_CHANGED :文字改變
        #TYPE_WINDOW_STATE_CHANGED :視窗狀態改變
        #TYPE_NOTIFICATION_STATE_CHANGED :通知狀態改變
        #TYPE_VIEW_HOVER_ENTER
        #TYPE_VIEW_HOVER_EXIT
        #TYPE_TOUCH_EXPLORATION_GESTURE_START
        #TYPE_TOUCH_EXPLORATION_GESTURE_END
        #TYPE_WINDOW_CONTENT_CHANGED
        #TYPE_VIEW_SCROLLED
        #TYPE_VIEW_TEXT_SELECTION_CHANGED
        #TYPE_ANNOUNCEMENT
        #TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
        #TYPE_GESTURE_DETECTION_START
        #TYPE_GESTURE_DETECTION_END
        #TYPE_TOUCH_INTERACTION_START
        #TYPE_TOUCH_INTERACTION_END
        #TYPE_WINDOWS_CHANGED

然後在AndroidManifest檔案裡把配置檔案配置到AccessibilityService上:

<service
    android:name=".MyService"
    android: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_info" />
</service>

到此一個AccessibilityService的開發就完成了。

其他

MainActivity:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView mInstallTV;
    private TextView mFinishTV;
    private TextView mStartTV;
    private TextView mStopTV;

    private Button mInstallBT;
    private Button mNextBT;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mInstallTV      = (TextView) findViewById(R.id.tv_install);
        mStartTV        = (TextView) findViewById(R.id.tv_start);
        mStopTV         = (TextView) findViewById(R.id.tv_stop);
        mFinishTV       = (TextView) findViewById(R.id.tv_finish);
        mInstallBT      = (Button) findViewById(R.id.bt_install);
        mNextBT         = (Button) findViewById(R.id.bt_next);
        mInstallTV      .setOnClickListener(this);
        mFinishTV       .setOnClickListener(this);
        mInstallBT      .setOnClickListener(this);
        mNextBT         .setOnClickListener(this);
        mStartTV        .setOnClickListener(this);
        mStopTV         .setOnClickListener(this);
    }

    @Override
    protected void onRestart() {
        super.onRestart();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv_start:
                if (serviceIsRunning()) {
                    showToast("服務已經在執行!");
                } else {
                    startAccessibilityService();
                }
                break;
            case R.id.tv_stop:
                startActivity(new Intent(MainActivity.this, SecondActivity.class));
                break;
            case R.id.tv_install:
                showToast(((TextView)v).getText().toString());
                break;
            case R.id.tv_finish:
                showToast(((TextView)v).getText().toString());
                break;
            case R.id.bt_install:
                showToast(((Button)v).getText().toString());
                break;
            case R.id.bt_next:
                showToast(((Button)v).getText().toString());
                break;
            default:
                showToast("未知按鈕");
                break;
        }
    }

    private void showToast(final String text) {
        //AccessibilityService觸發事件是非同步的,要回到UI執行緒改變UI
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MainActivity.this, "點選了" + text, Toast.LENGTH_LONG).show();
                Log.d("text=====", "=====" + text);
            }
        });
    }

    /**
     * 判斷自己的應用的AccessibilityService是否在執行
     *
     * @return
     */
    private boolean serviceIsRunning() {
        ActivityManager am = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo> services = am.getRunningServices(Short.MAX_VALUE);
        for (ActivityManager.RunningServiceInfo info : services) {
            if (info.service.getClassName().equals(getPackageName() + ".MyService")) {
                return true;
            }
        }
        return false;
    }

    /**
     * 前往設定介面開啟服務
     */
    private void startAccessibilityService() {
        new AlertDialog.Builder(this)
                .setTitle("開啟輔助功能")
                .setIcon(R.mipmap.ic_launcher)
                .setMessage("使用此項功能需要您開啟輔助功能")
                .setPositiveButton("立即開啟", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                // 隱式呼叫系統設定介面
                                Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
                                startActivity(intent);
                            }
                        }).create().show();
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="top.cokernut.sample.MainActivity">

    <LinearLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center_horizontal"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin">

        <TextView
            android:id="@+id/tv_install"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="30dp"
            android:background="#00AA66"
            android:layout_margin="10dp"
            android:text="安裝TV" />

        <Button
            android:id="@+id/bt_install"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="30dp"
            android:text="安裝Btn" />

        <Button
            android:id="@+id/bt_next"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="30dp"
            android:text="下一步Btn" />

        <TextView
            android:id="@+id/tv_finish"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="30dp"
            android:background="#00AA66"
            android:layout_margin="10dp"
            android:text="完成" />

        <TextView
            android:id="@+id/tv_start"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:padding="30dp"
            android:background="#0088FF"
            android:layout_margin="10dp"
            android:text="開始" />
        <TextView
            android:id="@+id/tv_stop"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:padding="30dp"
            android:background="#0088FF"
            android:layout_margin="10dp"
            android:text="停止" />
    </LinearLayout>
</ScrollView>

SecondActivity就一個TextView顯示資訊,就不貼程式碼了。