RePlugin 關(guān)于插件管理

RePlugin GitHub 主頁

RePlugin Wiki 主頁

RePlugin Wiki 插件的管理

RePlugin 原理剖析

全面插件化:RePlugin 的使命


大致目錄:

* 前言

* 外置插件

  * 安裝插件

  * 升級插件

  * 卸載插件

* 內(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)定),故這里不再贅述。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 接入比較簡單,只需按官方給出的文檔即可 宿主接入指南 插件接入指南 文檔都非常詳細(xì),在這我要說的是一些文檔上面沒有...
    ctrun閱讀 2,185評論 0 0
  • RePlugin的開源地址:https://github.com/Qihoo360/RePlugin官方介紹:ht...
    JarryWell閱讀 11,056評論 6 32
  • 一、RePlugin簡介 RePlugin是一套完整的、穩(wěn)定的、適合全面使用的,占坑類插件化方案。我們“逐>詞”拆...
    阿犇專用閱讀 4,169評論 1 17
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,725評論 25 709
  • 做了一個夢 ,像是孽緣久久不忘,就記下吧。 夢里時間、地點(diǎn)不知,背景只是一座基底很寬大的山。山勢高低起伏綿延千里,...
    大圣歸去來兮閱讀 230評論 0 0

友情鏈接更多精彩內(nèi)容