前言
看了lance老師的視頻教程后闸翅,自己模仿寫的Hermes的封裝霎烙,代碼上可能跟老師的有點不太一樣撬讽,但是思路是一致的。
效果
核心思想
aidl跨進程通訊
android中跨進程通訊悬垃,自然而然的肯定是使用了aidl,但是aidl使用起來游昼,相對會比較麻煩,因為每次都要自己寫一個aidl文件尝蠕,然后創(chuàng)建service烘豌,在service中返回binder進行通訊。動態(tài)代理
在c端可以提供一個接口看彼,然后創(chuàng)建動態(tài)代理廊佩,每次調(diào)用方法時,獲取到方法名靖榕,然后通過aidl跨進程調(diào)用标锄,在s端,再通過方法名和classid反射調(diào)用對應(yīng)的方法茁计。-
注釋
視頻中料皇,老師通過注釋,為了來讓給c端和s端操作的類保持一致星压,這里我也采用相同方式践剂,當(dāng)然也可以簡單操作,直接使用類名娜膘,我認為也不是什么問題逊脯。注意:這里操作的跨進程對象是單例,和視頻中保持一致
代碼解析
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)
}
}
}
...
}
做一些預(yù)處理工作竣贪,獲取到這個class的 classid 還有對應(yīng)的一些method军洼。
- MM進程連接
class MMActivity:AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Hermes.connect(this)
...
}
}
class Hermes{
...
fun connect(context:Context){
//創(chuàng)建客服端與服務(wù)端的連接
context.bindService(Intent(context,HermesService::class.java), conn,Context.BIND_AUTO_CREATE)
}
...
}
創(chuàng)建與主進程的連接巩螃。
- 在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
//先直接調(diào)用 讓另外一個進程創(chuàng)建,這樣就建立了 classid-instance的一個關(guān)系匕争,后面調(diào)用方法的時候牺六,直接通過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
}
}
這里需要做下延時,然后調(diào)用Hermes.getInstance汗捡,因為bindService獲取binder是異步的,Hermes.connect調(diào)用后還無法及時的獲取到binder畏纲。
在getInstance方法中扇住,首先我先利用了AIDL調(diào)用了 sendRequest
,發(fā)送創(chuàng)建單例對象的指令盗胀。然后再通過Proxy.newInstance創(chuàng)建IUserManager接口的代理對象返回艘蹋。接下來在MM進程中調(diào)用了這個代理對象的方法都會被我封裝成一個Request
對象然后調(diào)用sendRequest
傳給主進程,然后主進程執(zhí)行完方法后把返回對象序列化票灰,返回一個Response
女阀。
- 如何通過sendRequest方法
首先我們只創(chuàng)建一個AIDL,然后用這個AIDL來表示所有的請求,可以理解為就像Http
請求一樣屑迂,把請求數(shù)據(jù)封裝在一個對象中浸策,也就是Request,然后服務(wù)器接收到了這個Request
做好了處理,需要返回一些內(nèi)容惹盼,比如Http
請求中的一些返回值庸汗。這里使用Response
封裝了所有的返回內(nèi)容。
下面是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 {
//反射調(diào)用
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)
}
//實例存放起來蚯舱,后面調(diào)用實例方法
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來創(chuàng)建單例掩蛤,然后把classid-instance對應(yīng)關(guān)聯(lián)起來枉昏。
第二種是調(diào)用對象的method方法,先通過classid獲取到剛才創(chuàng)建的對象揍鸟,然后通過反射方法調(diào)用兄裂,方法調(diào)用后獲取到返回值,直接封裝成Response
對象蜈亩。
所以前面我在返回代理對象前懦窘,先調(diào)用了第一種方式創(chuàng)建一個單例,不然在后面通過classid就找不到對應(yīng)的對象了稚配。
- 動態(tài)代理handler截取請求畅涂,跨進程反射調(diào)用
class HermesInvokeHandler(val clsId: String) : InvocationHandler {
override fun invoke(obj: Any?, method: Method, params: Array<out Any>?): Any? {
var response:Response
//通過請求創(chuàng)建Request對象,然后調(diào)用binder 跨進程調(diào)用道川,獲取返回,如果有錯誤午衰,就直接返回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
}
}
調(diào)用成功后立宜,把Response
中的source
字符串再轉(zhuǎn)成returnType
類型。然后返回臊岸。
難點分析
不熟悉aidl的可以先借此機會練手橙数,先直接使用aidl進行通訊,然后實現(xiàn)了再來進行封裝帅戒,這樣會比較容易灯帮。
這里比較容易搞混的是動態(tài)代理和跨進程中binder里面的處理。首先我們要理解逻住,MM進程中首先調(diào)用的是代理對象钟哥,所以一開始進的,是代理對象的InvocationHandler瞎访,然后在InvocationHandler里面再利用AIDL去跨進程調(diào)用腻贰,然后才會運行到HermesService里面的方法,然后執(zhí)行完成之后返回
Response
又回到了InvocationHandler
里面了扒秸。這個順序需要搞清楚播演。