Android插件化與熱修復(fù)(五)---DroidPlugin 四大組件的插件化

為什么出現(xiàn)插件化?

  • 技術(shù)上

由于代碼量的增加,App遇到65535問(wèn)題。

  • 業(yè)務(wù)上

模塊解耦,獨(dú)立開發(fā),獨(dú)立上線。


插件化要解決的問(wèn)題

  • 代碼加載

ClassLoader機(jī)制:可以用此進(jìn)行類的加載。
組件生命周期管理:對(duì)于Android來(lái)說(shuō),組件是有生命的,所以還需要進(jìn)行此項(xiàng)任務(wù)。

  • 資源加載

加載方式:基本大同小異,使用AssetManager的隱藏方法addAssetPath。
管理方式:或者共用一套資源,采用資源分段機(jī)制解決沖突;或者獨(dú)立資源,不同插件管理自己的資源。


為什么選擇360 DroidPlugin 進(jìn)行介紹?

DroidPlugin Hook了系統(tǒng)幾乎所有的Sevice,欺騙了大部分的系統(tǒng)API。
DroidPlugin中,插件是有血有肉的系統(tǒng)管理的真正組件。
DroidPlugin通過(guò)Hook增強(qiáng)了Framework層的很多系統(tǒng)服務(wù),開發(fā)插件就跟開發(fā)獨(dú)立app差不多。


Hook機(jī)制

  • 參考上上一篇

Binder代理

  • 參考上一篇

四大組件的插件化

Activity

前言
  • 必須明確的問(wèn)題:

進(jìn)程間通信需要Binder對(duì)象。
插件化技術(shù)只能在本進(jìn)程中做手腳,由于進(jìn)程隔離的存在,我們對(duì)別的進(jìn)程無(wú)能為力。
一般的Activity都是需要在AndroidManifest.xml中顯式聲明,否則報(bào)異常。

啟動(dòng)沒(méi)有在AndroidManifest.xml中顯式聲明,但存在于自身APK中的Activity
  • Activity啟動(dòng)過(guò)程
    Activity的啟動(dòng)過(guò)程可以簡(jiǎn)要的通過(guò)下圖總結(jié),分為三步:第一步,先從App進(jìn)程啟動(dòng)Activity;第二步,通過(guò)IPC通信,進(jìn)入系統(tǒng)進(jìn)程System_server,完成生命周期,堆棧和權(quán)限管理等等;第三步,回到App進(jìn)程完成Activity對(duì)象的創(chuàng)建,并回調(diào)其生命周期方法。


  • 策略
    既然插件中的Activity無(wú)法預(yù)先在AndroidManifest.xml中顯式聲明,那么我們可以先在AndroidManifest.xml中,顯示聲明一個(gè)假的Activity;在第一步啟動(dòng)真Activity的時(shí)候,將真的Activity替換為假的Activity;然后用其來(lái)騙過(guò)系統(tǒng),讓系統(tǒng)為我們提供服務(wù)(如生命周期,權(quán)限管理等);最后在第三步再將假的Activity替換回插件中真的Activity。
    因此,hook點(diǎn)有兩個(gè):第一步和第三步。

  • 實(shí)施
    1.AndroidManifest.xml中顯示聲明假的Activity
    2.Hook AMS在本進(jìn)程的代理對(duì)象ActivityManagerNative,針對(duì)startActivity方法,將真Activity替換為假Activity
    3.Hook Handler的mCallback對(duì)象,在自定義的mCallback對(duì)象中,將假Activity還原回真Activity

  • 注意
    1.以上過(guò)程完成后,插件Activity已具有正常的生命周期。因?yàn)椋瑢?duì)于生命周期的回調(diào),AMS與App之間,并沒(méi)有使用Activity,而是使用token標(biāo)識(shí)。token標(biāo)識(shí)已經(jīng)能夠連接真假Activity。
    2.Activity的不同的啟動(dòng)模式也需要支持。
    3.由于IntentFilter的不確定性,所以不支持以IntentFilter的方式啟動(dòng)插件化中的Activity。
    4.本文只是加載自身APK中的類,但一般插件都存在于其他APK中,這種問(wèn)題如何解決?接下來(lái)就來(lái)講一下。

啟動(dòng)沒(méi)有在AndroidManifest.xml中顯示聲明,并且存在于外部插件中的Activity
  • 思路
    如何將插件的apk或者dex文件告知宿主,讓其完成插件類的加載?主要有兩種方式:Hook ClassLoader,全盤接管類加載的過(guò)程;委托系統(tǒng),告知系統(tǒng)插件相關(guān)信息,讓系統(tǒng)幫忙完成加載
  • 方式一:全盤接管
    DroidPlugin采用這種方式,Hook點(diǎn)較多,但每個(gè)插件都有自己對(duì)應(yīng)的ClassLoader,類的隔離性好,可完成代碼的熱加載。
  • 方式二:系統(tǒng)幫忙
    MutiDex采用這種方式,Hook點(diǎn)較少,簡(jiǎn)單,但插件和宿主共用宿主的ClassLoader,類的隔離性差,不可完成代碼的熱加載,可能需要重啟進(jìn)程。

