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.
使用這個類可以開發(fā)用于給用戶提供替換或者是增強反饋的輔助功能服務(wù)。


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在后臺運行并接收系統(tǒng)AccessibilityEvents事件的回調(diào)雳灾,當用戶界面的狀態(tài)發(fā)生改變時會觸發(fā)AccessibilityEvents事件炒嘲,例如焦點的變化浑劳,點擊一個按鈕。
這個服務(wù)可以獲取到活動窗口的內(nèi)容,開發(fā)一個輔助功能服務(wù)需要繼承AccessibilityService并實現(xiàn)其中的抽象方法躲雅。


An AccessibilityServiceInfo describes an AccessibilityServiceInfo. The system notifies an AccessibilityService for AccessibilityEvents according to the information encapsulated in this class.
一個AccessibilityService有一個用于描述AccessibilityService的AccessibilityServiceInfo對象慰于,系統(tǒng)會通知AccessibilityService根據(jù)AccessibilityServiceInfo把信息裝進AccessibilityEvents中绵脯。

繼承AccessibilityService并實現(xiàn)其中的抽象方法。

下面是我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;
    /**
     * 頁面變化回調(diào)事件
     * @param event event.getEventType() 當前事件的類型;
     *              event.getClassName() 當前類的名稱;
     *              event.getSource() 當前頁面中的節(jié)點信息畜挨;
     *              event.getPackageName() 事件源所在的包名
     */
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        // 事件頁面節(jié)點信息不為空
        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) {
        // 事件頁面節(jié)點信息不為空
        if (event.getSource() != null) {
            // 根據(jù)Text搜索所有符合條件的節(jié)點, 模糊搜索方式; 還可以通過ID來精確搜索findAccessibilityNodeInfosByViewId
            List<AccessibilityNodeInfo> stop_nodes = event.getSource().findAccessibilityNodeInfosByText(text);
            // 遍歷節(jié)點
            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的反饋時調(diào)用
     */
    @Override
    public void onInterrupt() {
        Log.d("test=====", "Interrupt");
    }
}

AccessibilityService里幾個重要的方法:

  • onServiceConnected() - 可選修己。系統(tǒng)會在成功連接上你的服務(wù)的時候調(diào)用這個方法恢总,在這個方法里你可以做一下初始化工作,例如設(shè)備的聲音震動管理睬愤,也可以調(diào)用setServiceInfo()進行配置AccessibilityServiceInfo片仿。
  • onAccessibilityEvent() - 必須。通過這個函數(shù)可以接收系統(tǒng)發(fā)送來的AccessibilityEvent尤辱,接收來的AccessibilityEvent是經(jīng)過過濾的砂豌,過濾是在配置工作時設(shè)置的。
  • onInterrupt() - 必須光督。這個在系統(tǒng)想要中斷AccessibilityService返給的響應(yīng)時會調(diào)用阳距。在整個生命周期里會被調(diào)用多次。
  • onUnbind() - 可選可帽。在系統(tǒng)將要關(guān)閉這個AccessibilityService會被調(diào)用娄涩。在這個方法中進行一些釋放資源的工作。

之后在AndroidManifest文件里注冊并添加相應(yīng)的權(quán)限:

<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對象蓄拣,通過這個對象設(shè)置監(jiān)聽系統(tǒng)事件類型,服務(wù)的反饋類型(震動努隙,語音球恤,聲音),事件時間間隔荸镊,你想要監(jiān)聽的App的包名咽斧。最后調(diào)用setServiceInfo()進行設(shè)置堪置,如:

 @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        AccessibilityServiceInfo info = new AccessibilityServiceInfo();
        info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
        info.packageNames = PACKAGE_NAMES; 
        ...配置
        setServiceInfo(info);
    }

2.從Android4.0開始,開發(fā)者可以通過在AndroidManifest里添加<meta-data>標簽配置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" 要監(jiān)聽的包名坎匿,過濾作用
         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 :窗口狀態(tài)改變
        #TYPE_NOTIFICATION_STATE_CHANGED :通知狀態(tài)改變
        #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的開發(fā)就完成了。

其他

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
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv_start:
                if (serviceIsRunning()) {
                    showToast("服務(wù)已經(jīng)在運行雷激!");
                } 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觸發(fā)事件是異步的替蔬,要回到UI線程改變UI
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MainActivity.this, "點擊了" + text, Toast.LENGTH_LONG).show();
                Log.d("text=====", "=====" + text);
            }
        });
    }

    /**
     * 判斷自己的應(yīng)用的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;
    }

    /**
     * 前往設(shè)置界面開啟服務(wù)
     */
    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) {
                                // 隱式調(diào)用系統(tǒng)設(shè)置界面
                                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顯示信息,就不貼代碼了屎暇。

<font size=5>源代碼</font>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末承桥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子根悼,更是在濱河造成了極大的恐慌凶异,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挤巡,死亡現(xiàn)場離奇詭異唠帝,居然都是意外死亡,警方通過查閱死者的電腦和手機玄柏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贴铜,“玉大人粪摘,你說我怎么就攤上這事∩馨樱” “怎么了徘意?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長轩褐。 經(jīng)常有香客問我椎咧,道長,這世上最難降的妖魔是什么把介? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任勤讽,我火速辦了婚禮,結(jié)果婚禮上拗踢,老公的妹妹穿的比我還像新娘脚牍。我一直安慰自己,他們只是感情好巢墅,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布诸狭。 她就那樣靜靜地躺著券膀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪驯遇。 梳的紋絲不亂的頭發(fā)上芹彬,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機與錄音叉庐,去河邊找鬼舒帮。 笑死,一個胖子當著我的面吹牛眨唬,可吹牛的內(nèi)容都是我干的会前。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼匾竿,長吁一口氣:“原來是場噩夢啊……” “哼瓦宜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起岭妖,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤临庇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后昵慌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體假夺,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年斋攀,在試婚紗的時候發(fā)現(xiàn)自己被綠了已卷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡淳蔼,死狀恐怖侧蘸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鹉梨,我是刑警寧澤讳癌,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站存皂,受9級特大地震影響晌坤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜旦袋,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一骤菠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疤孕,春花似錦娩怎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爬泥。三九已至,卻和暖如春崩瓤,著一層夾襖步出監(jiān)牢的瞬間袍啡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工却桶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留境输,地道東北人。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓颖系,卻偏偏與公主長得像嗅剖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子嘁扼,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容