最近想将路由框架加入跨进程的方法调用能力。首先想到的就是AIDL,一般所定义的AIDL接口,通常一个接口下的所有方法都是同时支持跨进程的。可是我想要这样的一种接口,可以同时拥有支持跨进程和不需要跨进程的方法:

public interface IProfileService {

    @Process(name = "main")
    String getName();

    String getPhone();

}

单纯的AIDL接口或者单纯的Java接口肯定都是解决不了的。那么将两者结合呢?

声明接口

声明一个AIDL接口IProfileInterface.aidl

interface IProfileInterface {
    String getName();
}

实现类ProfileService 同时实现IProfileServiceIProfileInterface.Stub

class ProfileService : IProfileService, IProfileInterface.Stub() {
    ...
}

但是这样肯定还是不行的,AIDL的产物除了有个IProfileInterface.Stub,还有个IProfileInterface.Stub.Proxy,发生IPC的时候会拿到Proxy实例,而这个Proxy是没有getPhone的,直接调用,肯定会crash。

那怎么办呢,一般路由通过查表拿到接口实现,返回给调用方,那我们可以在这里做文章,不直接提供接口实现,而是返回一个代理,代理发生调用的时候判断一下方法是否打上了@Process注解,有则走AIDL的Proxy类,没有则直接调用service的实现。同时,因为我们的路由框架是不知道具体service是什么,所以还需要让service提供对应的IBinder以及对IProfileInterface.Stub.asInterface做个包装,做一个所有service接口的父接口ISerivce,并让IProfileService继承ISerivce

public interface IService {

    IBinder getBinder();

    IInterface getInterface(IBinder binder);
}

public interface IProfileService extends IService { ... }

实现接口

然后在实现类ProfileService实现所有方法:

class ProfileService : IProfileService, IProfileInterface.Stub() {

    override fun getName(): String {
        Log.d("ProfileService", "getName at process:${Process.myPid()} at Thread:${Thread.currentThread()}")
        return "Enzo Wei"
    }

    override fun getPhone(): String {
        Log.d("ProfileService", "getPhone at process:${Process.myPid()} at Thread:${Thread.currentThread()}")
        return "010-123456"
    }

    
    override fun getBinder(): IBinder {
        return this
    }

    override fun getInterface(binder: IBinder?): IInterface {
        return asInterface(binder)
    }
}

框架实现

在路由框架中提供IBinder和实现代理:

    fun attach(binderProvider: IBinderProvider) {
            binderProvider.attach { clazz ->
                serviceMap[clazz]!!.binder
            }
    }
    
    private fun <T : IService> generateProxy(clazz: Class<T>, service: T): T {
        return Proxy.newProxyInstance(clazz.classLoader, arrayOf(clazz)) { proxy, method, args ->
            val binderAnnotation = method.getAnnotation(Process::class.java)
            if (binderAnnotation != null) {
                if (processList.any { processName -> processName == binderAnnotation.name }) {
                    val binderProxy = proxyMap[clazz] ?: getProxy(binderAnnotation.name, service)

                    val binderMethod = binderProxy!!.javaClass.getMethod(method.name, *method.parameterTypes)
                    invokeMethod(binderProxy!!, binderMethod, args)
                } else {
                    invokeMethod(service, method, args)
                }
            } else {
                invokeMethod(service, method, args)
            }
        } as T
    }
    
    private fun getProxy(process: String, service: IService): Any? {
        val bundle = GlobalContext.app.contentResolver.call(
            Uri.parse("content://process_dispatcher_${process}"),
            service.javaClass.name,
            null,
            null
        )

        return service.getInterface(bundle?.getBinder("binder"))
    }

    private fun invokeMethod(obj: Any, method: Method, args: Array<Any>?): Any? {
        return if (args.isNullOrEmpty()) {
            method.invoke(obj)
        } else {
            method.invoke(obj, *args)
        }
    }

ContentProvider.call跨进程

这里使用了ContentProvider.call方法来启动线程、拿到binder,这个过程是同步的,写法也简单很多

MainBinderProvider的实现(其实是ContentProvider的子类):

class MainBinderProvider : AbstractBinderProvider() {

    override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {

        val binder = getBinder(IProfileService::class.java)

        return Bundle().apply {
            putBinder("binder", binder)
        }
    }
}

Demo

最后是,Demo提供了两个进程main和other,以及两个service:IProfileServiceISayHelloService

public interface IProfileService {

    @Process(name = "main")
    String getName();

    String getPhone();

}

public interface ISayHelloService extends IService {
    @Process(name = "other")
    void hello();

    String helloWorld(String name);
}

其中IProfileService.getName指定运行在主进程,而IProfileService.getPhone未指定进程; ISayHelloService. hello指定运行在other进程,而ISayHelloService.helloWorld未指定进程;

另外ModuleBActivity运行在other进程,在ModuleBActivity中调用上面四个方法,看看结果:

从Demo的结果可以看到,达到我们最初的目的:在同一个接口下,可以按方法级部署于不同的进程。

Demo地址:https://github.com/enzowyf/ipc_router_demo/tree/router_and_contentprovider_with_aidl