BroadcastReceiver

前言
  • 必須明確的問(wèn)題:

廣播分靜態(tài)廣播和動(dòng)態(tài)廣播,兩種廣播的不同造成插件化方式也不同。靜態(tài)廣播需要預(yù)注冊(cè),注冊(cè)信息存在PMS中;動(dòng)態(tài)廣播不需要預(yù)注冊(cè),注冊(cè)信息存在AMS中;

動(dòng)態(tài)廣播
  • 思路
    不需要預(yù)注冊(cè),并且生命周期簡(jiǎn)單,只存活于onReceiver回調(diào)中。所以可自行手動(dòng)控制生命周期。

  • 實(shí)施
    1.利用ClassLoader機(jī)制,加載插件中的Receiver。從而實(shí)現(xiàn)注冊(cè)和發(fā)送。
    2.使得插件中的Receiver接收onReceiver回調(diào)。從而實(shí)現(xiàn)接收。

靜態(tài)廣播
  • 思路
    與Activity一樣,需要預(yù)注冊(cè),但卻不能與Activity的方式一樣完成插件化,因?yàn)镮ntentFilter的存在,宿主無(wú)法預(yù)知插件中具體使用的IntentFilter。
    因?yàn)閯?dòng)態(tài)廣播可以動(dòng)態(tài)添加IntentFilter,所以可將靜態(tài)廣播轉(zhuǎn)為動(dòng)態(tài)廣播,以此方式實(shí)現(xiàn)靜態(tài)廣播的插件化。

  • 實(shí)施
    1.解析插件AndroidManifest.xml,獲取receiver標(biāo)簽下面的內(nèi)容
    2.利用ClassLoader機(jī)制,加載插件中的Receiver,并把解析出來(lái)的每一個(gè)靜態(tài)Receiver都注冊(cè)為動(dòng)態(tài)的。從而實(shí)現(xiàn)注冊(cè)和發(fā)送。
    3.使得插件中的Receiver接收onReceiver回調(diào)。從而實(shí)現(xiàn)接收。

  • 注意
    因?yàn)殪o態(tài)廣播轉(zhuǎn)為動(dòng)態(tài)廣播,所以會(huì)犧牲靜態(tài)廣播的一個(gè)優(yōu)點(diǎn),即靜態(tài)廣播在進(jìn)程死亡之后是可以接收廣播的。

Service

  • Service工作原理
    Service組件的工作原理與Activity的工作原理,在流程上基本一致,都是從APP進(jìn)程到AMS,然后又從AMS回到APP進(jìn)程中,在APP主線程中完成相關(guān)生命周期的回調(diào)。
  • 策略
    但Service與Activity卻有一個(gè)比較大的區(qū)別:Activity的生命周期受到用戶行為的影響,而Service作為后臺(tái)任務(wù),用戶的操作行為則不會(huì)影響其生命周期。用戶行為是不確定的,并且只有系統(tǒng)才能感應(yīng)到,所以,我們?cè)贏ctivity插件化的時(shí)候,依然依托于系統(tǒng)對(duì)于生命周期的管理;而Service則與用戶行為無(wú)關(guān),所以生命周期完全可以由我們手動(dòng)控制,相對(duì)于Activity更簡(jiǎn)單。
    但是簡(jiǎn)單的手動(dòng)的操作生命周期也是不夠充分的,因?yàn)镾ervice還有個(gè)進(jìn)程優(yōu)先級(jí)的概念,并且優(yōu)先級(jí)相對(duì)較高,因此我們?nèi)匀恍枰粋€(gè)能夠被系統(tǒng)能夠認(rèn)可的Service組件,依托于它,來(lái)得到Service組件所應(yīng)具備的能力。
    結(jié)論是,我們可以效仿DL插件化方案,使用的代理的方式,實(shí)現(xiàn)Service的插件化。

  • 實(shí)施
    1.AndroidManifest.xml中顯示聲明假的Service,使其承載一個(gè)真正Service組件所應(yīng)具備的能力(如進(jìn)程優(yōu)先級(jí))。
    2.Hook AMS在本進(jìn)程的代理對(duì)象ActivityManagerNative,針對(duì)startService方法,將真Service替換為假Service。其他方法(stopService等)也基本一致。
    3.以上兩步與Activity的插件化基本一致,但第三步就不同了。在假Service的各個(gè)生命周期方法中(onStartCommand,onStart等),進(jìn)行任務(wù)分發(fā),執(zhí)行真Service的onStartCommond,onStart等對(duì)應(yīng)的方法。在分發(fā)的過(guò)程中,我們不得不注意以下幾個(gè)問(wèn)題:首先,為了插件中的所有類都可以被正常訪問(wèn),需要完成插件的加載;然后,在加載插件的時(shí)候,我們存儲(chǔ)了插件中所有的Service組件的信息,因此,只需要根據(jù)Intent中的Component信息就可以匹配到對(duì)應(yīng)的真Service;接著,就是創(chuàng)建真Service的Java對(duì)象,這里注意一下,由于是自己管理生命周期,所以這里我們需要給創(chuàng)建的真Service創(chuàng)建Context對(duì)象。最后,執(zhí)行任務(wù)分發(fā),直接執(zhí)行真Service對(duì)應(yīng)的生命周期方法即可。

  • 注意
    1.針對(duì)Service的多進(jìn)程模式的支持,可以聲明多個(gè)不同進(jìn)程的假Service。side effect就是同一個(gè)進(jìn)程的所有真Service都掛載在同一個(gè)假Service上,如果假Service掛掉,所應(yīng)依附于它的真Service也同時(shí)都掛掉。
    2.以上Service的實(shí)現(xiàn)方式與DL插件化方案類似。

