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>