Google为了让Android系统更实用,为用户提供了无障碍辅助服务(AccessibilityService
).
AccessibilityService
运行在后台,并且能够收到由系统发出的一些事件(AccessibilityEvent
,这些事件表示用户界面一系列的状态变化),比如焦点改变,输入内容变化,按钮被点击了等等,该种服务能够请求获取当前活动窗口并查找其中的内容.换言之,界面中产生的任何变化都会产生一个时间,并由系统通知给AccessibilityService
.这就像监视器监视着界面的一举一动,一旦界面发生变化,立刻发出警报.
A项目-模拟程序
模拟一个按钮点击
findViewById(R.id.btn_click).settext("模拟点击");
findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//Toast.makeText(MainActivity.this, "我被点击了!!!", Toast.LENGTH_SHORT).show();
Log.i(TAG, "onClick: 我被点击了!!!");
}
});
B项目-监听程序
public class ListeningService extends AccessibilityService { private static final String TAG = "WindowChange"; @Override protected void onServiceConnected() { super.onServiceConnected(); AccessibilityServiceInfo config = new AccessibilityServiceInfo(); //配置监听的事件类型为界面变化|点击事件 config.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_VIEW_CLICKED; config.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; if (Build.VERSION.SDK_INT >= 16) { config.flags = AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; } setServiceInfo(config); } @Override public void onAccessibilityEvent(AccessibilityEvent event) { AccessibilityNodeInfo nodeInfo = event.getSource();//当前界面的可访问节点信息 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {//界面变化事件 ComponentName componentName = new ComponentName(event.getPackageName().toString(), event.getClassName().toString()); ActivityInfo activityInfo = tryGetActivity(componentName); boolean isActivity = activityInfo != null; if (isActivity) { Log.i(TAG, componentName.flattenToShortString()); //格式为:(包名/.+当前Activity所在包的类名) //如果是模拟程序的操作界面 if (componentName.flattenToShortString().equals("com.botaoweb.blackdoplanet/.viewController.home.HM1100SignActivity")) { //当前是模拟程序的主页面,则模拟点击按钮 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { //通过id寻找控件,id格式为:(包名:id/+制定控件的id) //一般除非第三方应该是自己的,否则我们很难通过这种方式找到控件 //Listlist = nodeInfo.findAccessibilityNodeInfosByViewId("com.demon.simulationclick:id/btn_click"); //通过控件的text寻找控件 List list = nodeInfo.findAccessibilityNodeInfosByText("模拟点击"); if (list != null && list.size() > 0) { //模拟第三方点击事件 list.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK); } } } } } //监听第三方点击事件 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) { //View点击事件 Toast.makeText(this, nodeInfo.getText()+"被点击了", Toast.LENGTH_SHORT).show(); Log.i(TAG, "onAccessibilityEvent: " + nodeInfo.getText()); if ((nodeInfo.getText() + "").equals("模拟点击")) { Toast.makeText(this, "这是来自监听Service的响应!", Toast.LENGTH_SHORT).show(); Log.i(TAG, "onAccessibilityEvent: 这是来自监听Service的响应!"); } } } private ActivityInfo tryGetActivity(ComponentName componentName) { try { return getPackageManager().getActivityInfo(componentName, 0); } catch (PackageManager.NameNotFoundException e) { return null; } } @Override public void onInterrupt() { } }
配置Service
AndroidManifest.xml
···
android:label="@string/app_name"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
android:resource="@xml/accessibilityservice" />
···
在res/xml下新建一个accessibilityservice.xml文件,在该文件里进行配置,通过这种方式可以配置accessibilityservice的所有配置。
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagIncludeNotImportantViews"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_service_description"
tools:ignore="UnusedAttribute" />
方法 说明
android:accessibilityEventTypes 设置响应事件的类型typeAllMask,typeViewClicked,typeViewFocused,typeNotificationStateChanged,typeWindowStateChanged等
android:accessibilityFeedbackType 设置反馈给用户的方式
android:canRetrieveWindowContent 是否检索当前窗口的内容,即是否可以获取当前窗口的View
android:description 服务申请的权限描述说明
在MainActivity中
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private Intent intent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!isAccessibilitySettingsOn(MainActivity.this, ListeningService.class.getCanonicalName())) {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
} else {
intent = new Intent(MainActivity.this, ListeningService.class);
startService(intent);
}
}
});
findViewById(R.id.stop).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (intent != null) {
stopService(intent);
}
}
});
}
/**
* 检测辅助功能是否开启
*
* @param mContext
* @return boolean
*/
private boolean isAccessibilitySettingsOn(Context mContext, String serviceName) {
int accessibilityEnabled = 0;
// 对应的服务
final String service = getPackageName() + "/" + serviceName;
//Log.i(TAG, "service:" + service);
try {
accessibilityEnabled = Settings.Secure.getInt(mContext.getApplicationContext().getContentResolver(),
android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
Log.v(TAG, "accessibilityEnabled = " + accessibilityEnabled);
} catch (Settings.SettingNotFoundException e) {
Log.e(TAG, "Error finding setting, default accessibility to not found: " + e.getMessage());
}
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
if (accessibilityEnabled == 1) {
Log.v(TAG, "***ACCESSIBILITY IS ENABLED*** -----------------");
String settingValue = Settings.Secure.getString(mContext.getApplicationContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();
Log.v(TAG, "-------------- > accessibilityService :: " + accessibilityService + " " + service);
if (accessibilityService.equalsIgnoreCase(service)) {
Log.v(TAG, "We've found the correct setting - accessibility is switched on!");
return true;
}
}
}
} else {
Log.v(TAG, "***ACCESSIBILITY IS DISABLED***");
}
return false;
}
}