前言
看了lance老师的视频教程后,自己模仿写的Hermes的封装,代码上可能跟老师的有点不太一样,但是思路是一致的。
效果
核心思想
aidl跨进程通讯
android中跨进程通讯,自然而然的肯定是使用了aidl,但是aidl使用起来,相对会比较麻烦,因为每次都要自己写一个aidl文件,然后创建service,在service中返回binder进行通讯。动态代理
在c端可以提供一个接口,然后创建动态代理,每次调用方法时,获取到方法名,然后通过aidl跨进程调用,在s端,再通过方法名和classid反射调用对应的方法。-
注释
视频中,老师通过注释,为了来让给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。
- 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)
}
...
}
创建与主进程的连接。
- 在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
。
- 如何通过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就找不到对应的对象了。
- 动态代理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
类型。然后返回。
难点分析
不熟悉aidl的可以先借此机会练手,先直接使用aidl进行通讯,然后实现了再来进行封装,这样会比较容易。
这里比较容易搞混的是动态代理和跨进程中binder里面的处理。首先我们要理解,MM进程中首先调用的是代理对象,所以一开始进的,是代理对象的InvocationHandler,然后在InvocationHandler里面再利用AIDL去跨进程调用,然后才会运行到HermesService里面的方法,然后执行完成之后返回
Response
又回到了InvocationHandler
里面了。这个顺序需要搞清楚。