1. 程式人生 > >Android外掛化學習之路(一)之動態載入綜述

Android外掛化學習之路(一)之動態載入綜述

前段時間,公司專案完成了外掛化的開發,自己也因此學習了很多Android外掛化的知識,於是想把這些內容記錄下來,本次帶來Android外掛化的第一篇:動態載入綜述

背景知識

1.什麼是動態載入?

動態載入技術應由以下幾個部分組成:
1) 應用在執行的時候通過載入一些本地不存在的可執行檔案實現一些特定的功能;
2) 這些可執行檔案是可以替換的;
3) 更換靜態資源(比如換啟動圖、換主題、或者用伺服器引數開關控制廣告的隱藏現實等)不屬於動態載入;
4) Android中動態載入的核心思想是動態呼叫外部的 dex檔案,極端的情況下,Android APK自身帶有的Dex檔案只是一個程式的入口(或者說空殼),所有的功能都通過從伺服器下載最新的Dex檔案完成;

2.動態載入的型別

Android專案中,動態載入技術按照載入的可執行檔案的不同大致可以分為兩種:

1) 動態載入so庫;
2) 動態載入dex/jar/apk檔案(現在動態載入普遍說的是這種);

第一種,Android中NDK中其實就使用了動態載入,動態載入.so庫並通過JNI呼叫其封裝好的方法。後者一般是由C/C++編譯而成,執行在Native層,效率會比執行在虛擬機器層的Java程式碼高很多,所以Android中經常通過動態載入.so庫來完成一些對效能比較有需求的工作(比如T9搜尋、或者Bitmap的解碼、圖片高斯模糊處理等)。此外,由於so庫是由C/C++編譯而來的,只能被反編譯成彙編程式碼,相比中dex檔案反編譯得到的Smali程式碼更難被破解,因此so庫也可以被用於安全領域。這裡為後面要講的內容提前說明一下,一般情況下我們是把so庫一併打包在APK內部的,但是so庫其實也是可以從外部儲存檔案載入的。

第二種,“基於ClassLoader的動態載入dex/jar/apk檔案”,就是我們上面提到的“在Android中動態載入由Java程式碼編譯而來的dex包並執行其中的程式碼邏輯”,這是常規Android開發比較常用到的一種技術,目前網路上大多文章說到的動態載入指的就是這種。

3.Android中的動態載入技術
Java的可執行檔案是Jar,執行在虛擬機器上JVM上,虛擬機器通過ClassLoader載入Jar檔案並執行裡面的程式碼。所以Java程式也可以通過動態呼叫Jar檔案達到動態載入的目的。

Android專案中,所有Java程式碼都會被編譯成dex檔案,Android應用執行時,就是通過執行dex檔案裡的業務程式碼邏輯來工作的。使用動態載入技術可以在Android應用執行時載入外部的dex檔案,而通過網路下載新的dex檔案並替換原有的dex檔案就可以達到不安裝新APK檔案就升級應用(改變程式碼邏輯)的目的。

4.Android動態載入的大致過程

無論上面的哪種動態載入,其實基本原理都是在程式執行時載入一些外部的可執行的檔案,然後呼叫這些檔案的某個方法執行業務邏輯。需要說明的是,因為檔案是可執行的(so庫或者dex包,也就是一種動態連結庫),出於安全問題,Android並不允許直接載入手機外部儲存這類noexec(不可執行)儲存路徑上的可執行檔案

對於這些外部的可執行檔案,在Android應用中呼叫它們前,都要先把他們拷貝到data/packagename/內部儲存檔案路徑,確保庫不會被第三方應用惡意修改或攔截,然後再將他們載入到當前的執行環境並呼叫需要的方法執行相應的邏輯,從而實現動態呼叫。

動態載入的大致過程就是:
1) 把可執行檔案(.so/dex/jar/apk)拷貝到應用APP內部儲存;
2) 載入可執行檔案;
3) 呼叫具體的方法執行業務邏輯;

5.動態載入 so庫
動態載入so庫應該就是Android最早期的動態載入了,不過so庫不僅可以存放在APK檔案內部,還可以存放在外部儲存。Android開發中,更換so庫的情形並不多,但是可以通過把so庫挪動到APK外部,減少APK的體積,畢竟許多so庫檔案的體積可是非常大的。

6.動態載入 dex/jar/apk檔案

我們經常講到的那種Android動態載入技術就是這種,後面我們談到“動態載入”如果沒有特別指定,均預設是這個。

基礎知識:類載入器ClassLoader和dex檔案
動態載入dex/jar/apk檔案的基礎是類載入器ClassLoader,它的包路徑是java.lang,由此可見其重要性,虛擬機器就是通過類載入器載入其需要用的Class,這是Java程式執行的基礎。

現在網上有多種基於ClassLoader的Android動態載入的開源專案,大部分核心思想都差不多,按照複雜程度以及具體實現的框架,大致可以分為以下三種形式。

簡單的動態載入模式

Android應用在執行時使用ClassLoader動態載入外部的dex檔案非常簡單,不用覆蓋安裝新的APK,就可以更改APP的程式碼邏輯。但是Android卻很難使用外掛APK裡的res資源,這意味著無法使用新的XML佈局等資源,同時由於無法更改本地的Manifest清單檔案,所以無法啟動新的Activity等元件。

