抢红包软件背后的 Accessibility 服务及启动原理

前言

最近开发的一款设备使用到了 Accessibility 功能,Android 提供了Accessibility功能和服务帮助这些用户更加简单地操作设备。

需要实现 AccessibilityService ,AccessibilityService是一个系统服务,它运行在后台,并且能够收到由系统发出的一些事件,比如通知状态、view 的一些相关事件,指纹,touch 等。

界面中产生的任何变化都会由系统通知给 AccessibilityService。大家熟知的 抢红包 软件, Talkback 都是使用AccessibilityService 实现的。 自动化测试 等等,都是基于 Accessibility。

AccessibilityService 是 Service 吗

简单例子这里面就不讲了,大家可以 上搜索下。

AndroidManifest.xml 配置如下

<service

android:name= “XXX.Service”

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/accessible_service_config”/>//对AccessibilityService 的配置文件

</service>

</application>

AccessibilityService 写法就是 Service , android:permission , intent-filter 都是必要配置。

貌似还是 Service 那一套,但又不仅限于此,后面的内容可以发现它是不一般的 Servcie。

AccessibilityService 如何启动的

虽作为 Servcie,App 本身并没有启动和停止它,完全由系统调度,这是第一个不一样的地方。

在 AOSP 中类似的由系统调度的 App Service 有很多,比如 JobService 、 AutoFillService 、 NotificationAssistantService 等

下面将将具体解答这两个问题:

  1. 谁启动和停止 AccessibilityService ?

  2. 设备重启了,AccessbilityService 也会启动吗,不会被kill吗?

1. 谁启动了 AccessibilityService

安装AccessibilityService 的应用,会出现在设置-无障碍-应用列表中,选择打开,应用中的AccessibilityService 就启动。

代码上,设置应用 会将AccessibilityService 的 ComponentName 信息 存入 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES

AccessibilityManagerService 系统服务将监听该 Map 的变化,启动或者关闭相应 ComponentName 的 AccessibilityService。

流程如下:

  1. 监听 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES

  2. 更新 AccessibilityUserState

  3. 遍历 mEnableServices ,如果没有bind 则调用 bindService 启动它

2、重启会AccessbilityService 会启动吗,不会被kill?

AccessibilityManagerService 是 SystemService 进程,开机自启动,所以 AccessbilityService 的开机启动就简单了,源码是监听 unlock 广播,

bindService 则使用 Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE FLAG 来启动前台级别的 service,所以不会被Kill。

frameworks/baseservices/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java

public void bindLocked{

AccessibilityUserState userState = mUserStateWeakReference.get;

if(userState == null) return;

final long identity = Binder.clearCallingIdentity;

try {

int flags = Context.BIND_AUTO_CREATE

| Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE //前台service,所以不会被Kill

| Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS

| Context.BIND_INCLUDE_CAPABILITIES;

if(userState.getBindInstantServiceAllowedLocked) {

flags |= Context.BIND_ALLOW_INSTANT;

}

if(mService == null && mContext.bindServiceAsUser(

mIntent, this, flags, new UserHandle(userState.mUserId))) {

userState.getBindingServicesLocked.add(mComponentName);

}

} finally {

Binder.restoreCallingIdentity(identity);

}

mActivityTaskManagerService.setAllowAppSwitches(mComponentName.flattenToString,

mAccessibilityServiceInfo.getResolveInfo.serviceInfo.applicationInfo.uid,

userState.mUserId);

}

注意:开发中遇到过一种情况 AccessibilityService 不会启动,设置 App 中会显示 无法运行。点按可查看相关信息的提示,这题由于 AccessibilityService 所在的 App 发生了 crash,而 crash 信息会记录在AccessibilityUserState中,不再自动 bind。

以上流程AccessibilityManagerService 与 应用中AccessibilityService通信,

AccessibilityService 和 AccessibilityManagerService 的关系

