实现Android跨进程组件通信能有多简单?
作为一个Android开发,都要会点组件化知识。组件化的主要的特点,是剥离依赖,但组件间不直接依赖后,通信问题要怎么解决呢。
通常我们用的一下这种类似Binder通信的C/S架构,由一个ServiceManager服务管理器作为一个桥梁提供服务注册和服务查询,业务上要进行通信就是以下三部曲:定义服务接口,发布服务,使用服务。
这套架构在单一进程间运行很简单且直观,我们需要做的就是把服务接口下沉到公共依赖,在组件A实例化服务接口的对象,然后通过ServiceManager进行服务发布,组件B通过ServiceManager获取并使用服务就好了。但要想跨进程,就有点麻烦了。
保持这套架构的场景下,我们可以去写AIDL,让ServiceManager支持管理Binder类型的服务就好了,ServiceManager的角色用一个Android的Service去实现,每个组件要发布、查询服务,直接通过bind这个ServiceManager就可以了。通信模型大致如下:
看来做跨进程通信支持,模型变化也并不大,还剩下的问题就仅仅是每个服务类型的实现了,通常来讲就是定义AIDL以及实现Binder服务类。市面上很多框架也确实做到了这点,比如爱奇艺开源的Andromeda就支持跨进程的服务发布。
但我们还是得自己写AIDL,远程服务的使用端用IInterface代理的远程服务对象,每次使用都要处理RemoteException也要处理。实际上写过AIDL的朋友们可能知道这体验并不好。我觉得麻烦的点主要在于:
- 定义AIDL接口文件(无IDE自动提示支持)
- 编译AIDL生成Stub类 (需预先编译一次生成)
- 继承Stub类并实现AIDL中定义的接口 (必须继承Stub类型,不如普通服务的实现类只需实现对应的接口,可以继承其他类进行代码复用)
- 通过代理接口进行远程调用必须捕获RemoteException (要么每个调用都catch处理一下,要么写个包装类wrapper把异常吃掉返回默认值)
如果涉及到接口的改动,又要编译两次,改AIDL文件,改Stub类实现文件,如果在第4步用了包装类,还得改包装类文件……
所以,跨进程通信的问题点主要麻烦在于AIDL书写起来太麻烦了!
现在的期望是:通过现在流行的APT(注解处理工具)技术动态生成这些模板代码,让我们回到最初,还是只关注接口、实现就好了,让跨进程组件通信和普通组件通信一样简单、优雅。
那要怎么做呢,继续往下看!
如何简化AIDL实现?
仔细看来,AIDL流程中真正的实现核心还在于接口定义(对应AIDL定义)和接口实现(Stub实现),与普通服务定义并无二致。唯一比进程间服务多的处理大概就是远程服务需要考虑的RemoteException的处理了。整体实现过程其实好多都是模板化的工作,真正的核心也就是普通的接口定义以及实现,只要我们能够自动生成那些模板类,把实际实现代理给接口的实现类就好了。基于这种情况,我们来考虑使用动态生成代码的方式来避免写这些模板代码。
哪些东西可以自动生成?
- AIDL文件:AIDL与Interface接口功能是一致的,我们可以根据普通的Interface定义生成AIDL
- Stub类的实现:可自动生成Stub实现,内部只需要代理Interface的实现即可把远程调用转发给Interface的实现类
- Binder接口代理:如上面分析,ADIL通信中远端拿到的是服务的代理接口,每个调用都需要捕获RemoteException,我们可以自动生成这样的一个代理,将异常通过统一的方式转发出来,对于不想做特殊处理的调用者,提供默认的实现捕获异常或者重新抛出RuntimeException类型的异常,反正就是不需要调用者必须在每个方法的调用时进行异常处理了,让调用者对于是否要处理调用的异常有选择的权利。
新的跨进程通信模型
假设我们已经完成以上文件的自动生成,若定义一个IMusic
的接口,提供音乐相关服务,我们会自动生成如下文件:
- IMusicBinder.aidl: AIDL定义文件,编译后会生成
IMusicBinder
和IMusicBinder.Stub
。 - IMusicRemote.java:继承
IMusicBinder.Stub
,用于发布到远端,内部代理IMusic的具体实现。 - IMusicRemoteProxy.java:实现
IMusic
接口,用于远端服务调用,代理IMusicBinder
远程服务,内部处理每个调用的异常。
另外假设MusicService
是IMusic
的实现类,那新的模型大致就可以做成下面的样子:
流程大致描述为:
- 进程A的服务实现组件中,通过SimpleService把实现了IMusic的MusicService对象作为远程服务发布。
- 进程A的SImpleService内部把MusicService包装成IMusicRemote发布到RemoteServiceManager。RemoteServiceManager可以实现为上面讲到的一个Android的Service,用于真正的管理所有的远程服务对象的Binder。
- 进程B的服务使用组件中通过SimpleService发起IMusic服务的获取请求。
- 进程B的SimpleService向RemoteServiceManager请求IMusicRemote对象。
- RemoteServiceManager返回保存的IMusicRemote对象给进程B的SimpleService。
- 进程B的SimpleService将IMusicRemote对象包装成IMuisicRemoteProxy返回给服务使用组件。
自此,服务使用组件就拿到了一个IMusic服务对象,可以进行跨进程通信了。
于是,我们完成了简化的进程间通信模型,解答了小节标题提出的问题。
这样我们就可以只定义普通的接口和接口实现,但却等发布为一个跨进程的服务了!
我们可以:不用再写AIDL,不用再等两遍编译,不用再考虑每次调用跨进程方法需要处理烦人的RemoteException,不用再害怕修改一点接口而去改很多个文件了,甚至可以用上IDE的重构功能,轻松修改接口定义实现……
具体实现,开始使用
经过几周的努力,目前大概实现了一个可用的版本: github传送
具体怎么用呢,看下面:
一、先看看普通组件服务发布使用流程,如下:
// 1. 公共依赖模块,声明服务接口
interface IMusic {
fun play(name: String)
}
// 2. 音乐组件模块,实现服务接口
class MusicService