最近幾年移動開發業界興起了「 插件化技術 」的旋風,各個大廠都推出了自己的插件化架構,各種開源架構都評價自身功能優越性,令人目不暇接。随着公司業務快速發展,項目增多,開發資源卻有限,如何能在有限資源内滿足需求和項目的增長,同時又能快速響應問題和疊代新需求,這就是一個沖突點。此時,插件化技術正好風生水起,去了解各個主流架構實作思路,看看能對目前工作是否有幫助,是很有必要的。
主要分為以下幾個部分
插件化介紹
入門知識
實作原理
主流架構
實戰
小結
進階資料
百度百科裡是這麼定義插件的:「 是一種遵循一定規範的應用程式接口編寫出來的程式,隻能運作在程式規定的系統平台下,而不能脫離指定的平台單獨運作。」,也就是說,插件可以提供一種動态擴充能力,使得應用程式在運作時加載原本不屬于該應用的功能,并且做到動态更新和替換。
那麼在 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 接口實作通信。
暫不支援 Service 的動态注冊,不過這個可以通過将 Service 預先注冊在宿主的 AndroidManifest.xml 檔案中進行規避,因為 Service 的更新頻率通常非常低。
與其他主流架構的差別:
功能
DyLA
DiLA
ACDD
DyAPK
DPG
APF
Small
加載非獨立插件
×
x
√
加載.so字尾插件
!
Activity生命周期
Service動态注冊
資源分包共享
公共插件打包共享
支援AppCompat
支援本地網頁元件
支援聯調插件
透明度
插件Activity代碼無需修改
插件引用外部資源無需修改name
插件子產品無需修改build.gradle
VirtualAPK (2017年 6 月 )
VirtualAPK 是滴滴開源的一套插件化架構,支援幾乎所有的 Android 特性,四大元件方面。
實作思路:
VirtualAPK 對插件沒有額外的限制,原生的 apk 即可作為插件。插件工程編譯生成 apk後,即可通過宿主 App 加載,每個插件 apk 被加載後,都會在宿主中建立一個單獨的 LoadedPlugin 對象。如下圖所示,通過這些 LoadedPlugin 對象,VirtualAPK 就可以管理插件并賦予插件新的意義,使其可以像手機中安裝過的 App 一樣運作。
合并宿主和插件的ClassLoader 需要注意的是,插件中的類不可以和宿主重複
合并插件和宿主的資源 重設插件資源的 packageId,将插件資源和宿主資源合并
去除插件包對宿主的引用 建構時通過 Gradle 插件去除插件對宿主的代碼以及資源的引用
特性如下:
四大元件均不需要在宿主manifest中預注冊,每個元件都有完整的生命周期。
Activity:支援顯示和隐式調用,支援Activity的<code>theme</code>和<code>LaunchMode</code>,支援透明主題;
Service:支援顯示和隐式調用,支援Service的<code>start</code>、<code>stop</code>、<code>bind</code>和<code>unbind</code>,并支援跨程序bind插件中的Service;
Receiver:支援靜态注冊和動态注冊的Receiver;
ContentProvider:支援provider的所有操作,包括<code>CRUD</code>和<code>call</code>方法等,支援跨程序通路插件中的Provider。
自定義View:支援<code>自定義 View</code>,支援自定義屬性和<code>style</code>,支援動畫;
PendingIntent:支援<code>PendingIntent</code>以及和其相關的<code>Alarm</code>、<code>Notification</code>和<code>AppWidget</code>;
支援插件<code>Application</code>以及插件manifest中的<code>meta-data</code>;
支援插件中的<code>so</code>。
優秀的相容性
相容市面上幾乎所有的Android手機,這一點已經在滴滴出行用戶端中得到驗證。
資源方面适配小米、Vivo、Nubia 等,對未知機型采用自适應适配方案。
極少的 Binder Hook,目前僅僅 hook了兩個Binder:<code>AMS</code>和<code>IContentProvider</code>,hook 過程做了充分的相容性适配。
插件運作邏輯和宿主隔離,確定架構的任何問題都不會影響宿主的正常運作。
入侵性極低
插件開發等同于原生開發,四大元件無需繼承特定的基類;
精簡的插件包,插件可以依賴宿主中的代碼和資源,也可以不依賴;
插件的建構過程簡單,通過 Gradle 插件來完成插件的建構,整個過程對開發者透明。
如下是 VirtualAPK 和主流的插件化架構之間的對比。
特性
DynamicLoadApk
DynamicAPK
DroidPlugin
VirtualAPK
支援四大元件
隻支援Activity
全支援
元件無需在宿主manifest中預注冊
插件可以依賴宿主
支援 PendingIntent
Android 特性支援
大部分
幾乎全部
相容性适配
一般
中等
高
插件建構
無
部署aapt
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 實踐
要引用官方最新的版本,不然在宿主和插件合并<code>build.gradle</code> 的時候會出現一個 BUG,這是個坑位,注意行走。其次在子產品命名上要遵循一定的規則,比如業務子產品用 app.* ,公共庫子產品用 lib.* ,相當于包名 .app.,.lib. 。每次在插件中添加一個 activity 元件,都需要在宿主中配置路由,然後在重新編譯插件一遍,不然直接運作的話,在宿主中是找到新添加的 activity 元件,會報該元件沒在系統 manifest 中,是以每次新增或修改建議插件都重新編譯一遍。官方裡說了,對于 Service 支援不太友好,就沒去實踐了。
VirtualAPK 實踐
有個坑需要注意的是建構環境,官方說明是要以下版本環境,Gradle 2.14.1 和 com.android.tools.build 2.1.3, 之前編譯的是用最新的Gradle版本,導緻一直有問題,至于是否有其他問題,可以看官方文檔。
具體代碼
Small Demo :https://github.com/cr330326/MySmall
VirtualAPK Demo :https://github.com/cr330326/MyVirtualAPKDemo
正如開頭所說,要實作插件化的架構,無非就是解決那典型的三個問題:插件代碼如何加載、插件中的元件生命周期如何管理、插件資源和宿主資源沖突怎麼辦。每個架構針對這三個問題,都有不同的解決方案,同時呢,根據時間順序,後出來的架構往往都會吸收已經出的架構精髓,進而修複那些比較有裡程碑意義架構的不足。但這些架構的核心思想都是用到了代理模式,有的在表面層進行代理,有的則在系統應用層進行代理,通過代理達到替換和瞞天過海,最終讓 Android 系統誤以為調用插件功能和調用原生開發的功能是一樣的,進而達到插件化和原生相容程式設計的目的。
1,Android插件化從入門到放棄-最強合集
2,包建強的無線技術空間,寫給Android App 開發人員看的 Android 底層知識 置頂8篇
3,Android插件化原了解析
4,Android插件化:從入門到放棄
源于對掌握的Android開發基礎點進行整理,羅列下已經總結的文章,從中可以看到技術積累的過程。
1,Android系統簡介
2,ProGuard代碼混淆
3,講講Handler+Looper+MessageQueue關系
4,Android圖檔加載庫了解
5,談談Android運作時權限了解
6,EventBus初了解
7,Android 常見工具類
8,對于Fragment的一些了解
9,Android 四大元件之 " Activity "
10,Android 四大元件之" Service "
11,Android 四大元件之“ BroadcastReceiver "
12,Android 四大元件之" ContentProvider "
13,講講 Android 事件攔截機制
14,Android 動畫的了解
15,Android 生命周期和啟動模式
16,Android IPC 機制
17,View 的事件體系
18,View 的工作原理
19,了解 Window 和 WindowManager
20,Activity 啟動過程分析
21,Service 啟動過程分析
22,Android 性能優化
23,Android 消息機制
24,Android Bitmap相關
25,Android 線程和線程池
26,Android 中的 Drawable 和動畫
27,RecylerView 中的裝飾者模式
28,Android 觸摸事件機制
29,Android 事件機制應用
30,Cordova 架構的一些了解
31,有關 Android 插件化思考
32,開發人員必備技能——單元測試