关于获取 Android 前台 Activity 监听用户点击事件 Android辅助功能 Accessibility Services

时间:2022-10-11 00:58:36


概述

Accessbility 又叫做辅助功能,是Android官方推出帮助身体不便或者操作不灵活的人来辅助操作手机应用的。当然也可以用来干一些别的事,比如自动抢红包啊,静默安装app,帮助用于开一系列权限操作等。出于项目需求,大概研究了下Accessilibity的基本用法。

Accessibility用法

实现自己的辅助功能类

要实现自己的辅助功能,需要继承系统的AccessibilityService服务类。然后实现其中的抽象方法,这些方法被系统调用的顺序是:当辅助功能服务开始时onServiceConnected()方法会被调用,当辅助功能正在运行时onAccessibilityEvent(), onInterrupt()方法会被调用,当辅助功能结束时onUnbind()方法会被调用。以上也就是辅助功能的生命周期了。

  • onServiceConnected() (可选的方法)系统会在成功连接上你的服务的时候调用这个方法,在这个方法里你可以做一下初始化工作,例如设备的声音震动管理,也可以调用setServiceInfo()进行配置工作。
  • onAccessibilityEvent() - (必须实现的方法) 通过这个函数可以接收系统发送来的AccessibilityEvent,接收来的AccessibilityEvent是经过过滤的,过滤是在配置工作时设置的。在整个生命周期里会被调用多次
  • onInterrupt() - (required) 这个在系统想要中断AccessibilityService返给的响应时会调用。在整个生命周期里会被调用多次。
  • onUnbind() - (optional) 在系统将要关闭这个AccessibilityService会被调用。在这个方法中进行一些释放资源的工作。

Manifest 注册和权限申明

想要实现自己的辅助功能,你需要在Manifest配置文件中注册该服务,且申明相应的权限。如此,你才能接收到系统的服务功能服务。

Accessibility service 的申明

为了实现辅助功能,你需要在Manifest中添加如下代码:

<application>
<service android:name=".MyAccessibilityService"
android:label="@string/accessibility_service_label"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
</service>
</application>

注意 1. action 是固定的“android.accessibilityservice.AccessibilityService”用于系统启动我们的辅助功能
注意2. 你必须给你的辅助功能服务申请权限 android:permission=”android.permission.BIND_ACCESSIBILITY_SERVICE”,否者不能使用辅助功能。

Accessibility service 配置

辅助功能必须指定一个辅助事件配置文件来处理和过滤指定的事件。辅助功能配置可以由AccessibilityServiceInfo来实现,在辅助功能服务的onServiceConnected()方法中调用AccessibilityServiceInfo类的setServiceInfo()方法来配置。

当然从Android 4.0开始,你也可以在manifest中添加 标签来未辅助功能指定一个xml的配置。代码如下:

<service android:name=".MyAccessibilityService">
...
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>

标签指向一个XML资源文件,文件路径(/res/xml/accessibility_service_config.xml). 示例代码如下:

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_service_description"
android:packageNames="com.example.android.apis"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFlags="flagDefault"
android:accessibilityFeedbackType="feedbackSpoken"
android:notificationTimeout="100"
android:canRetrieveWindowContent="true"
/>

如上配置可以指定该辅助功能监听哪个应用的操作,允许处理哪些事件,用户点击事件反馈的类型,查找节点方式,超时时间,是否允许辅助功能获得窗口节点信息等。配置文件属性解释如下:


  • android:description :辅助功能描述,描述该辅助功能用来干嘛的
  • android:packageNames :指定辅助功能监听的包名,不指定表示监听所有应用
  • android:accessibilityEventTypes:辅助功能处理事件类型,一般配置为typeAllMask表示接收所有事件
  • android:accessibilityFlags:辅助功能查找截点方式,一般配置为flagDefault默认方式。
  • android:accessibilityFeedbackType:操作相应按钮以后辅助功能给用户的反馈类型,包括声音,震动等。
  • android:notificationTimeout:相应时间设置
  • android:canRetrieveWindowContent:是否允许辅助功能获得窗口的节点信息,为了能正常实用辅助功能,请务必保持该项配置为true

从一个AccessibilityEvent中调查完全视图层级的能力隐式地暴露私有用户信息给你的无障碍服务。出于这个原因,你的服务必须通过无障碍服务配置XML文件请求这个级别的访问权,通过包含canRetrieveWindowContent属性和设置它为true。如果你不在你的服务配置xml文件中包含这个设置,那么对getSource()的调用会失败。
想要了解更多更详细的配置,请阅读AccessibilityServiceInfo类。该类就是用于辅助功能服务配置信息类。

开启辅助功能开关

如下代码判断辅助功能是否开启,如果没有开启,则跳转到系统页面去开启。

/**
* 该辅助功能开关是否打开了
* @param accessibilityServiceName:指定辅助服务名字
* @param context:上下文
* @return
*/
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) {
Log.e(TAG, "get accessibility enable failed, the err:" + e.getMessage());
}
if (accessibilityEnable == 1) {
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
String settingValue = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();
if (accessibilityService.equalsIgnoreCase(serviceName)) {
Log.v(TAG, "We've found the correct setting - accessibility is switched on!");
return true;
}
}
}
}else {
Log.d(TAG,"Accessibility service disable");
}
return false;
}

/**
* 跳转到系统设置页面开启辅助功能
* @param accessibilityServiceName:指定辅助服务名字
* @param context:上下文
*/
private void openAccessibility(String accessibilityServiceName, Context context){
if (!isAccessibilitySettingsOn(accessibilityServiceName,context)) {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
}
}

使用辅助功能查找节点

首先我们可以通过如下方法获取当前页面节点信息AccessibilityNodeInfo:

  1. AccessibilityEvent#getSource :得到AccessibilityNodeInfo事件来源
  2. AccessibilityService#getRootInActiveWindow :得到当前窗口根节点所有信息

以上两个方法都可以获取到当前页面的节点信息,然后我们可以通过AccessibilityNodeInfo来查找具体的View,然后对该View操作。查找View有两如下两种方法:

  1. AccessibilityNodeInfo#findAccessibilityNodeInfosByText():通过Text查找
  2. AccessibilityNodeInfo#findAccessibilityNodeInfosByViewId():通过Id查找

我们一般都通过指定的Text内容来查找,这样查找方便,简单

使用辅助功能模拟点击

我们通过AccessibilityNodeInfo#findAccessibilityNodeInfosByText()查找到指定的node节点后,就可以通过相应的方法模拟点击事件了。方法如下:

  • AccessibilityNodeInfo#performAction() :执行用户行为操作

比如点击事件代码如下:

node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
  • 1

用户常用的行为事件类型一般有如下:

  • ACTION_CLICK:模拟点击
  • ACTION_SELECT:模拟选中
  • ACTION_LONG_CLICK:模拟长按
  • ACTION_SCROLL_FORWARD:模拟往前滚动

总结:

虽然很多手机支持辅助功能,但是由于各手机厂商定制或者自定义View,没有严格按照Google标准来,使得有些界面使用了自定义的View,导致辅助功能找不到其节点,从而使得即使有辅助功能,也不能模拟点击View操作。

​​demo 下载​​

使用后说明:

  此方式可以成功获得前台Activity,但监听用户点击会有问题,如果用户点击了没有焦点的位置是监听不到的。另外如果没有root权限可能每次让用户打开也不方便。