Hermes跨进程通讯代码封装(仿写)

前言

看了lance老师的视频教程后,自己模仿写的Hermes的封装,代码上可能跟老师的有点不太一样,但是思路是一致的。

效果

《Hermes跨进程通讯代码封装(仿写)》 ezgif-1-2d5f50fed0.gif

核心思想

  1. aidl跨进程通讯
    android中跨进程通讯,自然而然的肯定是使用了aidl,但是aidl使用起来,相对会比较麻烦,因为每次都要自己写一个aidl文件,然后创建service,在service中返回binder进行通讯。

  2. 动态代理
    在c端可以提供一个接口,然后创建动态代理,每次调用方法时,获取到方法名,然后通过aidl跨进程调用,在s端,再通过方法名和classid反射调用对应的方法。

  3. 注释
    视频中,老师通过注释,为了来让给c端和s端操作的类保持一致,这里我也采用相同方式,当然也可以简单操作,直接使用类名,我认为也不是什么问题。

     注意:这里操作的跨进程对象是单例,和视频中保持一致
    

代码解析

1.主进程注册类

class MainActivity : AppCompatActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
       Hermes.register(UserManager::class.java)  
   }
}

class Hermes{
      ...
      fun register(cls:Class<*>){
            //保存2个map, clsid-cls,cls-method
            cls.declaredAnnotations.forEach { annotation ->
                if (annotation is ClassId){
                    clsIdMap.put(annotation.id,cls)
                    clsMethodMap.put(cls,cls.declaredMethods)
                }
            }
        }
      ...
}

做一些预处理工作,获取到这个class的 classid 还有对应的一些method。

  1. MM进程连接
class MMActivity:AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        Hermes.connect(this)
        ...
      }
}
class Hermes{
  ...
        fun connect(context:Context){
            //创建客服端与服务端的连接
            context.bindService(Intent(context,HermesService::class.java), conn,Context.BIND_AUTO_CREATE)
        }
  ...
}

创建与主进程的连接。

  1. 在MM进程中,通过接口获取代理对象
class MMActivity:AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
      ...
      Handler().postDelayed({
            manager = Hermes.getInstance(IUserManager::class.java)
        },1000)
    }
}
class Hermes{
        fun <T> getInstance(cls:Class<T>):T?{
            val annotation = cls.annotations.filter { annotation -> annotation is ClassId  }.last() as ClassId
            //先直接调用 让另外一个进程创建,这样就建立了 classid-instance的一个关系,后面调用方法的时候,直接通过classid获取到instance
            iHermes!!.sendRequest(Request(TYPE_GET_INSTANCE,annotation.id,"access\$getMInstance\$cp",null))
            return Proxy.newProxyInstance(javaClass.classLoader, arrayOf(cls),HermesInvokeHandler(annotation.id)) as T
        }
}

这里需要做下延时,然后调用Hermes.getInstance,因为bindService获取binder是异步的,Hermes.connect调用后还无法及时的获取到binder。
在getInstance方法中,首先我先利用了AIDL调用了 sendRequest,发送创建单例对象的指令。然后再通过Proxy.newInstance创建IUserManager接口的代理对象返回。接下来在MM进程中调用了这个代理对象的方法都会被我封装成一个Request对象然后调用sendRequest传给主进程,然后主进程执行完方法后把返回对象序列化,返回一个Response

  1. 如何通过sendRequest方法
    首先我们只创建一个AIDL,然后用这个AIDL来表示所有的请求,可以理解为就像Http请求一样,把请求数据封装在一个对象中,也就是Request,然后服务器接收到了这个 Request做好了处理,需要返回一些内容,比如Http请求中的一些返回值。这里使用Response封装了所有的返回内容。

下面是IHermes.aidl,只需要定义一个方法。

package com.javalong.hermes;
import com.javalong.hermes.Request;
import com.javalong.hermes.Response;
interface IHermes {
     Response sendRequest(in Request request);
}

//Service
class HermesService : Service() {
    val instanceMap = ConcurrentHashMap<String,Any>()
    override fun onBind(p0: Intent?): IBinder {
        return object : IHermes.Stub() {
            override fun sendRequest(request: Request): Response {
                //反射调用
                when (request.type) {
                    Hermes.TYPE_GET_INSTANCE -> {
                        val cls = Hermes.getClassById(request.classId) ?: return Response("",false,"")
                        val methods = Hermes.getMethodsByClass(cls) ?: return Response("",false,"")
                        val methodArr = methods.filter { method -> method.name==request.methodName }
                        val obj:Any
                        obj = if(request.param==null|| request.param!!.isEmpty()){
                            methodArr[0].invoke(cls)
                        }else{
                            methodArr[0].invoke(cls,request.param)
                        }
                        //实例存放起来,后面调用实例方法
                        instanceMap.put(request.classId,obj)
                        return Response(Gson().toJson(obj),true,"")
                    }
                    Hermes.TYPE_GET_METHOD -> {
                        val cls = Hermes.getClassById(request.classId) ?: return Response("",false,"")
                        val methods = Hermes.getMethodsByClass(cls) ?: return Response("",false,"")
                        val methodArr = methods.filter { method -> method.name==request.methodName }
                        val instance = instanceMap.get(request.classId)
                        val obj:Any?
                        obj = if(!(request.param!=null&&!request.param!!.isEmpty())){
                            methodArr[0].invoke(instance)
                        }else{
                            methodArr[0].invoke(instance, *request.param!!)
                        }
                        if(obj==null){
                            return Response("",true,"")
                        }
                        return Response(Gson().toJson(obj),true,"")
                    }
                }
                return Response("",false,"")
            }
        }
    }
}

这里给Request分为了2中,一种是getInstance来创建单例,然后把classid-instance对应关联起来。
第二种是调用对象的method方法,先通过classid获取到刚才创建的对象,然后通过反射方法调用,方法调用后获取到返回值,直接封装成Response对象。

所以前面我在返回代理对象前,先调用了第一种方式创建一个单例,不然在后面通过classid就找不到对应的对象了。

  1. 动态代理handler截取请求,跨进程反射调用
class HermesInvokeHandler(val clsId: String) : InvocationHandler {
  override fun invoke(obj: Any?, method: Method, params: Array<out Any>?): Any? {
      var response:Response
      //通过请求创建Request对象,然后调用binder 跨进程调用,获取返回,如果有错误,就直接返回null
      if(params==null) {
           response = Hermes.sendRequest(Request(Hermes.TYPE_GET_METHOD, clsId, method.name,null))
      }else{
           response = Hermes.sendRequest(Request(Hermes.TYPE_GET_METHOD, clsId, method.name,params))
      }

      if (response.source.isNotEmpty()) {
          return Gson().fromJson(response.source, method.returnType)
      }
      return null
  }
}

调用成功后,把Response中的source字符串再转成returnType类型。然后返回。

难点分析

  1. 不熟悉aidl的可以先借此机会练手,先直接使用aidl进行通讯,然后实现了再来进行封装,这样会比较容易。

  2. 这里比较容易搞混的是动态代理和跨进程中binder里面的处理。首先我们要理解,MM进程中首先调用的是代理对象,所以一开始进的,是代理对象的InvocationHandler,然后在InvocationHandler里面再利用AIDL去跨进程调用,然后才会运行到HermesService里面的方法,然后执行完成之后返回 Response又回到了InvocationHandler里面了。这个顺序需要搞清楚。

    原文作者:javalong
    原文地址: https://www.jianshu.com/p/40f5b924a041
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