分析完以上流程,可以看到

  • AccessibilityManagerService 充当了 client

  • AccessibilityService 则是 service

  • 所以调用过程的本质上是AccessibilityManagerService -> AccessibilityService。

    可能有朋友会有疑惑:AccessibilityService 虽然本质上是 Service ,但分明又提供了 onServiceConnected 的方法,那它到底属于 client 还是 service?

    从以上时序图可以看出,AccessibilityManagerService 绑定 AccessibilityService 成功后,会立即调用 IAccessibilityServiceClient#init。

    init 的目的很简单:将 AccessibilityManagerService 侧的 IAccessibilityServiceConnection 接口回调回来,可以方便AccessibilityService 反过来调用 AccessibilityManagerService。回调过来的接口正常的话会调用上述提供的 onServiceConnected。

    简言之,这个 onServiceConnected 指的是 AccessibilityManagerService 的 IAccessibilityServiceConnection 服务准备好了。

    public abstract class AccessibilityService extends Service {

    public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub

    implements HandlerCaller.Callback {

    public void init(IAccessibilityServiceConnection connection, int connectionId,

    IBinder windowToken) {

    // Binder 线程切换到 Main 线程

    // 传递 id 和 AccessibilityManagerService 返回的 connection 接口

    Message message = mCaller.obtainMessageIOO(DO_INIT, connectionId,

    connection, windowToken);

    mCaller.sendMessage(message);

    }

    @Override

    public void executeMessage(Message message) {

    switch (message.what) {

    caseDO_INIT: {

    mConnectionId = message.arg1;

    SomeArgs args = (SomeArgs) message.obj;

    IAccessibilityServiceConnection connection =

    (IAccessibilityServiceConnection) args.arg1;

    IBinder windowToken = (IBinder) args.arg2;

    args.recycle;

    // 如果系统返回的 connection 没有问题

    // 回调 AccessibilityService 自己的初始化

    // 并回调提供的 onServiceConnected

    if(connection != null) {

    AccessibilityInteractionClient.getInstance.addConnection(mConnectionId,

    connection);

    mCallback.init(mConnectionId, windowToken);

    mCallback.onServiceConnected;

    } else{

    AccessibilityInteractionClient.getInstance.removeConnection(

    mConnectionId);

    mConnectionId = AccessibilityInteractionClient.NO_ID;

    AccessibilityInteractionClient.getInstance.clearCache;

    mCallback.init(AccessibilityInteractionClient.NO_ID, null);

    }

    return;

    }

    }

    }

    }

    }

    所以说:AccessibilityService 这种特殊的 Service,既是供 AccessibilityManagerService 传递无障碍事件的 service ,同时又是会反向调用 AccessibilityManagerService 的 client 。

    那 AccessibilityService 什么时候需要反向调用 AccessibilityManagerService 呢?

    其实这种的场景很多,这构成了 AccessibilityService 功能的重要部分,包括:

  • 动态更新 Accessibility 的配置(serServiceInfo)

  • 发出具体的手势(dispatchGesture)

  • 发出截图的请求(takeScreenshot)

  • 发出屏幕缩放的请求(setMagnificationScaleAndCenter)

  • 等等

  • 下面提及的 Accessibility 配置的动态更新正是这个场景之一!

    Accessibility 配置的加载

    AccessibilityService 支持很多配置,但是很多配置在实际开发中都是用不到的。配置的方式有 静态 和 动态 两种。

  • 静态配置(更为推荐) 就像文章开头在 meta-data 里配置信息

  • 动态配置 运行中调用 serServiceInfo 根据需要动态更新配置

  • 从上图可以看出,

  • 静态配置:AccessibilityManagerService 通过 PackageManager 获取 xml 的配置信息,就转化为 AccessibilityServiceInfo ,AccessibilityServiceInfo 保存在 System Server 进程

  • 动态配置:app 使用 IAccessbilityServiceConnection 接口作为桥梁,去调用 System Server ,设置 AccessibilityServiceInfo

  • 具体配置的内容,这里就不多讲了,整个Accessibility 的内容是很庞大的,所以相关配置也比较多。

    AccessibilityService 的调试

    上面说过开启的 AccessibilityService 会存在 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES 中,通过相应的 adb 命令可以实时打印这些信息,高效调试。

    adb shell settings get secure enabled_accessibility_services

    可以读当然也可以写,免去了到 Settings App 里手动打开:

    adb shell settings put secure enabled_accessibility_services <com.example.xxx/.xxxService>

    注意:参数是所有打开的 Service 列表,新写入的 Service 名称要在已有的 Service 后面追加,用 : 隔开。不然会清除其他 开启的Servcie。

    adb shell settings put secure enabled_accessibility_services <xxx/.xxxService:xxx/.xxxService:xxx/.xxxService>

    除了查看开启,同时还有个命令可以观察 AccessibilityService 的 bound 、 unbound 和 crash 等详细信息。

    adb shell dumpsys accessibility

    总结

    本次主要分享了AccessibilityService 是如何启动, AccessibilityService 如何与SystemServer 中 AccessibilityManagerService 如何相互调用。

    大概关系如图所示:

    总结如下:

  • AccessibityManagerService 创建和管理 AccessibilityServiceConnection

  • AccessibilityServiceConnection 与 App 中的 AccessibilityService 一 一对应

  • AccessibilityService 将 IAccessibilityServiceClient 接口暴露给 AccessibityManagerService 调度

  • AccessibityManagerService 将 IAccessibilityServiceConnection 接口传递给 AccessibilityService 回调

  • 再次梳理下 AccessibilityService 的特点:

    1. 由 AccessibityManagerService bind 和 unbind

    2. 设备重启会 自行启动

    3. 拥有 前台 Service 的 Flag,优先级高不会被 kill 掉

    4. 接受 AccessibityManagerService 的调度,同时会反向调用,既是 service 又是 client

    – EOF –

    点击标题可跳转

    1、 Android 12 适配工作来了,实践 Jetpack SplashScreen API

    2、 面试突击:OkHttp 原理 8 连问

    3、 APP 加固攻防梳理

    推荐关注「安卓开发精选」,提升安卓开发技术

    点赞和在看就是最大的支持??

    声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

    上一篇 2021年10月12日
    下一篇 2021年10月12日

    相关推荐