关于进程间通讯,许多项目中或许根本没有涉及到多进程,许多公司的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
        }
    }
}

2.3 内部通讯协议完善

当客户端建议恳求,想要履行某个办法的时分,首要服务端会先向Registery中查询注册的服务,然后找到这个要履行的办法,这个流程是在内部完结。

override fun send(request: Request?): Response? {
    //获取服务目标id
    val serviceId = request?.serviceId
    val methodName = request?.methodName
    val params = request?.params
    // 反序列化拿到详细的参数类型
    val neededParams = parseParameters(params)
    val method = Registry.instance.findMethod(serviceId, methodName, neededParams)
    Log.e("TAG", "method $method")
    Log.e("TAG", "neededParams $neededParams")
    when (request?.type) {
        REQUEST_TYPE.GET_INSTANCE.ordinal -> {
            //==========履行静态办法
            try {
                var instance: Any? = null
                instance = if (neededParams == null || neededParams.isEmpty()) {
                    method?.invoke(null)
                } else {
                    method?.invoke(null, neededParams)
                }
                if (instance == null) {
                    return Response("instance == null", -101)
                }
                //存储实例目标
                Registry.instance.setServiceInstance(serviceId ?: "", instance)
                return Response(null, 200)
            } catch (e: Exception) {
                return Response("${e.message}", -102)
            }
        }
        REQUEST_TYPE.INVOKE_METHOD.ordinal -> {
            //==============履行一般办法
            val instance = Registry.instance.getServiceInstance(serviceId)
            if (instance == null) {
                return Response("instance == null ", -103)
            }
            //办法履行回来的成果
            return try {
                val result = if (neededParams == null || neededParams.isEmpty()) {
                    method?.invoke(instance)
                } else {
                    method?.invoke(instance, neededParams)
                }
                Response(gson.toJson(result), 200)
            } catch (e: Exception) {
                Response("${e.message}", -104)
            }
        }
    }
    return null
}

当客户端建议恳求时,会将恳求的参数封装到Request中,在服务端接纳到恳求后,就会解析这些参数,变成Method履行时需求传入的参数。

private fun parseParameters(params: Array<Parameters>?): Array<Any?>? {
    if (params == null || params.isEmpty()) {
        return null
    }
    val objects = arrayOfNulls<Any>(params.size)
    params.forEachIndexed { index, parameters ->
        objects[index] =
            gson.fromJson(parameters.value, Class.forName(parameters.className))
    }
    return objects
}

例如用户中心调用setUserInfo办法时,需求传入一个User实体类,如下所示:

UserManager().setUserInfo(User("ming",25))

那么在调用这个办法的时分,首要会把这个实体类转成一个JSON字符串,例如:

{
    "name":"ming",
    "age":25
}

为啥要”多此一举“呢?其实这种处理办法是最快速直接的,转成json字符串之后,可以最大限度地下降数据传输的巨细,比及服务端处理这个办法的时分,再把Request中的params反json转成User目标即可。

fun findMethod(serviceId: String?, methodName: String?, neededParams: Array<Any?>?): Method? {
    //获取服务
    val serviceClazz = serviceMaps[serviceId] ?: return null
    //获取办法调集
    val methods = methodsMap[serviceClazz] ?: return null
    return methods[rebuildParamsFunc(methodName, neededParams)]
}
private fun rebuildParamsFunc(methodName: String?, params: Array<Any?>?): String {
    val stringBuffer = StringBuffer()
    stringBuffer.append(methodName).append("(")
    if (params == null || params.isEmpty()) {
        stringBuffer.append(")")
        return stringBuffer.toString()
    }
    stringBuffer.append(params[0]?.javaClass?.name)
    for (index in 1 until params.size) {
        stringBuffer.append(",").append(params[index]?.javaClass?.name)
    }
    stringBuffer.append(")")
    return stringBuffer.toString()
}

那么在查找注册办法的时分就简单多了,直接抽丝剥茧一层一层取到最终的Method。在拿到Method之后,这儿是有2种处理办法,一种是经过静态单例的办法拿到实例目标,并保存在服务端;另一种便是履行一般办法,由于在反射的时分需求拿到类的实例目标才能调用,所以才在GET_INSTANCE的时分存一遍

