从源码看Accessibility事件分发流程

前言

最近在做一个需求的时候发现,在Android 7.0上面,想要唤出系统的最近任务界面,只能通过辅助功能才能实现,Stack Overflow上有这个问题的讨论:How do I launch the recent apps menu in android?

原本想要看看辅助功能是如何唤出系统的最近任务界面的,但是搜了一下AccessibilityService,几乎大部分的文章都是介绍如何利用辅助点击实现抢红包的功能,很少有提到我想搞清楚的事情:Accessibility事件具体是如何分发的。于是,趁着这个机会翻看Android 7.0的源码,整理了Accessibility事件分发流程。

Accessibility事件分发流程简述

先用一张流程图,简要描述Accessibility事件的分发流程:

从源码看Accessibility事件分发流程_第1张图片
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方法:

AccessibilityManager的getInstance方法

接着看看AM的构造方法:

AccessibilityManager的构造方法

最后看看AM的tryConnectToServiceLocked方法:

AccessibilityManager的tryConnectToServiceLocked方法

总结:AM在构造的时候,通过tryConnectToServicesLocked()方法,获取IAccessibilityManager类型的AMS本地binder。这样,目标App的AM,就可以利用AMS本地binder,与AMS进行跨进程通讯。

1.2 AMS向目标App的AM通讯

AM通过tryConnectToServiceLocked方法除了获取AMS的本地binder,同时也把自己的变量mClient注册到取AMS中。

我们看看AM的tryConnectToServiceLocked方法:

AccessibilityManager的tryConnectToServiceLocked方法

接着看看AM的IAccessibilityManagerClient类型变量mClient:

AccessibilityManager的mClient变量

总结:AM通过tryConnectToServiceLocked方法获取AMS的本地binder,同时也把自己的变量mClient注册到取AMS中。当AMS状态有变化的时候,AMS就可以利用IAccessibilityManagerClient类型的AM本地binder,回调到AM中了。

1.3 总结

通过AM,目标App与远程AMS建立起了联系。

从源码看Accessibility事件分发流程_第2张图片
目标App与远程AMS建立联系

二. Accessibility事件从目标App分发到辅助App的过程

2.1 View把进行Accessibility事件分发给AM

View在触发一些如点击或者滑动的操作时,会调用sendAccessibilityEvent进行Accessibility事件的分发。

View的performClick方法

可以看到,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方法

View的sendAccessibilityEventUncheckedInternal方法

首先这里会调用onInitializeAccessibilityEvent初始化AccessibilityEvent事件的一些信息,比如className, packageName, source等,这也是我们平时使用辅助服务接收到的信息。

View的onInitializeAccessibilityEvent方法

接着,调用了getParent().requestSendAccessibilityEvent(this, event),即分发给ParentView。

我们知道,Activity最外面的布局是DecorView,而DecorView的Parent是ViewRootImpl,所以最后会调用ViewRootImpl的requestSendAccessibilityEvent(View child, AccessibilityEvent event)方法。

我们看看ViewRootImpl的requestSendAccessibilityEvent(View child, AccessibilityEvent event)方法

ViewRootImpl的requestSendAccessibilityEvent方法

可以看到,这里最终调用mAccessibilityManager.sendAccessibilityEvent(event)。

到这里,终于把View和AM联系起来了。

从源码看Accessibility事件分发流程_第3张图片
View把Accessibility事件分发到AccessibilityManager

2.2 AM把Accessibility事件分发给远程AMS

下面看看AM的sendAccessibilityEvent方法

AccessibilityManager的sendAccessibilityEvent方法

首先会调用getServiceLocked(),获取AMS的本地binder

AccessibilityManager的getServiceLocked方法

最后,调用AMS的sendAccessibilityEvent方法,把Accessibility事件分到到AMS。到这里,又把AM与远程的AMS联系起来了

总结:View借助AM,把Accessibility事件分发到远程的AMS。

从源码看Accessibility事件分发流程_第4张图片
Accessibility事件从View分发到远程AccessibilityManagerService

2.3 远程的AMS把Accessibility事件分发到辅助App的AS中

下面看看AMS的sendAccessibilityEvent方法:

AccessibilityManagerService的sendAccessibilityEvent方法

这里主要是调用notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event, boolean isDefault)

AccessibilityManagerService的notifyAccessibilityServicesDelayedLocked方法

可以看到,就是从当前的UserState中,遍历mBoundServices,调用内部类Service的notifyAccessibilityEvent(event)方法。

但是注意下,在调用Service的notifyAccessibilityEvent(event)方法之前,先调用canDispatchEventToServiceLocked(service,event)。

这里主要是判断service声明所监听的packageName,是否和event里面的packageName一致,一致才会通知相应的service。这就是我们平时使用辅助功能声明需要监听的packageName。

AccessibilityManagerService的canDispatchEventToServiceLocked方法

Service的notifyAccessibilityEvent(event)方法最终会调用notifyAccessibilityEventInternal( eventType, event)方法,这里面调用IAccessibilityServiceClient类型listener的onAccessibilityEvent(event),最终就会回调到AS的onAccessibilityEvent(event).

我们平时使用辅助功能的时候,复写AS的onAccessibilityEvent方法,在这里接收到Accessbility事件的分发。

Service的notifyAccessibilityEventInternal方法

这样,AMS把Accessibility事件分发到辅助App了。

总结:AMS的内部类Service,利用IAccessibilityServiceClient类型listener(其实是一个本地binder),把Accessibility事件从远程分发到辅助App的AS中。

从源码看Accessibility事件分发流程_第5张图片
AccessibilityManagerService把Accessibility事件分发到辅助App的AccessibilityService

三. 辅助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):

AccessibilityService的onBind方法

再看看IAccessibilityServiceClientWrapper类:

IAccessibilityServiceClientWrapper类

IAccessibilityServiceClientWrapper在构造的时候传入Callbacks回调,这样在AMS调用IAccessibilityServiceClientWrapper的onAccessibilityEvent方法的时候,IAccessibilityServiceClientWrapper就会调用Callbacks的onAccessibilityEvent方法。

下面看看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开启辅助功能的时候

AccessibilityManagerService的enableAccessibilityService方法

2,用户在系统设置页面,关闭某个App辅助功能的时候

AccessibilityManagerService的disableAccessibilityService方法

3,接收到用户删除App事件的时候

4,接收到App被强制停止事件的时候

接下来看看AMS的onUserStateChangedLocked方法:

AccessibilityManagerService的onUserStateChangedLocked方法

这里会调用updateServicesLocked()方法:

AccessibilityManagerService的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:

Service的bindLocked方法

可以看到,这里调用了context的bindServiceAsUser(),执行了绑定AS的操作,看看这个mIntent是啥就一目了然了:

Service的构造方法

在构造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的过程。

从源码看Accessibility事件分发流程_第6张图片
Accessibility事件的分发流程图

你可能感兴趣的:(从源码看Accessibility事件分发流程)