前言
最近在做一个需求的时候发现,在Android 7.0上面,想要唤出系统的最近任务界面,只能通过辅助功能才能实现,Stack Overflow上有这个问题的讨论:How do I launch the recent apps menu in android?
原本想要看看辅助功能是如何唤出系统的最近任务界面的,但是搜了一下AccessibilityService,几乎大部分的文章都是介绍如何利用辅助点击实现抢红包的功能,很少有提到我想搞清楚的事情:Accessibility事件具体是如何分发的。于是,趁着这个机会翻看Android 7.0的源码,整理了Accessibility事件分发流程。
Accessibility事件分发流程简述
先用一张流程图,简要描述Accessibility事件的分发流程:
Accessibility事件经过目标App端的AccessibilityManager,分发给远程的AccssibilityManagerService,最后分发给辅助App端AccessibilityService。
说明
目标App是指发出Accessibility事件的App,而辅助App是指想要接收Accessibility事件进行一些辅助操作的App。以抢红包插件为例,微信是目标App,发出Accessibility事件,而抢红包插件就是辅助App,接收Accessibility事件。
本篇文章介绍的是辅助App接收到目标App发出的Accessibility事件过程,以后有机会再分析,辅助App端向目标App发送如点击事件等功能的过程。
以下几个类会在文中经常出现,先在这里露个脸:
AccessibilityManager,目标App借助AccessibilityManager来向远程的AccssibilityManagerService发送Accessibility事件
AccssibilityManagerService,远程的AccssibilityManagerService负责将目标App的Accessibility事件分发给辅助App
AccssibilityService,l辅助App通过AccessibilityService接收Accessibility事件
为了方便阅读,下文对以上几个类使用简称:
AccessibilityManager: AM
AccssibilityService: AS
AccssibilityManagerService: AMS
下面,我们跟着源码,看看Accessibility事件的具体分发流程。
一. 目标App与远程AMS建立联系
AMS是运行在系统进程的,目标App想要跟AMS进行跨进程通信,目标App就一定得先跟远程的AMS建立联系,也就是需要获取到远程AMS的本地binder。所以我们先了解一下,目标App进程与远程AMS建立联系的过程。
1.1 目标App的AM向远程AMS端的通讯
目标App拥有AM的一个单例,AM在构造的时候,通过tryConnectToServicesLocked()方法,获取AMS的本地binder。
我们看看AM的静态getInstance方法:
接着看看AM的构造方法:
最后看看AM的tryConnectToServiceLocked方法:
总结:AM在构造的时候,通过tryConnectToServicesLocked()方法,获取IAccessibilityManager类型的AMS本地binder。这样,目标App的AM,就可以利用AMS本地binder,与AMS进行跨进程通讯。
1.2 AMS向目标App的AM通讯
AM通过tryConnectToServiceLocked方法除了获取AMS的本地binder,同时也把自己的变量mClient注册到取AMS中。
我们看看AM的tryConnectToServiceLocked方法:
接着看看AM的IAccessibilityManagerClient类型变量mClient:
总结:AM通过tryConnectToServiceLocked方法获取AMS的本地binder,同时也把自己的变量mClient注册到取AMS中。当AMS状态有变化的时候,AMS就可以利用IAccessibilityManagerClient类型的AM本地binder,回调到AM中了。
1.3 总结
通过AM,目标App与远程AMS建立起了联系。
二. Accessibility事件从目标App分发到辅助App的过程
2.1 View把进行Accessibility事件分发给AM
View在触发一些如点击或者滑动的操作时,会调用sendAccessibilityEvent进行Accessibility事件的分发。
可以看到,View在触发对应的操作会发送对应的Accessibility事件:
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
…
AccessibilityEvent定义的几种事件类型,就是我们平时使用辅助服务想要接收到的事件类型:
TYPE_VIEW_CLICKED
TYPE_VIEW_LONG_CLICKED
TYPE_VIEW_FOCUSED
TYPE_WINDOW_STATE_CHANGED
TYPE_VIEW_SCROLLED
…
sendAccessibilityEvent接着会调用sendAccessibilityEventUncheckedInternal方法
首先这里会调用onInitializeAccessibilityEvent初始化AccessibilityEvent事件的一些信息,比如className, packageName, source等,这也是我们平时使用辅助服务接收到的信息。
接着,调用了getParent().requestSendAccessibilityEvent(this, event),即分发给ParentView。
我们知道,Activity最外面的布局是DecorView,而DecorView的Parent是ViewRootImpl,所以最后会调用ViewRootImpl的requestSendAccessibilityEvent(View child, AccessibilityEvent event)方法。
我们看看ViewRootImpl的requestSendAccessibilityEvent(View child, AccessibilityEvent event)方法
可以看到,这里最终调用mAccessibilityManager.sendAccessibilityEvent(event)。
到这里,终于把View和AM联系起来了。
2.2 AM把Accessibility事件分发给远程AMS
下面看看AM的sendAccessibilityEvent方法
首先会调用getServiceLocked(),获取AMS的本地binder
最后,调用AMS的sendAccessibilityEvent方法,把Accessibility事件分到到AMS。到这里,又把AM与远程的AMS联系起来了
总结:View借助AM,把Accessibility事件分发到远程的AMS。
2.3 远程的AMS把Accessibility事件分发到辅助App的AS中
下面看看AMS的sendAccessibilityEvent方法:
这里主要是调用notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event, boolean isDefault)
可以看到,就是从当前的UserState中,遍历mBoundServices,调用内部类Service的notifyAccessibilityEvent(event)方法。
但是注意下,在调用Service的notifyAccessibilityEvent(event)方法之前,先调用canDispatchEventToServiceLocked(service,event)。
这里主要是判断service声明所监听的packageName,是否和event里面的packageName一致,一致才会通知相应的service。这就是我们平时使用辅助功能声明需要监听的packageName。
Service的notifyAccessibilityEvent(event)方法最终会调用notifyAccessibilityEventInternal( eventType, event)方法,这里面调用IAccessibilityServiceClient类型listener的onAccessibilityEvent(event),最终就会回调到AS的onAccessibilityEvent(event).
我们平时使用辅助功能的时候,复写AS的onAccessibilityEvent方法,在这里接收到Accessbility事件的分发。
这样,AMS把Accessibility事件分发到辅助App了。
总结:AMS的内部类Service,利用IAccessibilityServiceClient类型listener(其实是一个本地binder),把Accessibility事件从远程分发到辅助App的AS中。
三. 辅助App端的AS与远程AMS的联系
3.1远程的 AMS与辅助App的AS建立联系的过程
上面说到,IAccessibilityServiceClient类型listener的onAccessibilityEvent(event),最终就会回调到AS的onAccessibilityEvent(event),那AMS具体是如何跟AS联系起来的呢?
IAccessibilityServiceClient.Stub的实现类IAccessibilityServiceClientWrapper,这个binder会在AS的onBind(Intent intent)返回,也就是绑定AS的时候,AS会返回IAccessibilityServiceClient类型的binder给AMS,这样远程的AMS就可以利用本地binder向AS进行通信,把Accessibility事件分发到辅助App的AS中了。
看看AS的onBind(Intent intent):
再看看IAccessibilityServiceClientWrapper类:
IAccessibilityServiceClientWrapper在构造的时候传入Callbacks回调,这样在AMS调用IAccessibilityServiceClientWrapper的onAccessibilityEvent方法的时候,IAccessibilityServiceClientWrapper就会调用Callbacks的onAccessibilityEvent方法。
下面看看Callbacks的onAccessibilityEvent方法:
可以看到,最终调用了AS的onAccessibilityEvent()方法了。
总结:AMS在绑定AS的时候,AS的onBind方法会构造一个IAccessibilityServiceClientWrapper实例给AccessibilityManagerService,IAccessibilityServiceClientWrapper在构造的时候传入了Callbacks参数。
1,AMS调用了IAccessibilityServiceClientWrapper的onAccessibilityEvent()方法
2,IAccessibilityServiceClientWrapper的onAccessibilityEvent()会调用Callbacks的onAccessibilityEvent()方法
3,Callbacks的onAccessibilityEvent()方法会调用AS的onAccessibilityEvent()方法
这样远程的AMS跟辅助App的AS建立了联系:AMS绑定AS的时候,AS会返回IAccessibilityServiceClient类型的binder给AMS,远程的AMS利用本地binder向AS进行通信,把Accessibility事件分发到辅助App的AS中。
3.2 AMS绑定AS的过程
上面说到绑定AS的时候,会返回IAccessibilityServiceClient类型的binder给AMS。这样远程的AMS就可以利用本地binder跟AS进行通信了。
那么,AMS是什么时候绑定AS的呢?
首先,AMS在一些系统状态变化的时候,会调用onUserStateChangedLocked()来更新状态:
1,用户在系统设置页面,为某个App开启辅助功能的时候
2,用户在系统设置页面,关闭某个App辅助功能的时候
3,接收到用户删除App事件的时候
4,接收到App被强制停止事件的时候
…
接下来看看AMS的onUserStateChangedLocked方法:
这里会调用updateServicesLocked()方法:
这里面主要做了三件事情:
1,遍历保存了辅助服务信息的UserState的已安装服务变量mInstalledServices;
2,根据componentName,在mEnabledServices里面查找enabled状态的service,如果不存在就构造一个Service
3,调用Service的bindLocked方法
说明一下,其实componentName里面保存的就是AccessibilityService的packageName和className。
而这里AMS内部类Service就保存了componentName。因此可以把AMS内部类Service,看做是保存AS信息的代理类。
下面,我们看看Service的bindLocked:
可以看到,这里调用了context的bindServiceAsUser(),执行了绑定AS的操作,看看这个mIntent是啥就一目了然了:
在构造Service的时候,构造了mIntent,传入了上述的componentName。也就是说,调用bindServiceAsUser(mIntent...),就是绑定了AS。
到这里,AMS绑定AS的过程就梳理清楚了。总结一下:
1,AMS在一些系统状态变化的时候,如用户在系统设置页面开启某个App的辅助功能时,会调用onUserStateChangedLocked()来更新状态。
2,根据componentName,在mEnabledServices里面查找enabled状态的service,并调用service的bindLocked()方法
3,最后会调用context的bindServiceAsUser(mIntent...),绑定AS
3.3 总结
所以,辅助App端的AS与远程AMS的联系也清楚了:
1,AMS在一些系统状态变化的时候,如用户在系统设置页面开启某个App的辅助功能时,会绑定辅助App声明的AS。
2,AS的onBind()方法会返回IAccessibilityServiceClient本地binder给AMS。
3,当有Accessibility事件分发的时候,AMS利用IAccessibilityServiceClient本地binder,就可以把Accessibility事件分发到辅助App端的AS。
4,AS的onAccessibilityEvent()方法最终会接收到分发的Accessibility事件。
四. 最后
这篇文章,主要介绍了,Accessibility事件由目标App端经AMS,分发到辅助App端AS的过程。同时分析了目标App端的AM是如何与远程AMS联系起来的,以及远程AMS是如何与辅助App端的AS联系起来的。
最后用下面的流程图,回顾一下Accessibility事件,经过目标App端的AM,分发给远程的AMS,最后分发给辅助App端AS的过程。