ContentProvider

  • ContentProvider工作原理
    1.ContentProvider的核心是數(shù)據(jù)共享,提供CRUD服務(wù),可以完成跨進(jìn)程的大數(shù)據(jù)量傳輸,主要依賴于匿名共享內(nèi)存(Ashmem)機(jī)制。Binder在此只是用來(lái)傳遞文件描述符,支持跨進(jìn)程共享。另外,與其他三大組件不同,其沒(méi)有生命周期。
    2.之前闡述的其他三大組件的插件化方案是,只有通過(guò)插件系統(tǒng)啟動(dòng)的進(jìn)程,才能感知到插件中的它們,而Android系統(tǒng)卻無(wú)法真正的感受到它們,因而第三方的App都無(wú)法使用插件的它們。而我們的希望將插件的ContentProvider共享給整個(gè)Android系統(tǒng),這才是它存在的真正意義。
    3.ContentProvider在Android系統(tǒng)啟動(dòng)或者新安裝App的時(shí)候就完成了信息的注冊(cè)。
    4.ContentProvider會(huì)優(yōu)先查詢本進(jìn)程,獲取其他進(jìn)程的ContentProvider需要借助AMS。基于這一點(diǎn),如果只是想把ContentProvider提供給本進(jìn)程使用,那么插件化方案可以不需要Hook AMS。
    5.ContentProvider有個(gè)安裝的過(guò)程,這個(gè)過(guò)程可以理解為一層緩存,方便使用ContentProvider時(shí),直接拿取使用。 另外,安裝是以進(jìn)程為單位的(目標(biāo)進(jìn)程和本地進(jìn)程都安裝)。
    6.App的啟動(dòng)過(guò)程中,ContentProvider就會(huì)進(jìn)行安裝,這個(gè)安裝的時(shí)機(jī)比Application的onCreate還要早。
  • 實(shí)施
    1.與Service的插件化幾乎是相同:.Hook AMS在本進(jìn)程的代理對(duì)象ActivityManagerNative,攔截getContentProvider方法和publishContentProvider方法,從而實(shí)現(xiàn)對(duì)于插件ContentProvider的控制。
    2.另外需要注意:進(jìn)行任務(wù)分發(fā)的時(shí)候,需要制定規(guī)則,以拿到真正的插件ContentProvider信息,DroidPlugin將插件信息放在查詢參數(shù)里面。

  • 注意
    ContentProvider的使用頻度并不高,插件系統(tǒng)可以考慮不支持,或者輕量支持(只共享給本App,不共享給系統(tǒng),這樣不需要Hook AMS,實(shí)現(xiàn)更簡(jiǎn)單)。


總結(jié)

  • 四大組件不過(guò)就是一些普通Java類,但如果僅此而已,我們完全可以簡(jiǎn)單地采用Java的動(dòng)態(tài)加載技術(shù)來(lái)實(shí)現(xiàn)插件化。然后事實(shí)并非如此簡(jiǎn)單,四大組件還有一個(gè)重要特性是:其具有生命周期(ContentProvider除外),正是因?yàn)槿绱耍覀兊牟寮瘜?shí)現(xiàn)方案還需要關(guān)注如何賦予它們“生命”。這兩點(diǎn),就是我們實(shí)現(xiàn)四大組件插件化過(guò)程中,所需要重點(diǎn)解決的兩點(diǎn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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