大致目錄:
* 前言
* 外置插件
* 安裝插件
* 升級插件
* 卸載插件
* 內(nèi)置插件
* 添加內(nèi)置插件
* 刪除內(nèi)置插件
* 使用內(nèi)置插件的時機(jī)
* 內(nèi)置插件的升級
* 預(yù)加載插件
* 插件的運(yùn)行
* 安全與簽名校驗(yàn)
* 插件管理進(jìn)程
* 插件的目錄結(jié)構(gòu)
一、前言
無論是插件還是主程序,都可以對自己和其它插件做相應(yīng)的插件管理工作。但需要理解的是:不是所有的 APK 都能作為 RePlugin 的插件并安裝進(jìn)來的。必須要嚴(yán)格按照《插件接入指南》中所述完成接入,其編譯出的 APK 才能成為插件,且這個 APK 同時也可以被安裝到設(shè)備中。
二、外置插件
外置插件是指可通過“下載”、“放入SD卡”等方式來安裝并運(yùn)行的插件。以下是外置插件的管理方案:
2.1 安裝插件
要安裝一個插件,只需使用 RePlugin.install() 方法,傳遞一個 APK 路徑即可。
RePlugin.install("/sdcard/exam.apk");
注意
-
無論安裝還是升級,都會將源文件移動(而非復(fù)制)到插件的安裝路徑(如 app_p_a)上,這樣可大幅度節(jié)省安裝和升級時間,但顯然的,源文件也就會消失。
若想改變這個行為,你可以參考
RePluginConfig中的setMoveFileWhenInstalling()方法升級插件和此等同,故不再贅述
2.1.1 最佳實(shí)踐
以下為 360 手機(jī)衛(wèi)士或其它合作 App 采用的設(shè)計(jì),可供你參考:
除非是基礎(chǔ)和核心功能插件,否則請盡量減少“靜默安裝”(指的是用戶無感知的情況下,偷偷在后臺安裝)插件的情況,以減少內(nèi)部存儲空間的消耗,降低對用戶的影響。
若插件需要下載,則請覆寫
RePluginCallbacks.onPluginNotExistsForActivity()方法,并在此打開你的下載頁面并控制其邏輯下載插件前建議告知用戶其插件大小,尤其針對運(yùn)營商網(wǎng)絡(luò)的情況
有關(guān)“插件下載”的處理,以及針對插件安裝失敗原因做進(jìn)一步的操作,請閱讀《自定義您的 RePlugin》 中“在插件不存在時,提示下載”一節(jié)。
2.1.2 安裝或升級失???
安裝或升級失?。ǚ祷刂禐?Null)的原因有如下幾種:
是否開啟了“簽名校驗(yàn)”功能且簽名不在“白名單”之中?—— 通常在 Logcat 中會出現(xiàn)
“verifySignature: invalid cert: ”。如是,則請參考安全與簽名校驗(yàn)一節(jié),了解如何將簽名加白,或關(guān)閉簽名校驗(yàn)功能(默認(rèn)為關(guān)閉)是否將 replugin-host-lib 升級到 2.1.4 及以上?—— 在 2.1.3 及之前版本,若沒有填寫
“meta-data”,則可能導(dǎo)致安裝失敗,返回值為null。官方在 2.1.4 版本中已經(jīng)修復(fù)了此問題(衛(wèi)士和其它 App 的所有插件都填寫了meta-data,所以問題沒出現(xiàn))APK 安裝包是否有問題?—— 請將插件 APK 直接安裝到設(shè)備上(而非作為插件)試試。如果在設(shè)備中安裝失敗,則插件安裝也一定是失敗的。
是否沒有 SD 卡的讀寫權(quán)限?—— 如果你的插件 APK 放到了 SD 卡上,則請務(wù)必確保主程序中擁有 SD 卡權(quán)限(主程序
Manifest要聲明,且 ROM 允許),否則會出現(xiàn)權(quán)限問題,當(dāng)然,放入應(yīng)用的files目錄則不受影響。設(shè)備內(nèi)部存儲空間是否不足?—— 通常出現(xiàn)此問題時其 Logcat 會出現(xiàn)
“copyOrMoveApk: Copy/Move Failed”的警告。如是,則需要告知用戶去清理手機(jī)。
2.2 升級插件
為了簡化操作,升級插件的做法和安裝是一樣的,仍可以直接調(diào)用 RePlugin.install() 方法。
RePlugin.install("/sdcard/exam_new.apk");
注意
如果插件正在運(yùn)行,則不會立即升級,而是“緩存”起來。直到所有“正在使用插件”的進(jìn)程結(jié)束并重啟后才會生效
升級可能會占用內(nèi)部存儲空間(因?yàn)橐尫判碌?APK)
不支持“插件降級”,但可以“同版本覆蓋”(在 RePlugin 2.1.5 版本中開始支持)
出于穩(wěn)定性和實(shí)際需求考慮,
RePlugin暫時沒有計(jì)劃支持“熱修復(fù)”方案。
2.2.1 最佳實(shí)踐
以下為 360 手機(jī)衛(wèi)士或其它合作 App 采用的設(shè)計(jì),可供你參考:
大部分情況下,應(yīng)盡可能“靜默升級”,以減少對用戶的打擾
針對升級而言,可在后臺線程做一次“預(yù)加載”,提前釋放 Dex。具體做法:
PluginInfo pi = RePlugin.install("/sdcard/exam_new.apk");
if (pi != null) {
RePlugin.preload(pi);
}
-
若插件正在運(yùn)行,則會有兩種場景,需分別對待:
若是遇到嚴(yán)重問題,需要“強(qiáng)制升級”,則應(yīng)立即提示用戶,待同意后則重啟進(jìn)程
通常情況下,建議在“鎖定屏幕”后重啟進(jìn)程,讓其在后臺生效
若插件沒有運(yùn)行,則可直接升級
2.3 卸載插件
要卸載插件,則需要使用 RePlugin.uninstall() 方法。只需傳遞一個“插件名”即可。
RePlugin.uninstall("exam");
注意
如果插件正在運(yùn)行,則不會立即卸載插件,而是將卸載訴求記錄下來。直到所有“正在使用插件”的進(jìn)程結(jié)束并重啟后才會生效
由于內(nèi)置插件是捆在主程序包內(nèi)的,故無法卸載“內(nèi)置插件”(此處有待官方優(yōu)化)。
出于穩(wěn)定性和實(shí)際需求考慮,
RePlugin暫時沒有計(jì)劃支持“熱卸載”方案。
2.3.1 最佳實(shí)踐
以下為 360 手機(jī)衛(wèi)士或其它合作 App 采用的設(shè)計(jì),可供你參考:
在卸載時彈出對話框,提示用戶“是否同意卸載”
-
若插件在運(yùn)行時需要被卸載,則有兩種做法:
提示用戶“需要重新啟動應(yīng)用才能生效”
在“鎖定屏幕”后重新啟動進(jìn)程,讓其在后臺生效
若插件沒有運(yùn)行,則可以直接卸載,無需提示用戶
三、內(nèi)置插件
內(nèi)置插件是指可以“隨著主程序發(fā)版”而下發(fā)的插件,通常這個插件會放到主程序的 Assets 目錄下。
針對內(nèi)置插件而言,開發(fā)者無需調(diào)用安裝方法,由 RePlugin來“按需安裝”。
“內(nèi)置插件”是可以被“升級”的。升級后的插件等同于“外置插件”。
3.1 添加內(nèi)置插件
添加一個內(nèi)置插件是非常簡單的,甚至可以無需任何 Java 代碼。只需兩步即可:
將 APK 改名為:
[插件名].jar([ ]在實(shí)際上不需要添加)放入主程序的
assets/plugins目錄
這樣,當(dāng)編譯主程序時,RePlugin 的“動態(tài)編譯方案”會自動在 assets 目錄下生成一個名叫 “plugins-builtin.json” 文件,記錄了其內(nèi)置插件的主要信息,方便運(yùn)行時直接獲取。
必須改成 “[插件名].jar” 后,才能被 RePlugin-Host-Gradle 識別,進(jìn)而成為“內(nèi)置插件”。
[插件名] 可以是“包名”,也可以是“插件別名”。有關(guān)這方面的說明,請閱讀《插件的信息》中“插件命名”一節(jié)。
3.2 刪除內(nèi)置插件
刪除內(nèi)置插件非常簡單,直接移除相應(yīng)的 Jar 文件,其余均交給 RePlugin 來自動化完成。
注意:若用戶已使用了內(nèi)置插件,則即便用戶升級主程序,其包內(nèi)已不帶這個內(nèi)置插件,但用戶仍可繼續(xù)使用它
這樣可防止出現(xiàn)“用戶升級主程序后,發(fā)現(xiàn)內(nèi)置插件突然用不了”的情況。
3.3 使用內(nèi)置插件的時機(jī)
不同于“外置插件”需要先調(diào)用 RePlugin.install 方法后才能使用,內(nèi)置插件可無需調(diào)用此方法。而一旦插件被使用,則 RePlugin 會在觸發(fā)相應(yīng)邏輯前,為你做下列操作:
將內(nèi)置插件釋放到數(shù)據(jù)目錄下(近似于調(diào)用
install()方法)若需要加載 Dex,則還會釋放優(yōu)化后的 Dex 到數(shù)據(jù)目錄下,這可能會需要一些時間
這樣做的好處是,不會占用太多的“內(nèi)部存儲空間”,畢竟不是所有內(nèi)置插件,都一定會被用到。
3.4 內(nèi)置插件的升級
內(nèi)置插件的升級分為兩種情況:主程序隨包升級、通過 install() 方法升級
主程序隨包升級:當(dāng)用戶升級了帶“新版本內(nèi)置插件”的主程序時,則
RePlugin會在使用插件前先做升級通過
install()方法升級:若通過RePlugin.install()方法做的升級(大多為用戶從服務(wù)器上下載并更新),則RePlugin在調(diào)用install()方法時開始做升級。當(dāng)然,其規(guī)則仍遵循安裝插件的規(guī)則,例如“插件運(yùn)行時先不覆蓋”等。
值得注意的是,無論采用何種方式,均“不支持降級”,但支持“同版本覆蓋”升級,也即:
內(nèi)置插件:只要 APK 的時間戳和大小發(fā)生變化就升級,若兩者均無變化,則不會升級。(在 RePlugin 2.1.5 版本中開始支持)
外置插件:只要調(diào)用
RePlugin.install()方法即可將“內(nèi)置插件”轉(zhuǎn)化為“外置插件”。同樣的,需遵循安裝插件規(guī)則。
3.5 最佳實(shí)踐
以下為 360 手機(jī)衛(wèi)士或其它合作 App 采用的設(shè)計(jì),可供你參考:
需控制“內(nèi)置插件”的數(shù)量,因?yàn)闀加弥鞒绦?APK 的大小
-
比較適合成為“內(nèi)置插件”的有:
核心業(yè)務(wù)插件:沒有它就等于“核心功能缺失”。比如 360 手機(jī)衛(wèi)士的“首頁體檢”、“清理”插件等
基礎(chǔ)插件:各插件都需要用到,且為必須的。比如“安全 WebView”、“下載”插件等
啟動時必備插件:明確要在啟動時要用到的功能。比如 360 手機(jī)衛(wèi)士的 “Push”、“常駐服務(wù)管理”等
可將一些啟動時必須要加載的,以及經(jīng)常要用到的內(nèi)置插件做一次“預(yù)加載”。具體做法:
RePlugin.preload("exam");
四、預(yù)加載插件
什么是預(yù)加載?一言以蔽之,就是將插件的 Dex “提前做釋放”,并將 Dex 緩存到內(nèi)存中,這樣在下次啟動插件時,可無需走 dex2oat 過程,速度會快很多。
預(yù)加載不會做下列事情:
不會“啟動插件”
不會加載其
Application對象不會打開
Activity和其它組件等。
換言之,預(yù)加載的目的非常單純,就是提前釋放 Dex,僅此而已。
4.1 預(yù)加載的用法
如之前所述,預(yù)加載有兩種做法:
- 預(yù)加載當(dāng)前安裝的插件
此為絕大多數(shù)用到的場景。直接預(yù)加載當(dāng)前安裝的插件即可,如果當(dāng)前正在運(yùn)行這個插件,則調(diào)用此方法則是無效的,畢竟當(dāng)前插件已經(jīng)早就被使用過了。
可使用 RePlugin.preload(pluginName),例如:
RePlugin.preload("exam");
- 預(yù)加載新安裝的插件
此場景主要用于“后臺升級某個插件”。如果此插件“正在被使用”,則必須借助RePlugin.install()方法的返回值(新插件的信息)來做預(yù)加載。
可使用 RePlugin.preload(PluginInfo),例如:
PluginInfo pi = RePlugin.install("/sdcard/exam_new.apk");
if (pi != null) {
RePlugin.preload(pi);
}
4.2 最佳實(shí)踐
以下為 360 手機(jī)衛(wèi)士或其它合作 App 采用的設(shè)計(jì),可供你參考:
建議將
RePlugin.preload()方法的調(diào)用放到“工作線程”中進(jìn)行。由于此方法是“同步”的,所以直接在 UI 線程中調(diào)用時,可能會卡住,甚至導(dǎo)致 ANR 問題。如果正在
preload某插件,則無論在哪個進(jìn)程和線程,在過程中加載這個插件時,可能會出現(xiàn)卡頓,這和為了安全起見,做了進(jìn)程鎖有關(guān)。建議在preload做完后再打開此插件。
五、插件的運(yùn)行
插件運(yùn)行的場景有很多,包括:
打開插件的四大組件
獲取插件的
PackageInfo/Context/ClassLoader等預(yù)加載
(preload)使用插件
Binder
如果想判斷插件是否在運(yùn)行,可使用 RePlugin.isPluginRunning() 方法。
六、安全與簽名校驗(yàn)
作為一家安全公司旗下的開源項(xiàng)目,其“安全性”是作為其重點(diǎn)之一來考慮的。曾經(jīng)有幾個 App 在使用動態(tài)加載 Dex 方案(非 RePlugin)時,被爆出有可能攜帶“病毒”,經(jīng)追查發(fā)現(xiàn)是由于沒有對外來的 Dex 和 Apk 做“校驗(yàn)”導(dǎo)致。所以說,一旦不做校驗(yàn),則不排除惡意人會劫持 DNS 或網(wǎng)絡(luò),并通過網(wǎng)絡(luò)來下發(fā)惡意插件,對你的應(yīng)用造成很不好的影響。
若開啟此開關(guān),則一旦簽名校驗(yàn)失敗,則會在 Logcat 中提示
“verifySignature: invalid cert”,且install()方法返回null。此外,出于性能考慮,內(nèi)置插件無需做“簽名校驗(yàn)”,僅“外置插件”會做。
要打開簽名校驗(yàn)也是非常簡單的。只需兩步:
第一步:打開開關(guān)
例如,若你繼承 RePluginApplication,則請?jiān)趧?chuàng)建 RePluginConfig 時調(diào)用其 setVerifySign(true) 即可。
當(dāng)然,更推薦的做法是傳遞 !BuildConfig.DEBUG 參數(shù)。這表示:若為 Debug 環(huán)境下則無需校驗(yàn)簽名,只有 Release 才會校驗(yàn)。以下是具體用法:
@Override
protected RePluginConfig createConfig() {
RePluginConfig c = new RePluginConfig();
c.setVerifySign(!BuildConfig.DEBUG);
...
return c;
}
如果你是“非繼承式”,則需要在調(diào)用 RePlugin.App.attachBaseContext() 的地方,傳遞RePluginConfig,并設(shè)置 setVerifySign 即可。以下是具體用法:
RePluginConfig c = new RePluginConfig();
c.setVerifySign(!BuildConfig.DEBUG);
...
RePlugin.App.attachBaseContext(context, c);
自 RePlugin 2.1.4 版本開始,默認(rèn)將“關(guān)閉”簽名校驗(yàn),之前默認(rèn)為“開啟”。
第二步:加入合法簽名
光是打開其開關(guān)還是不夠的,還應(yīng)該將“合法的簽名”加入到 RePlugin 的“白名單”中,可調(diào)用 RePlugin.addCertSignature() 來完成。例如:
// Add signature to "White List"
RePlugin.addCertSignature("379C790B7B726B51AC58E8FCBCFEB586");
其中,其參數(shù)傳遞的是簽名證書的 MD5,且去掉“:”’。
請務(wù)必去掉“:”,且不要傳遞 SHA1 或其它非簽名 MD5 內(nèi)容
獲取簽名的做法有很多,比較推薦的是使用 keytool 工具,可參見此文檔的介紹。
出于性能考慮,RePlugin 不會自動將“主程序簽名”加入進(jìn)來。如有需要,建議你自行加入。
6.1 最佳實(shí)踐
以下為 360 手機(jī)衛(wèi)士或其它合作 App 采用的設(shè)計(jì),可供你參考:
強(qiáng)烈建議開啟安全和簽名校驗(yàn)
若在調(diào)用
install()方法前就已對 APK 做了校驗(yàn)(例如,手機(jī)衛(wèi)士是云控加密 MD5 + V5 簽名校驗(yàn)),則可關(guān)閉,以避免重復(fù)校驗(yàn)請盡量不要使用和“主程序”一樣的簽名,而是單獨(dú)創(chuàng)建一個
七、插件管理進(jìn)程
由于 RePlugin 支持獨(dú)特的“跨進(jìn)程安全通訊”(見 IPC 類)以及復(fù)雜的插件管理機(jī)制,為保證插件能統(tǒng)一由“一個中心”來管理,提高每個進(jìn)程的啟動、運(yùn)行速度,官方團(tuán)隊(duì)在設(shè)計(jì) RePlugin 之初,就設(shè)計(jì)了一個“插件管理進(jìn)程”,所有插件、進(jìn)程等信息均在此進(jìn)程中被記錄,各進(jìn)程均從此中獲取、修改等,而無需像其它那樣,要求“每個進(jìn)程各自初始化信息”。RePlugin 的這種做法有點(diǎn)像 AMS。
7.1 目前我們有兩種進(jìn)程可以作為“插件管理進(jìn)程”:
7.1.1 以“常駐進(jìn)程”作為“插件管理進(jìn)程”(默認(rèn))
在 RePlugin 2.1.7 及以前版本,這是唯一的方式。RePlugin 默認(rèn)的“常駐進(jìn)程”名為“:GuardService”,通常在后臺運(yùn)行,存活時間相對較久。這樣的最大好處是:應(yīng)用“冷啟動”的概率被明顯的降低,大部分都變成了“熱啟動”,速度更快。
適合作為常駐進(jìn)程的場景包括:
以后臺服務(wù)為主要業(yè)務(wù)的應(yīng)用,例如:手機(jī)安全類、健身和健康監(jiān)控類、OS 內(nèi)應(yīng)用等
需要有常駐通知欄的應(yīng)用,例如:音樂類、清理類等
需保持常連接(例如
Push等)的應(yīng)用,如:即時通訊類、泛社交類等
目前市面上多數(shù)應(yīng)用都集成了推送功能(例如友盟、極光推送),常駐進(jìn)程可以掛載在那里。
優(yōu)點(diǎn),這是結(jié)合“常駐進(jìn)程”長期存活的特點(diǎn)而展開的:
各進(jìn)程啟動時,插件信息的獲取速度會更快(因直接通過
Binder從常駐進(jìn)程獲?。?/p>只要常駐進(jìn)程不死,其它進(jìn)程殺掉重啟后,仍能快速啟動(熱啟動,而非“冷啟動”)
如果做得好的話,甚至可以做到 “0 秒啟動”,如 360 手機(jī)衛(wèi)士。
缺點(diǎn):
若應(yīng)用為“冷啟動”(無任何進(jìn)程時啟動),則需要同時拉起“常駐進(jìn)程”,時間可能有所延長
若應(yīng)用對“進(jìn)程”數(shù)量比較敏感,則此模式會無形中“多一個進(jìn)程”
7.1.2 以“主進(jìn)程”作為“插件管理進(jìn)程”
和“常駐進(jìn)程”不同的是,自 RePlugin 2.2.0 開始,主進(jìn)程也可以作為“插件管理進(jìn)程”。這樣做的最大好處是:應(yīng)用啟動時,可以做到“只有一個進(jìn)程”(注意,這不代表你不能開啟其它插件進(jìn)程,這里只是說沒有“常駐進(jìn)程”了而已)。當(dāng)然,代價是享受不到“常駐進(jìn)程”時的一些好處。
從適用場景上來看,只要是不符合上述“常駐進(jìn)程”中所涉及到的場景的,本模式都適合。
優(yōu)點(diǎn):
無需額外啟動任何進(jìn)程,例如你的應(yīng)用只有一個進(jìn)程的話,那采用此模型后,也只有一個進(jìn)程
應(yīng)用冷啟動(無任何進(jìn)程時啟動)的時間會短一些,因?yàn)闊o需再拉起額外進(jìn)程
缺點(diǎn):
“冷啟動”的頻率會更高,更容易被系統(tǒng)回收,再次啟動的速度略慢于“熱啟動”
7.2 如何使用?
若不設(shè)置,則默認(rèn)是以“常駐進(jìn)程”作為“插件管理進(jìn)程”。
如需切換到以“主進(jìn)程”作為“插件管理進(jìn)程”(也即不產(chǎn)生額外進(jìn)程),則需要在宿主的 app/build.gradle 中添加下列內(nèi)容,用以設(shè)置 persistentEnable 字段為 False:
apply plugin: 'replugin-host-gradle'
repluginHostConfig {
// ... 其它RePlugin參數(shù)
// 設(shè)置為“不需要常駐進(jìn)程”
persistentEnable = false
}
八、插件的目錄結(jié)構(gòu)
無論是內(nèi)置插件,還是外置插件,為了保證穩(wěn)定性,RePlugin 會把經(jīng)過驗(yàn)證的插件放到一個特殊的目錄下,以防止“源文件”被刪除后的一些問題。
由于歷史原因,內(nèi)置插件和外置插件的存放路徑略有不同。以下將分別予以說明。以下為簡化起見,將 “/data/data/[你的主程序包名]” 統(tǒng)一簡化成“主程序路徑”:
外置插件(未來將只有這一種目錄):
APK 存放路徑:主程序路徑/app_p_a
Dex 存放路徑:主程序路徑/app_p_od
Native 存放路徑:主程序路徑/app_p_n
插件數(shù)據(jù)存放路徑:主程序路徑/app_plugin_v3_data
內(nèi)置插件 & 舊 P-N 插件(未來將等同于外置插件):
APK 存放路徑:主程序路徑/app_plugin_v3
Dex 存放路徑:主程序路徑/app_plugin_v3_odex
Native 存放路徑:主程序路徑/app_plugin_v3_libs
插件數(shù)據(jù)存放路徑:主程序路徑/app_plugin_v3_data
8.1 文件的組織形式
外置插件:為了方便使用,插件會有一個 JSON 文件,用來記錄所有已安裝插件的信息。目前位于
“主程序路徑/app_p_a/p.l”中。有興趣的朋友可以自行打開此文件來閱覽其中內(nèi)容。內(nèi)置插件:不同于外置插件,內(nèi)置插件 的 JSON 文件只存放于主程序
“assets/plugins-builtin.json”文件下。每次會從那里獲取信息。
官方計(jì)劃將“內(nèi)置插件”的管控做到和“外置插件”的一致。屆時兩者的管理將變得統(tǒng)一起來。
- P-N 插件(即將廢棄):由于歷史原因,P-N 插件不采用記錄 Json 的形式,而是在
“主程序路徑/files”下,檢索所有“p-n-”開頭,且末尾為“.jar”的文件,并讀取其內(nèi)容頭,進(jìn)而找到插件的信息,并記錄到內(nèi)存中。由于該方案即將廢棄(雖然截止到 2017 年 7 月,衛(wèi)士多數(shù)插件仍然在用,同樣穩(wěn)定),故這里不再贅述。