3 客户端 – connect

在第二节中,咱们已经完结了通讯协议的建设,最终一步便是客户端经过绑定服务,向服务端建议通讯了。

3.1 bindService

/**
 * 绑定服务
 *
 */
fun connect(
    context: Context,
    pkgName: String,
    action: String = "",
    service: Class<out IPCService>
) {
    val intent = Intent()
    if (pkgName.isEmpty()) {
        //同app内的不同进程
        intent.setClass(context, service)
    } else {
        //不同APP之间进行通讯
        intent.setPackage(pkgName)
        intent.setAction(action)
    }
    //绑定服务
    context.bindService(intent, IpcServiceConnection(service), Context.BIND_AUTO_CREATE)
}
inner class IpcServiceConnection(val simpleService: Class<out IPCService>) : ServiceConnection {
    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        val mService = IIPCServiceInterface.Stub.asInterface(service) as IIPCServiceInterface
        binders[simpleService] = mService
    }
    override fun onServiceDisconnected(name: ComponentName?) {
        //断连之后,直接移除即可
        binders.remove(simpleService)
    }
}

关于绑定服务这块,信任同伴们也很熟悉了,这个需求说一点的便是,在Android 5.0今后,启动服务不能只依靠action启动,还需求指定运用包名,否则就会报错。

在服务衔接成功之后,即回调onServiceConnected办法的时分,需求拿到服务端的一个署理目标,即IIPCServiceInterface的实例目标,然后存储在binders调集中,key为绑定的服务类class目标,value便是对应的服务端的署理目标。

fun send(
    type: Int,
    service: Class<out IPCService>,
    serviceId: String,
    methodName: String,
    params: Array<Parameters>
): Response? {
    //创立恳求
    val request = Request(type, serviceId, methodName, params)
    //建议恳求
    return try {
        binders[service]?.send(request)
    } catch (e: Exception) {
        null
    }
}

当拿到服务端的署理目标之后,就可以在客户端调用send办法向服务端发送音讯。

class Channel {
    //====================================
    /**每个服务对应的Binder目标*/
    private val binders: ConcurrentHashMap<Class<out IPCService>, IIPCServiceInterface> by lazy {
        ConcurrentHashMap()
    }
    //====================================
    /**
     * 绑定服务
     *
     */
    fun connect(
        context: Context,
        pkgName: String,
        action: String = "",
        service: Class<out IPCService>
    ) {
        val intent = Intent()
        if (pkgName.isEmpty()) {
            intent.setClass(context, service)
        } else {
            intent.setPackage(pkgName)
            intent.setAction(action)
            intent.setClass(context, service)
        }
        //绑定服务
        context.bindService(intent, IpcServiceConnection(service), Context.BIND_AUTO_CREATE)
    }
    inner class IpcServiceConnection(val simpleService: Class<out IPCService>) : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            val mService = IIPCServiceInterface.Stub.asInterface(service) as IIPCServiceInterface
            binders[simpleService] = mService
        }
        override fun onServiceDisconnected(name: ComponentName?) {
            //断连之后,直接移除即可
            binders.remove(simpleService)
        }
    }
    fun send(
        type: Int,
        service: Class<out IPCService>,
        serviceId: String,
        methodName: String,
        params: Array<Parameters>
    ): Response? {
        //创立恳求
        val request = Request(type, serviceId, methodName, params)
        //建议恳求
        return try {
            binders[service]?.send(request)
        } catch (e: Exception) {
            null
        }
    }
    companion object {
        private val instance by lazy {
            Channel()
        }
        /**
         * 获取单例目标
         */
        fun getDefault(): Channel {
            return instance
        }
    }
}

3.2 动态署理获取接口实例

回到1.2末节中,咱们界说了一个IUserManager接口,经过前面咱们界说的通讯协议,只需咱们获取了IUserManager的实例目标,那么就可以调用其间的恣意一般办法,所以在客户端需求设置一个获取接口实例目标的办法。