不過可以先把要用到的全部res資源都放到主APK裡面,同時把所有需要的Activity先全部寫進Manifest裡,只通過動態載入更新程式碼,不更新res資源,如果需要改動UI介面,可以通過使用純Java程式碼建立佈局的方式繞開XML佈局。同時也可以使用Fragment代替Activity,這樣可以最大限度得避開“無法註冊新元件的限制”。

這種模式的框架比較適用一些UI變化比較少的專案,比如遊戲SDK,基本就只有登陸、註冊介面,而且基本不會變動,更新的往往只有程式碼邏輯。

代理Activity模式
我們可以通過動態載入,讓現在的Android應用啟動一些“新”的Activity,甚至不用安裝就啟動一個“新”的APK。宿主APK需要先註冊一個空殼的Activity用於代理執行外掛APK的Activity的生命週期。

1) 宿主APK可以啟動未安裝的外掛APK;

2) 外掛APK也可以作為一個普通APK安裝並且啟動;

3) 外掛APK可以呼叫宿主APK裡的一些功能;

4) 宿主APK和外掛APK都要接入一套指定的介面框架才能實現以上功能;

同時也主要有一下幾點限制:

1) 需要在Manifest註冊的功能都無法在外掛實現,比如應用許可權、LaunchMode、靜態廣播等;

2) 宿主一個代理用的Activity難以滿足外掛一些特殊的Activity的需求,外掛Activity的開發受限於代理Activity;

3) 宿主專案和外掛專案的開發都要接入共同的框架,大多時候,外掛需要依附宿主才能執行,無法獨立執行;

代理Activity模式的核心在於“使用宿主的一個代理Activity為外掛所有的Activity提供元件工作需要的環境”,隨著代理模式的逐漸成熟,現在還出現了“使用Hack手段給外掛的Activity注入環境”的模式。

動態建立Activity模式
動態建立Activity模式的核心是“執行時位元組碼操作”,現在宿主註冊一個不存在的Activity,啟動外掛的某個Activity時都把想要啟動的Activity替換成前面註冊的Activity,從而是後者能正常啟動。

1) 主APK可以啟動一個未安裝的外掛APK;
2) 外掛APK可以是任意第三方APK,無需接入指定的介面,理所當然也可以獨立執行;

動態載入技術的作用與缺點

作用
1) 規避APK覆蓋安裝的升級過程,提高使用者體驗,順便能 規避 一些安卓市場的限制;
2) 動態修復應用的一些 緊急BUG,做好最後一道保障;
3) 當應用體積太龐大的時候,可以把一些模組通過動態載入以外掛的形式分割出去,這樣可以減少主專案的體積,提高專案的編譯速度,也能讓主專案和外掛專案並行開發;
4) 外掛模組可以用懶載入的方式在需要的時候才初始化,從而 提高應用的啟動速度;
5) 從專案管理上來看,分割外掛模組的方式做到了 專案級別的程式碼分離,大大降低模組之間的耦合度,同一個專案能夠分割出不同模組在多個開發團隊之間 並行開發,如果出現BUG也容易定位問題;
6) 在Android應用上 推廣 其他應用的時候,可以使用動態載入技術讓使用者優先體驗新應用的功能,而不用下載並安裝全新的APK;
7) 減少主專案DEX的方法數,65535問題 徹底成為歷史(雖然現在在Android Studio中很容易開啟MultiDex,這個問題也不難解決);

缺點
1) 開發方式可能變得比較詭異、繁瑣,與常規開發方式不同;
2) 隨著動態載入框架複雜程度的加深,專案的構建過程也變得複雜,有可能要主專案和外掛專案分別構建,再整合到一起;
3) 由於外掛專案是獨立開發的,當主專案載入外掛執行時,外掛的執行環境已經完全不同,程式碼邏輯容易出現BUG,而且在主專案中除錯外掛十分繁瑣;
4) 非常規的開發方式,有些框架使用反射強行呼叫了部分Android系統Framework層的程式碼,部分Android ROM可能已經改動了這些程式碼,所以有存在相容性問題的風險,特別是在一些古老Android裝置和部分三星的手機上;
5) 採用動態載入的外掛在使用系統資源(特別是Theme)時經常有一些相容性問題,特別是部分三星的手機;

其他動態修改程式碼的技術

上面說到的都是基於ClassLoader的動態載入技術(除了載入SO庫外),使用ClassLoader的一個特點就是,如果程式不重新啟動,載入過一次的類就無法重新載入。因此,如果使用ClassLoader來動態升級APP或者動態修復BUG,都需要重新啟動APP才能生效。

除了使用ClassLoader外,還可以使用jni hook的方式修改程式的執行程式碼。前者是在虛擬機器上操作的,而後者做的已經是Native層級的工作了,直接修改應用執行時的記憶體地址,所以使用jni hook的方式時,不用重新應用就能生效。

目前採用jni hook方案的專案中比較熱門的有阿里的dexposed和AndFix。

動態載入開源專案