最近幾年移動開發業界興起了「 插件化技術 」的旋風,各個大廠都推出了本身的插件化框架,各類開源框架都評價自身功能優越性,使人應接不暇。隨着公司業務快速發展,項目增多,開發資源卻有限,如何能在有限資源內知足需求和項目的增加,同時又能快速響應問題和迭代新需求,這就是一個矛盾點。此時,插件化技術正好風生水起,去了解各個主流框架實現思路,看看能對目前工做是否有幫助,是頗有必要的。html
主要分爲如下幾個部分vue
插件化介紹node
入門知識android
實現原理git
主流框架github
實戰面試
小結mongodb
百度百科裏是這麼定義插件的:「 是一種遵循必定規範的應用程序接口編寫出來的程序,只能運行在程序規定的系統平臺下,而不能脫離指定的平臺單獨運行。」,也就是說,插件能夠提供一種動態擴展能力,使得應用程序在運行時加載本來不屬於該應用的功能,而且作到動態更新和替換。編程
那麼在 Android 中,何爲「 插件化 」,顧名思義,就是把一些核心複雜依賴度高的業務模塊封裝成獨立的插件,而後根據不一樣業務需求進行不一樣組合,動態進行替換,可對插件進行管理、更新,後期對插件也可進行版本管理等操做。在插件化中有兩個概念須要講解下:安全
所謂宿主,就是須要能提供運行環境,給資源調用提供上下文環境,通常也就是咱們主 APK ,要運行的應用,它做爲應用的主工程所在,實現了一套插件的加載和管理的框架,插件都是依託於宿主的APK而存在的。
插件能夠想象成每一個獨立的功能模塊封裝爲一個小的 APK ,能夠經過在線配置和更新實現插件 APK 在宿主 APK 中的上線和下線,以及動態更新等功能。
那麼爲什麼要使用插件化技術,它有何優點,能給咱們帶來什麼樣好處,這裏簡單列舉了如下幾點:
讓用戶不用從新安裝 APK 就能升級應用功能,減小發版本頻率,增長用戶體驗。
提供一種快速修復線上 BUG 和更新的能力。
按需加載不一樣的模塊,實現靈活的功能配置,減小服務器對舊版本接口兼容壓力。
模塊化、解耦合、並行開發、 65535 問題。
首先咱們要知道插件化技術是屬於比較複雜一個領域,複雜點在於它涉及知識點普遍,不只僅是上層作應用架構能力,還要求咱們對 Android 系統底層知識須要有必定的認知,這裏簡單羅列了其中會涉及的知識點:
首先,要介紹的是 Binder ,咱們都知道 Android 多進程通訊核心就是 Binder ,若是沒有它真的步履維艱。 Binder 涉及兩層技術,你能夠認爲它是一箇中介者模式,在客戶端和服務器端之間, Binder 就起到中介的做用。若是要實現四大組件的插件化,就須要在 Binder 上作修改, Binder 服務端的內容沒辦法修改,只能改客戶端的代碼,並且四大組件的每一個組件的客戶端都不同,這個就須要深刻研究了。學習Binder的最好方式是 AIDL ,這方面在網上有不少資料,最簡單的方式就是本身寫個 aidl 文件自動生成一個 Java 類,而後去查看這個Java類的每一個方法和變量,而後再去看四大組件,其實都是跟 AIDL 差很少的實現方式。
其次,是 App 打包的流程。代碼寫完了,執行一次打包操做,中途經歷了資源打包、 Dex 生成、簽名等過程。其中最重要的就是資源的打包,即 AAPT 這一步,若是宿主和插件的資源id衝突,一種解決辦法就是在這裏作修改。
第三, App 在手機上的安裝流程也很重要。熟悉安裝流程不只對插件化有幫助,在遇到安裝 Bug 的時候也很是重要。手機安裝 App 的時候,常常會有下載異常,提示資源包不能解析,這時須要知道安裝 App 的這段代碼在什麼地方,這只是第一步。第二步須要知道, App 下載到本地後,具體要作哪些事情。手機有些目錄不能訪問, App 下載到本地以後,放到哪一個目錄下,而後會生成哪些文件。插件化有個增量更新的概念,如何下載一個增量包,從本地具體哪一個位置取出一個包,這個包的具體命名規則是什麼,等等。這些細節都必需要清楚明白。
第四,是 App 的啓動流程。 Activity 啓動有幾種方式?一種是寫一個 startActivity ,第二種是點擊手機 App ,經過手機系統裏的 Launcher 機制,啓動 App 裏默認的 Activity 。一般, App 開發人員喜聞樂見的方式是第二種。那麼第一種方式的啓動原理是什麼呢?另外,啓動的時候,Main 函數在哪裏?這個 Main 函數的位置很重要,咱們能夠對它所在的類作修改,從而實現插件化。
第五點更重要,作 Android 插件化須要控制兩個地方。首先是插件 Dex 的加載,如何把插件 Dex 中的類加載到內存?另外是資源加載的問題。插件多是 Apk 也多是 so 格式,無論哪種,都不會生成 R.id ,從而沒辦法使用。這個問題有好幾種解決方案。一種是是重寫 Context 的 getAsset 、 getResource 之類的方法,偷換概念,讓插件讀取插件裏的資源,但缺點就是宿主和插件的資源 id 會衝突,須要重寫 AAPT 。另外一種是重寫 AMS中保存的插件列表,從而讓宿主和插件分別去加載各自的資源而不會衝突。第三種方法,就是打包後,執行一個腳本,修改生成包中資源id。
第六點,在實施插件化後,如何解決不一樣插件的開發人員的工做區問題。好比,插件1和插件2,須要分別下載哪些代碼,如何獨立運行?就像機票和火車票,如何只運行本身的插件,而不運行別人的插件?這是協同工做的問題。火車票和機票,這兩個 Android 團隊的各自工做區是不同的,這時候就要用到 Gradle 腳本了,每一個項目分別有各自的倉庫,有各自不一樣的打包腳本,只須要把本身的插件跟宿主項目一塊兒打包運行起來,而不用引入其餘插件,還有更厲害的是,也能夠把本身的插件看成一個 App 來打包並運行。
上面介紹了插件化的入門知識,一共六點,每一點都須要花大量時間去理解。不然,在面對插件化項目的時候,不少地方你會一頭霧水。而只要理解了這六點核心,一切可迎刃而解。
在Android中應用插件化技術,其實也就是動態加載的過程,分爲如下幾步:
把可執行文件( .so/dex/jar/apk 等)拷貝到應用 APP 內部。
加載可執行文件,更換靜態資源
調用具體的方法執行業務邏輯
Android 項目中,動態加載技術按照加載的可執行文件的不一樣大體能夠分爲兩種:
動態加載 .so 庫
動態加載 dex/jar/apk文件(如今動態加載廣泛說的是這種)
第一點, Android 中 NDK 中其實就使用了動態加載,動態加載 .so 庫並經過 JNI 調用其封裝好的方法。後者通常是由 C/C++ 編譯而成,運行在 Native 層,效率會比執行在虛擬機層的 Java 代碼高不少,因此 Android 中常常經過動態加載 .so 庫來完成一些對性能比較有需求的工做(好比 Bitmap 的解碼、圖片高斯模糊處理等)。此外,因爲 .so 庫是由 C/C++ 編譯而來的,只能被反編譯成彙編代碼,相比中 dex 文件反編譯獲得的 Smali 代碼更難被破解,所以 .so 庫也能夠被用於安全領域。
其二,「基於 ClassLoader 的動態加載 dex/jar/apk 文件」,就是咱們指在 Android 中 動態加載由 Java 代碼編譯而來的 dex 包並執行其中的代碼邏輯,這是常規 Android 開發比較少用到的一種技術,目前說的動態加載指的就是這種。
Android 項目中,全部 Java 代碼都會被編譯成 dex 文件,Android 應用運行時,就是經過執行 dex 文件裏的業務代碼邏輯來工做的。使用動態加載技術能夠在 Android 應用運行時加載外部的 dex 文件,而經過網絡下載新的 dex 文件並替換原有的 dex 文件就能夠達到不安裝新 APK 文件就升級應用(改變代碼邏輯)的目的。
因此說,在 Android 中的 ClassLoader 機制主要用來加載 dex 文件,系統提供了兩個 API 可供選擇:
PathClassLoader:只能加載已經安裝到 Android 系統中的 APK 文件。所以不符合插件化的需求,不做考慮。
DexClassLoader:支持加載外部的 APK、Jar 或者 dex 文件,正好符合文件化的需求,全部的插件化方案都是使用 DexClassloader 來加載插件 APK 中的 .class文件的。
在 Android 中實現插件化框架,須要解決的問題主要以下:
資源和代碼的加載
Android 生命週期的管理和組件的註冊
宿主 APK 和插件 APK 資源引用的衝突解決
下面分析幾個目前主流的開源框架,看看每一個框架具體實現思路和優缺點。
DL 動態加載框架 ( 2014 年末)
是基於代理的方式實現插件框架,對 App 的表層作了處理,經過在 Manifest 中註冊代理組件,當啓動插件組件時,首先啓動一個代理組件,而後經過這個代理組件來構建,啓動插件組件。 須要按照必定的規則來開發插件 APK,插件中的組件須要實現通過改造後的 Activity、FragmentActivity、Service 等的子類。
優勢以下:
插件須要遵循必定的規則,所以安全方面可控制。
方案簡單,適用於自身少許代碼的插件化改造。
缺點以下:
不支持經過 This 調用組件的方法,須要經過 that 去調用。
因爲 APK 中的 Activity 沒有註冊,不支持隱式調用 APK 內部的 Activity。
插件編寫和改造過程當中,須要考慮兼容性問題比較多,聯調起來會比較費時費力。
DroidPlugin ( 2015 年 8 月)
DroidPlugin 是 360 手機助手實現的一種插件化框架,它能夠直接運行第三方的獨立 APK 文件,徹底不須要對 APK 進行修改或安裝。一種新的插件機制,一種免安裝的運行機制,是一個沙箱(可是不徹底的沙箱。就是對於使用者來講,並不知道他會把 apk 怎麼樣), 是模塊化的基礎。
實現原理:
共享進程:爲android提供一個進程運行多個 apk 的機制,經過 API 欺騙機制瞞過系統。
佔坑:經過預先佔坑的方式實現不用在 manifest 註冊,經過一帶多的方式實現服務管理。
Hook 機制:動態代理實現函數 hook ,Binder 代理繞過部分系統服務限制,IO 重定向(先獲取原始 Object –> Read ,而後動態代理 Hook Object 後–> Write 回去,達到瞞天過海的目的)。
插件 Host 的程序架構:
優勢以下:
支持 Android 四大組件,並且插件中的組件不須要在宿主 APK 中註冊。
支持 Android 2.3 及以上系統,支持全部的系統 API。
插件與插件之間,插件與宿主之間的代碼和資源徹底隔閡。
實現了進程管理,插件的空進程會被及時回收,佔用內存低。
缺點以下:
插件 APK 中不支持自定義資源的 Notification,通知欄限制。
插件 APK 中沒法註冊具備特殊的 IntentFilter 的四大組件。
缺少對 Native 層的 Hook 操做,對於某些帶有 Native 代碼的插件 APK 支持不友好,可能沒法正常運行。
因爲插件與插件,插件與宿主之間的代碼徹底隔離,所以,插件與插件,插件與宿主之間的通訊只能經過 Android 系統級別的通訊方式。
安全性擔心(能夠修改,hook一些重要信息)。
機型適配(不是全部機器上都能行,由於大量用反射相關,若是rom廠商深度定製了framework層,反射的方法或者類不在,容易插件運用失敗)
Small ( 2015 年末)
Small 是一種實現輕巧的跨平臺插件化框架,基於「輕量、透明、極小化、跨平臺」的理念,實現原理有如下三點。
動態加載類:咱們知道插件化不少都從 DexClassLoader 類有個 DexPathList 清單,支持 dex/jar/zip/apk 文件格式,卻沒有支持 .so 文件格式,所以 Small 框架則是把 .so 文件包裝成 zip 文件格式,插入到 DexPathList 集合中,改寫動態加載的代碼。
資源分段:因爲 Android 資源的格式是 0xPPTTNNNN ,PP 是包 ID ,00-02 是屬於系統,7f 屬於應用程序,03-7e 則保留,能夠在這個範圍內作文章 , TT 則是 Type 好比,attr 、layout 、string 等等,NNNN 則是資源全局 ID。那麼這個框架則是對資源包進行從新打包,每一個插件從新分配資源 ID ,這樣就保證了宿主和插件的資源不衝突。
動態代理註冊:在 Android 中要使用四大組件,都是須要在 manifest 清單中註冊,這樣纔可使用,那如何在不註冊狀況也能使用呢,這裏就是用到動態代理機制進行 Hook ,在發送 AMS 以前用佔坑的組件來欺騙系統,經過認證後,再把真正要調用的組件還原回來,達到瞞天過海目的。
架構圖:
優勢以下:
全部插件支持內置宿主包中。
插件的編碼和資源文件的使用與普通開發應用沒有差異。
經過設定 URI ,宿主以及 Native 應用插件,Web 插件,在線網頁等可以方便進行通訊。
支持 Android 、 iOS 、和 Html5 ,三者能夠經過同一套 Javascript 接口實現通訊。
缺點以下:
與其餘主流框架的區別:
DyLA : Dynamic-load-apk @singwhatiwanna
DiLA : Direct-Load-apk @FinalLody
APF : Android-Plugin-Framework @limpoxe
ACDD : ACDD @bunnyblue
DyAPK : DynamicAPK @TediWang
DPG : DroidPlugin @cmzy, 360
複製代碼
DyLA | DiLA | ACDD | DyAPK | DPG | APF | Small |
---|---|---|---|---|---|---|
加載非獨立插件 | × | x | √ | √ | × | √ |
加載.so後綴插件 | × | × | ! | × | × | × |
Activity生命週期 | √ | √ | √ | √ | √ | √ |
Service動態註冊 | × | × | √ | × | √ | √ |
資源分包共享 | × | × | ! | ! | × | ! |
公共插件打包共享 | × | × | × | × | × | × |
支持AppCompat | × | × | × | × | × | × |
支持本地網頁組件 | × | × | × | × | × | × |
支持聯調插件 | × | x | × | × | × | × |
ACDD | DyAPK | APF | Small |
---|---|---|---|
插件Activity代碼無需修改 | √ | √ | √ |
插件引用外部資源無需修改name | × | × | × |
插件模塊無需修改build.gradle | × | x | × |
VirtualAPK (2017年 6 月 )
VirtualAPK 是滴滴開源的一套插件化框架,支持幾乎全部的 Android 特性,四大組件方面。
架構圖:
實現思路:
VirtualAPK 對插件沒有額外的約束,原生的 apk 便可做爲插件。插件工程編譯生成 apk後,便可經過宿主 App 加載,每一個插件 apk 被加載後,都會在宿主中建立一個單獨的 LoadedPlugin 對象。以下圖所示,經過這些 LoadedPlugin 對象,VirtualAPK 就能夠管理插件並賦予插件新的意義,使其能夠像手機中安裝過的 App 同樣運行。
合併宿主和插件的ClassLoader 須要注意的是,插件中的類不能夠和宿主重複
合併插件和宿主的資源 重設插件資源的 packageId,將插件資源和宿主資源合併
去除插件包對宿主的引用 構建時經過 Gradle 插件去除插件對宿主的代碼以及資源的引用
特性以下:
四大組件均不須要在宿主manifest中預註冊,每一個組件都有完整的生命週期。
Activity:支持顯示和隱式調用,支持Activity的theme
和LaunchMode
,支持透明主題;
Service:支持顯示和隱式調用,支持Service的start
、stop
、bind
和unbind
,並支持跨進程bind插件中的Service;
Receiver:支持靜態註冊和動態註冊的Receiver;
ContentProvider:支持provider的全部操做,包括CRUD
和call
方法等,支持跨進程訪問插件中的Provider。
自定義View:支持自定義 View
,支持自定義屬性和style
,支持動畫;
PendingIntent:支持PendingIntent
以及和其相關的Alarm
、Notification
和AppWidget
;
支持插件Application
以及插件manifest中的meta-data
;
支持插件中的so
。
優秀的兼容性
兼容市面上幾乎全部的Android手機,這一點已經在滴滴出行客戶端中獲得驗證。
資源方面適配小米、Vivo、Nubia 等,對未知機型採用自適應適配方案。
極少的 Binder Hook,目前僅僅 hook了兩個Binder:AMS
和IContentProvider
,hook 過程作了充分的兼容性適配。
插件運行邏輯和宿主隔離,確保框架的任何問題都不會影響宿主的正常運行。
入侵性極低
插件開發等同於原生開發,四大組件無需繼承特定的基類;
精簡的插件包,插件能夠依賴宿主中的代碼和資源,也能夠不依賴;
插件的構建過程簡單,經過 Gradle 插件來完成插件的構建,整個過程對開發者透明。
以下是 VirtualAPK 和主流的插件化框架之間的對比。
特性 | DynamicLoadApk | DynamicAPK | Small | DroidPlugin | VirtualAPK |
---|---|---|---|---|---|
支持四大組件 | 只支持Activity | 只支持Activity | 只支持Activity | 全支持 | 全支持 |
組件無需在宿主manifest中預註冊 | √ | × | √ | √ | √ |
插件能夠依賴宿主 | √ | √ | √ | × | √ |
支持 PendingIntent | × | × | × | √ | √ |
Android 特性支持 | 大部分 | 大部分 | 大部分 | 幾乎所有 | 幾乎所有 |
兼容性適配 | 通常 | 通常 | 中等 | 高 | 高 |
插件構建 | 無 | 部署aapt | Gradle插件 | 無 | Gradle插件 |
RePlugin (2017 年 7 月)
RePlugin是一套完整的、穩定的、適合全面使用的,佔坑類插件化方案,由360手機衛士的RePlugin Team研發,也是業內首個提出」全面插件化「(全面特性、全面兼容、全面使用)的方案。
框架圖:
主要優點有: 極其靈活:主程序無需升級(無需在Manifest中預埋組件),便可支持新增的四大組件,甚至全新的插件 很是穩定:Hook 點僅有一處(ClassLoader),無任何 Binder Hook!如此可作到其崩潰率僅爲「萬分之一」,並完美兼容市面上近乎全部的 Android ROM。 特性豐富:支持近乎全部在「單品」開發時的特性。包括靜態 Receiver、 Task-Affinity 坑位、自定義 Theme、進程坑位、AppCompat、DataBinding等。 易於集成:不管插件仍是主程序,只需「數行」就能完成接入。 管理成熟:擁有成熟穩定的「插件管理方案」,支持插件安裝、升級、卸載、版本管理,甚至包括進程通信、協議版本、安全校驗等。 數億支撐:有 360 手機衛士龐大的數億用戶作支撐,三年多的殘酷驗證,確保App用到的方案是最穩定、最適合使用的。
主要是測試各個框架之間上手的容易度如何,並作不一樣對比,這邊寫了兩個 Demo 例子,一個是基於 Small 框架,一個基於 VirtualAPK 框架,從中能看出不一樣。
Small 實踐
要引用官方最新的版本,否則在宿主和插件合併build.gradle
的時候會出現一個 BUG,這是個坑位,注意行走。其次在模塊命名上要遵循必定的規則,好比業務模塊用 app.* ,公共庫模塊用 lib.* ,至關於包名 .app.,.lib. 。每次在插件中添加一個 activity 組件,都須要在宿主中配置路由,而後在從新編譯插件一遍,否則直接運行的話,在宿主中是找到新添加的 activity 組件,會報該組件沒在系統 manifest 中,因此每次新增或修改建議插件都從新編譯一遍。官方里說了,對於 Service 支持不太友好,就沒去實踐了。
VirtualAPK 實踐 有個坑須要注意的是構建環境,官方說明是要如下版本環境,Gradle 2.14.1 和 com.android.tools.build 2.1.3, 以前編譯的是用最新的Gradle版本,致使一直有問題,至因而否有其餘問題,能夠看官方文檔。
具體代碼
Small Demo : github.com/cr330326/My…
VirtualAPK Demo : github.com/cr330326/My…
正如開頭所說,要實現插件化的框架,無非就是解決那典型的三個問題:插件代碼如何加載、插件中的組件生命週期如何管理、插件資源和宿主資源衝突怎麼辦。每一個框架針對這三個問題,都有不一樣的解決方案,同時呢,根據時間順序,後出來的框架每每都會吸取已經出的框架精髓,進而修復那些比較有里程碑意義框架的不足。但這些框架的核心思想都是用到了代理模式,有的在表面層進行代理,有的則在系統應用層進行代理,經過代理達到替換和瞞天過海,最終讓 Android 系統誤覺得調用插件功能和調用原生開發的功能是同樣的,進而達到插件化和原生兼容編程的目的
聲明原做者:斜槓Allen 原文地址:www.apkbus.com/blog-945851…
看完你就該會git了(手把手教你用vue+node+mongodb搭建一個小商城
歡迎關注個人微信公衆號:終端研發部,有問題隨時來撩我!