Hermes跨進程通訊代碼封裝(仿寫)

前言

看了lance老師的視頻教程后闸翅,自己模仿寫的Hermes的封裝霎烙,代碼上可能跟老師的有點不太一樣撬讽,但是思路是一致的。

效果

ezgif-1-2d5f50fed0.gif

核心思想

  1. aidl跨進程通訊
    android中跨進程通訊悬垃,自然而然的肯定是使用了aidl,但是aidl使用起來游昼,相對會比較麻煩,因為每次都要自己寫一個aidl文件尝蠕,然后創(chuàng)建service烘豌,在service中返回binder進行通訊。

  2. 動態(tài)代理
    在c端可以提供一個接口看彼,然后創(chuàng)建動態(tài)代理廊佩,每次調(diào)用方法時,獲取到方法名靖榕,然后通過aidl跨進程調(diào)用标锄,在s端,再通過方法名和classid反射調(diào)用對應(yīng)的方法茁计。

  3. 注釋
    視頻中料皇,老師通過注釋,為了來讓給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军洼。

  1. 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)建與主進程的連接巩螃。

  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
            //先直接調(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女阀。

  1. 如何通過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)的對象了稚配。

  1. 動態(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類型。然后返回臊岸。

難點分析

  1. 不熟悉aidl的可以先借此機會練手橙数,先直接使用aidl進行通訊,然后實現(xiàn)了再來進行封裝帅戒,這樣會比較容易灯帮。

  2. 這里比較容易搞混的是動態(tài)代理和跨進程中binder里面的處理。首先我們要理解逻住,MM進程中首先調(diào)用的是代理對象钟哥,所以一開始進的,是代理對象的InvocationHandler瞎访,然后在InvocationHandler里面再利用AIDL去跨進程調(diào)用腻贰,然后才會運行到HermesService里面的方法,然后執(zhí)行完成之后返回 Response又回到了InvocationHandler里面了扒秸。這個順序需要搞清楚播演。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市伴奥,隨后出現(xiàn)的幾起案子写烤,更是在濱河造成了極大的恐慌,老刑警劉巖渔伯,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件顶霞,死亡現(xiàn)場離奇詭異,居然都是意外死亡锣吼,警方通過查閱死者的電腦和手機选浑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玄叠,“玉大人古徒,你說我怎么就攤上這事《潦眩” “怎么了隧膘?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長寺惫。 經(jīng)常有香客問我疹吃,道長,這世上最難降的妖魔是什么西雀? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任萨驶,我火速辦了婚禮,結(jié)果婚禮上艇肴,老公的妹妹穿的比我還像新娘腔呜。我一直安慰自己叁温,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布核畴。 她就那樣靜靜地躺著膝但,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谤草。 梳的紋絲不亂的頭發(fā)上跟束,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音丑孩,去河邊找鬼泳炉。 笑死,一個胖子當(dāng)著我的面吹牛嚎杨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播氧腰,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼枫浙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了古拴?” 一聲冷哼從身側(cè)響起箩帚,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎黄痪,沒想到半個月后紧帕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡桅打,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年是嗜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挺尾。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡鹅搪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出遭铺,到底是詐尸還是另有隱情丽柿,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布魂挂,位于F島的核電站甫题,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏涂召。R本人自食惡果不足惜坠非,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芹扭。 院中可真熱鬧麻顶,春花似錦赦抖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至矫钓,卻和暖如春要尔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背新娜。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工赵辕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人概龄。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓还惠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親私杜。 傳聞我的和親對象是個殘疾皇子蚕键,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容