fun <T> getInstanceWithName(
    service: Class<out IPCService>,
    classType: Class<T>,
    clazz: Class<*>,
    methodName: String,
    params: Array<Parameters>
): T? {
    //获取serviceId
    val serviceId = clazz.getAnnotation(ServiceId::class.java)
    val response = Channel.getDefault()
        .send(REQUEST.GET_INSTANCE.ordinal, service, serviceId.name, methodName, params)
    Log.e("TAG", "response $response")
    if (response != null && response.result) {
        //恳求成功,回来接口实例目标
        return Proxy.newProxyInstance(
            classType.classLoader,
            arrayOf(classType),
            IPCInvocationHandler()
        ) as T
    }
    return null
}

当咱们经过客户端发送一个获取单例的恳求后,假如成功了,那么就直接回来这个接口的单例目标,这儿直接运用动态署理的办法回来一个接口实例目标,那么后续履行这个接口的办法时,会直接走到IPCInvocationHandler的invoke办法中。

class IPCInvocationHandler(
    val service: Class<out IPCService>,
    val serviceId: String?
) : InvocationHandler {
    private val gson = Gson()
    override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
        //履行客户端发送办法恳求
        val response = Channel.getDefault()
            .send(
                REQUEST.INVOKE_METHOD.ordinal,
                service,
                serviceId,
                method?.name ?: "",
                args
            )
        //拿到服务端回来的成果
        if (response != null && response.result) {
            //反序列化得到成果
            return gson.fromJson(response.value, method?.returnType)
        }
        return null
    }
}

由于服务端在拿到Method的回来成果时,将javabean转换为了json字符串,因而在IPCInvocationHandler中,当调用接口中办法获取成果之后,用Gson将json转换为javabean目标,那么就直接获取到了成果。

3.3 结构运用

服务端:

UserManager2.getDefault().setUserInfo(User("ming", 25))
IPC.register(UserManager2::class.java)

同时在服务端需求注册一个IPCService的实例,这儿用的是IPCService01

<service
    android:name=".UserService"
    android:enabled="true"
    android:exported="true" />
<service
    android:name="com.lay.ipc.service.IPCService01"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.GET_USER_INFO" />
    </intent-filter>
</service>

客户端:

调用connect办法,需求绑定服务端的服务,传入包名和action

IPC.connect(
    this,
    "com.lay.learn.asm",
    "android.intent.action.GET_USER_INFO",
    IPCService01::class.java
)

首要获取IUserManager的实例,留意这儿要和服务端注册的UserManager2是同一个ServiceId,而且接口、javabean需求存放在与服务端一样的文件夹下

val userManager = IPC.getInstanceWithName(
    IPCService01::class.java,
    IUserManager::class.java,
    "getDefault",
    null
)
val info = userManager?.getUserInfo()

经过动态署理拿到接口的实例目标,只需调用接口中的办法,就会进入到InvocationHandler中的invoke办法,在这个办法中,经过查找服务端注册的办法名然后找到对应的Method,经过反射调用拿到UserManager中的办法回来值。

这样其实就经过5-6行代码,就完结了进程间通讯,是不是比咱们在运用AIDL的时分要方便地许多。

4 总结

假如咱们面对下面这个类,假如这个类是个私有类,外部没法调用,想经过反射的办法调用其间某个办法。

@ServiceId(name = "UserManagerService")
public class UserManager2 implements IUserManager {
    private static UserManager2 userManager2 = new UserManager2();
    public static UserManager2 getDefault() {
        return userManager2;
    }
    private User user;
    @Nullable
    @Override
    public User getUserInfo() {
        return user;
    }
    @Override
    public void setUserInfo(@NonNull User user) {
        this.user = user;
    }
    @Override
    public int getUserId() {
        return 0;
    }
    @Override
    public void setUserId(int id) {
    }
}

那么咱们可以这样做:

val method = UserManager2::class.java.getDeclaredMethod("getUserInfo")
method.isAccessible = true
method.invoke(this,params)

其实这个结构的原理便是上面这几行代码所可以完结的事;经过服务端注册的办法,将UserManager2中一切的办法Method收集起来;当另一个进程,也便是客户端想要调用其间某个办法的时分,经过办法名来获取到对应的Method,调用这个办法得到最终的回来值