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

Posted Jingle.zhang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了抢红包软件背后的 Accessibility 服务及启动原理相关的知识,希望对你有一定的参考价值。

前言

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

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

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

后面几篇文章,将慢慢揭开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 写法就是 Serviceandroid:permissionintent-filter 都是必要配置。

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

AccessibilityService 如何启动的

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

在 AOSP 中类似的由系统调度的 App Service 有很多,比如 JobServiceAutoFillServiceNotificationAssistantService

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

  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) {
                ...
                case DO_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 的 boundunboundcrash 等详细信息。

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

以上是关于抢红包软件背后的 Accessibility 服务及启动原理的主要内容,如果未能解决你的问题,请参考以下文章

如何让微信自动抢红包?

苹果手机怎么弄微信自动抢红包

微信自动抢红包怎么设置 最简单的微信自动抢红包方法

自动抢红包程序思路

CSDN日报20170307——《浮华背后与我的路》

苹果手机怎么安装自动抢红包神器