Accessibility Service介绍与使用
Android系统提供的辅助功能,旨在帮助身体不便或操作不灵活的人辅助使用手机应用
一般在手机中 叫 无障碍模式
当然 也可以用于一些其他事情 如 自动抢红包 静默安装apk 自动点击弹框等
需要用户主动打开无障碍模式,并且打开所需的特定的无障碍服务 该服务才会生效
生命周期:
该服务由系统管理生命周期
(1)onServiceConnected()
当用户开启辅助功能会回调该函数
(2)onAccessibilityEvent()
当收到通过配置过滤的相关事件
(3)onInterrupt()
服务断开时回调
断开服务:
Android7.0以上调用 disableSelf() 或者 用户关闭了Accessibility Service
实例:点击系统弹框: 接受/拒绝 基本使用流程:
1 创建MyAccessibilityService类 继承AccessibilityService 并在Manifest中注册
<application>
<service android:name=".MyAccessibilityService"
android:label="@string/accessibility_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_config" />
</service>
</application>
除了 android:name 写自己的类名 其他照着写 不能漏了
android:label 无障碍界面你开启的服务的名称 如设置为应用名
accessibility_service_config 创建res/xml/accessibility_service_config文件 通过静态方式进行服务的配置
2 accessibility_service_config.xml
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_description"
android:packageNames="com.x.xx.xxx"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFlags="flagDefault"
android:accessibilityFeedbackType="feedbackSpoken"
android:notificationTimeout="100"
android:canRetrieveWindowContent="true"
android:description无障碍界面你设置的服务的描述字段
android:packageNames 要过滤的包名
若有多个包名 则通过,分隔
如果不过滤任何package则不设置
或者动态方式:
accessibilityServiceInfo.packageNames = null;
android:accessibilityEventTypes 要响应的事件类型 如
typeAllMask 所有类型
typeWindowStateChanged 窗口变化
typeWindowContentChanged 窗口内容变化 等等
android:accessibilityFlags 辅助功能额外信息
flagReportViewIds 可以携带view 的id信息
一般设置为flagDefault
android:accessibilityFeedbackType 操作相应事件后的反馈
feedbackSpoken 发声
feedbackVisual 视觉反馈
feedbackHaptic 震动反馈
android:notificationTimeout 两个相同事件发送的间隔(ms)
android:canRetrieveWindowContent=“true” 允许辅助功能获得窗口的节点信息 要设置为true
可以通过下面的方式进行动态设置
3 MyAccessibilityService类
(1)onServiceConnected() //当用户开启辅助功能会回调该函数
在该函数中可以通过AccessibilityServiceInfo类 动态的方式去 设置配置服务操作
如 eventTypes packageNames等 但不能动态设置canRetrieveWindowContent(不能生效)
得通过上方静态设置
(2)onAccessibilityEvent()
当收到通过配置过滤的相关事件
通过event.getEventType() 获取相应的事件类型
即 android:accessibilityEventTypes 的类型
然后进行相关操作
(3)onInterrupt()
服务断开时回调
(4) isAccessibilitySettingsOn 判断服务是否开启
private boolean isAccessibilitySettingsOn(String accessibilityServiceName, Context context) {
int accessibilityEnable = 0;
String serviceName = context.getPackageName() + "/" + accessibilityServiceName;
try {
accessibilityEnable = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, 0);
} catch (Exception e) {
Logger.t(TAG).e("get accessibility enable failed, the err:" + e.getMessage());
}
if (1 == accessibilityEnable) {
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
String settingValue = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (null != settingValue) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();
if (accessibilityService.equalsIgnoreCase(serviceName)) {
Logger.t(TAG).d("accessibility service:" + serviceName + " is on.");
return true;
}
}
}
} else {
Logger.t(TAG).d("accessibility service disable.");
}
return false;
}
(5) 服务开启
1 无root或者系统签名:
跳转到无障碍界面:
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
startActivity(intent)
注意:有的系统可能并不是该ACTION 可能会crash
2 应用具有系统签名 自动去打开无障碍服务
先申请权限:
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
因为系统的安全配置文件是在Settings.Secure中 直接去操作该文件即可
private void enableAccessibility(Context context) {
try {
Settings.Secure.putString(context.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
"com.xxx.xx你的应用包名/com.xx.xx.service.MyAccessibilityService你应用的AccessibilityService服务的完整路径");
Settings.Secure.putString(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED, "1");
Logger.t(TAG).d("enableAccessibility.");
} catch (Exception e) {
Logger.t(TAG).e("Exception:" + e.toString());
}
}
(6) onAccessibilityEvent当收取到某个事件后:
AccessibilityNodeInfo nodeInfo = findViewByText("接受", true);
if (nodeInfo != null) {
performViewClick(nodeInfo);
} else {
Logger.t(TAG).d("nodeInfo is null.");
}
通过 text去进行查找:
private AccessibilityNodeInfo findViewByText(String text, boolean clickable) {
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
Logger.t(TAG).d("accessibilityNodeInfo is 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;
}
也可以通过Id去查找
点击操作:
private void performViewClick(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null) {
return;
}
Logger.t(TAG).d("performViewClick start.");
while (nodeInfo != null) {
if (nodeInfo.isClickable()) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
Logger.t(TAG).d("has clicked the view");
nodeInfo.recycle(); // 当点击完后回收
break;
}
nodeInfo = nodeInfo.getParent();
}
}
4 关闭服务
Android7.0以上调用 disableSelf() 或者 用户关闭了Accessibility Service
对于root的手机
private void disableAccessibility(Context context) {
try {
Settings.Secure.putInt(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED, 0);
Logger.t(TAG).d("Accessibility disabled");
} catch (Exception e) {
Logger.t(TAG).e("Exception:" + e.toString());
}
}
似乎以上两个方法都关闭不成功
问题:
1 accessibilityNodeInfo is null
与
2 getRootInActiveWindow return null when the window changed
没有配置
android:canRetrieveWindowContent=“true” 允许辅助功能获得窗口的节点信息 要设置为true
覆盖安装apk 就不回调onConnected??