Android进阶宝典 -- 告别繁琐的AIDL吧,手写IPC通信框架,5行代码实现进程间通信(上)
对于进程间通信,很多项目中可能根本没有涉及到多进程,很多公司的app可能就一个主进程,但是对于进程间通信,我们也是必须要了解的。
如果在Android中想要实现进程间通信,有哪些方式呢?
(1)发广播(sendBroadcast):e.g. 两个app之间需要通信,那么可以通过发送广播的形式进行通信,如果只想单点通信,可以指定包名。但是这种方式存在的弊端在于发送方无法判断接收方是否接收到了广播,类似于UDP的通信形式,而且存在丢数据的形式;
(2)Socket通信:这种属于Linux层面的进程间通信了,除此之外,还包括管道、信号量等,像传统的IPC进程间通信需要数据二次拷贝,这种效率是最低的;
(3)AIDL通信:这种算是Android当中主流的进程间通信方案,通过Service + Binder的形式进行通信,具备实时性而且能够通过回调得知接收方是否收到数据,弊端在于需要管理维护aidl接口,如果不同业务方需要使用不同的aidl接口,维护的成本会越来越高。
那么本篇文章并不是说完全丢弃掉AIDL,它依然不失为一个很好的进程间通信的手段,只是我会封装一个适用于任意业务场景的IPC进程间通讯框架,这个也是我在自己的项目中使用到的,不需要维护很多的AIDL接口文件。
有需要源码的伙伴,可以去我的github首页获取 FastIPC源码地址,分支:feature/v0.0.1-snapshot,有帮助的话麻烦给点个star⭐️⭐️⭐️
1 服务端 - register
首先这里先说明一下,就是对于传统的AIDL使用方式,这里就不再过多介绍了,这部分还是比较简单的,有兴趣的伙伴们可以去前面的文章中查看,本文将着重介绍框架层面的逻辑。
那么IPC进程间通信,需要两个端:客户端和服务端。服务端会提供一个注册方法,例如客户端定义的一些服务,通过向服务端注册来做一个备份,当客户端调用服务端某个方法的时候来返回值。
object IPC {
//==========================================
/**
* 服务端暴露的接口,用于注册服务使用
*/
fun register(service: Class<*>) {
Registry.instance.register(service)
}
}
其实在注册的时候,我们的目的肯定是能够方便地拿到某个服务,并且能够调用这个服务提供的方法,拿到我想要的值;所以在定义服务的时候,需要注意以下两点:
(1)需要定义一个与当前服务一一对应的serviceId,通过serviceId来获取服务的实例;
(2)每个服务当中定义的方法同样需要对应起来,以便拿到服务对象之后,通过反射调用其中的方法。
所以在注册的时候,需要从这两点入手。
1.1 定义服务唯一标识serviceId
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
annotation class ServiceId(
val name: String
)
一般来说,如果涉及到反射,最常用的就是通过注解给Class做标记,因为通过反射能够拿到类上标记的注解,就能够拿到对应的serviceId。
class Registry {
//=======================================
/**用于存储 serviceId 对应的服务 class对象*/
private val serviceMaps: ConcurrentHashMap<String, Class<*>> by lazy {
ConcurrentHashMap()
}
/**用于存储 服务中全部的方法*/
private val methodsMap: ConcurrentHashMap<Class<*>, ConcurrentHashMap<String, Method>> by lazy {
ConcurrentHashMap()
}
//=======================================
/**
* 服务端注册方法
* @param service 服务class对象
*/
fun register(service: Class<*>) {
// 获取serviceId与服务一一对应
val serviceIdAnnotation = service.getAnnotation(ServiceId::class.java)
?: throw IllegalArgumentException("只有标记@ServiceId的服务才能够被注册")
//获取serviceId
val name = serviceIdAnnotation.name
serviceMaps[name] = service
//temp array
val methods: ConcurrentHashMap<String, Method> = ConcurrentHashMap()
// 获取服务当中的全部方法
for (method in service.declaredMethods) {
//这里需要注意,因为方法中存在重载方法,所以不能把方法名当做key,需要加上参数
val buffer = StringBuffer()
buffer.append(method.name).append("(")
val params = method.parameterTypes
if (params.size > 0) {
buffer.append(params[0].name)
}
for (index in 1 until params.size) {
buffer.append(",").append(params[index].name)
}
buffer.append(")")
//保存
methods[buffer.toString()] = method
}
//存入方法表
methodsMap[service] = methods
}
companion object {
val instance by lazy { Registry() }
}
}
通过上面的register方法,当传入定义的服务class对象的时候,首先获取到服务上标记的@ServiceId注解,注意这里如果要注册必须标记,否则直接抛异常;拿到serviceId之后,存入到serviceMaps中。
然后需要获取服务中的全部方法,因为考虑到重载方法的存在,所以不能单单以方法名作为key,而是需要把参数也加上,因此这里做了一个逻辑就是将方法名与参数名组合一个key,存入到方法表中。
这样注册任务就完成了,其实还是比较简单的,关键在于完成2个表:服务表和方法表的初始化以及数据存储功能。
1.2 使用方式
@ServiceId("UserManagerService")
interface IUserManager {
fun getUserInfo(): User?
fun setUserInfo(user: User)
fun getUserId(): Int
fun setUserId(id: Int)
}
假设项目中有一个用户信息管理的服务,这个服务用于给所有的App提供用户信息查询。
@ServiceId("UserManagerService")
class UserManager : IUserManager {
private var user: User? = null
private var userId: Int = 0
override fun getUserInfo(): User? {
return user
}
override fun setUserInfo(user: User) {
this.user = user
}
override fun getUserId(): Int {
return userId
}
override fun setUserId(id: Int) {
this.userId = id
}
}
用户中心可以注册这个服务,并且调用setUserInfo方法保存用户信息,那么其他App(客户端)连接这个服务之后,就可以调用getUserInfo这个方法,获取用户信息,从而完成进程间通信。
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: entrySet key class com.lay.learn.asm.binder.UserManager
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue key setUserInfo(com.lay.learn.asm.binder.User)
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue value public void com.lay.learn.asm.binder.UserManager.setUserInfo(com.lay.learn.asm.binder.User)
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue key getUserInfo()
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue value public com.lay.learn.asm.binder.User com.lay.learn.asm.binder.UserManager.getUserInfo()
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue key getUserId()
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue value public int com.lay.learn.asm.binder.UserManager.getUserId()
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue key setUserId(int)
2023-01-23 22:15:54.729 13361-13361/com.lay.learn.asm E/TAG: mapValue value public void com.lay.learn.asm.binder.UserManager.setUserId(int)
我们看调用register方法之后,每个方法的key值都是跟参数绑定在一起,这样服务端注册就完成了。
2 客户端与服务端的通信协议
对于客户端的连接,其实就是绑定服务,那么这里就会使用到AIDL通信,但是跟传统的相比,我们是将AIDL封装到框架层内部,对于用户来说是无感知的。
2.1 创建IPCService
这个服务就是用来完成进程间通信的,客户端需要与这个服务建立连接,通过服务端分发消息,或者接收客户端发送来的消息。
abstract class IPCService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
这里我定义了一个抽象的Service基类,为啥要这么做,前面我们提到过是因为整个项目中不可能只有一个服务,因为业务众多,为了保证单一职责,需要划分不同的类型,所以在框架中会衍生多个实现类,不同业务方可以注册这些服务,当然也可以自定义服务继承IPCService。
class IPCService01 : IPCService() {
}
在IPCService的onBind需要返回一个Binder对象,因此需要创建aidl文件。
2.2 定义通讯协议
像我们在请求接口的时候,通常也是向服务端发起一个请求(Request),然后得到服务端的一个响应(Response),因此在IPC通信的的时候,也可以根据这种方式建立通信协议。
data class Request(
val type: Int,
val serviceId: String?,
val methodName: String?,
val params: Array<Parameters>?
) : Parcelable {
//=====================================
/**请求类型*/
//获取实例的对象
val GET_INSTANCE = "getInstance"
//执行方法
val INVOKE_METHOD = "invokeMethod"
//=======================================
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString(),
parcel.readString(),
parcel.createTypedArray(Parameters.CREATOR)
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(type)
parcel.writeString(serviceId)
parcel.writeString(methodName)
}
override fun describeContents(): Int {
return 0
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Request
if (type != other.type) return false
if (serviceId != other.serviceId) return false
if (methodName != other.methodName) return false
if (params != null) {
if (other.params == null) return false
if (!params.contentEquals(other.params)) return false
} else if (other.params != null) return false
return true
}
override fun hashCode(): Int {
var result = type
result = 31 * result + (serviceId?.hashCode() ?: 0)
result = 31 * result + (methodName?.hashCode() ?: 0)
result = 31 * result + (params?.contentHashCode() ?: 0)
return result
}
companion object CREATOR : Parcelable.Creator<Request> {
override fun createFromParcel(parcel: Parcel): Request {
return Request(parcel)
}
override fun newArray(size: Int): Array<Request?> {
return arrayOfNulls(size)
}
}
}
对于客户端来说,致力于发起请求,请求实体类Request参数介绍如下:
type表示请求的类型,包括两种分别是:执行静态方法和执行普通方法(考虑到反射传参);
serviceId表示请求的服务id,要请求哪个服务,便可以获取到这个服务的实例对象,调用服务中提供的方法;
methodName表示要请求的方法名,也是在serviceId服务中定义的方法;
params表示请求的方法参数集合,我们在服务端注册的时候,方法名 + 参数名 作为key,因此需要知道请求的方法参数,以便获取到Method对象。
data class Response(
val value:String?,
val result:Boolean
):Parcelable {
@SuppressLint("NewApi")
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readBoolean()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(value)
parcel.writeByte(if (result) 1 else 0)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Response> {
override fun createFromParcel(parcel: Parcel): Response {
return Response(parcel)
}
override fun newArray(size: Int): Array<Response?> {
return arrayOfNulls(size)
}
}
}
对于服务端来说,在接收到请求之后,需要针对具体的请求返回相应的结果,Response实体类参数介绍:
result表示请求成功或者失败;
value表示服务端返回的结果,是一个json字符串。
因此定义aidl接口文件如下,输入一个请求之后,返回一个服务端的响应。
interface IIPCServiceInterface {
Response send(in Request request);
}
这样IPCService就可以将aidl生成的Stub类作为Binder对象返回。
abstract class IPCService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return BINDERS
}
companion object BINDERS : IIPCServiceInterface.Stub() {
override fun send(request: Request?): Response? {
when(request?.type){
REQUEST.GET_INSTANCE.ordinal->{
}
REQUEST.INVOKE_METHOD.ordinal->{
}
}
return null
}
}
}
续:Android进阶宝典 -- 告别繁琐的AIDL吧,手写IPC通信框架,5行代码实现进程间通信(下)
作者:layz4android
来源:juejin.cn/post/7192465342159912997