02 项目架构-IPC通信框架
Android App开发中的IPC(进程间通信)无处不在。比如我们使用的 AlarmManager 、 InputMethodService 都是系统为我们提供的服务,处于单独的进程中。如果需要在自己的App进程中使用这些服务就需要进行IPC通信。
除此之外,我们自己的程序中也会存在进程通信的可能(特别是在一些大型APP中)
QQ:未登陆
微信:使用一段时间后:
场景:在Service中开启定位服务,Service处于单独的进程,需要在App主进程或者其他APP中获得定位结果。
服务中提供暴露给其他进程使用的方法并提供一个 ServiceId 注解标记,而服务实现中必须给到相同的 ServiceId 与方法实现,不强制要求 LocationManager 一定需要继承 ILocationManager j接口,但是为了保证方法签名统一建议继承。(不然一个是getLocation,另一个是getLocation2就不好玩了)
在Service进行定位,定位结果在 LocationManager 中记录。在这个Service中使用框架注册 LocationManager 。
不需要返回 Binder 对象,这意味着使用者不需要编写繁琐没任何提示的AIDL文件。
框架内部会提供 com.enjoy.ipc.IPCService$IPCServiceX 多个预留Service,用于与其他进程通信,如果一个App存在多个进程都需要提供各自进程的服务,可以使用不同的Service。所以本质上依然是借助的Service+Binder通信,但框架将细节封装隐藏,使用更加简单。
获得结果对象后就能像调用本地方法一样调用远程方法(RPC调用)。
在使用中简化了:
1、不需要自己定义AIDL接口,使用的JavaBean也不要求实现 Parcelable 接口;
2、在客户端不需要直接使用 bindService 获得 Binder 对象;
服务端需要定义暴露服务的接口(ILocationManager),客户端如果是其他APP,则需要将接口类放到自己的源码中(不需要接口实现)。接口中定义的方法就是服务端提供给其他进程使用的方法。
整个框架包含了服务端与客户端两端接口。
在服务进程中会缓存 ServiceId 与对应的服务实现Class对象: 服务表 ,同时服务实现中的所有方法列表也需要进行记录: 方法表 。由于一个服务中可能存在多个方法,所以其数据结构为 Map<Class,Map<String,Method>> ,外层 Map 的key为服务Class,内层 Map 的key则为方法标记。
当客户端需要调用服务时,将 ServiceId 、MethodName以及执行方法需要的参数传递给服务端,服务端查表利用反射 Method#invoke 即可执行服务中的方法。
其中客户端的请求被封装为 Request 对象,服务端响应则封装为 Response 对象
服务端只需要暴露服务接口给其他进程使用,所以服务端只需要调用框架的注册接口 regiest 对服务实现进行注册。( 注册的是服务实现,而不是服务接口 )
注册时,通过反射获得Class上的 ServiceId 即可记录 服务表 。同时利用反射获得Class中所有的public Method即可记录 方法表 。
由于框架本质还是利用Binder来完成通信,为了与其他进程通信,框架内部提供了多个预留的Service。
通信Service会返回一个AIDL生成的Binder类对象
客户端使用 send 方法向服务端发起请求。
服务端接收到请求后的实现:
客户端需要先与服务端建立连接,因此框架中提供了 connect 方法,内部封装 bindService 实现与服务端通信Service( IPCService )的绑定。
唯一需要注意的是:
当完成绑定后,客户端就可以获得服务端通信Service提供的 IIPCService 对象,客户端调用 IIPCService#send 发起请求。
当我们需要获得 Location 。则应该调用 LocationManager.getDefault().getLocation() 。这句调用会需要执行 LocationManager 的两个方法: getDefault 与 getLocation 。
然而这个对象存在服务端,客户端如何获得?
我们可以利用动态代理,在客户端创建一个 "假的" 服务接口对象(代理)。
当我们执行这个代理对象的方法( getLocation )时,会回调 IPCInvocationHandler#invoke 方法,在这个方法中框架会向服务端发起请求: IIPCService#send
而 getLocation 会返回一个 Location 记录定位信息的对象,这个对象会被服务端json序列化发送过来,因此,客户端只需要在此处获得 Method 的返回类型并反序列化即可。
RPC指的是:从客户端上通过参数传递的方式调用服务器上的一个函数并得到返回的结果,隐藏底层的通讯细节。在使用形式上像调用本地函数一样去调用远程的函数。
比如我们使用Okhttp进行网络请求:
这种方式很显然不是RPC。
而使用Retrofit:
RPC:我们调用远程的XXX方法,就像在调用本地方法一样。