1. 程式人生 > >(4.6.29)Android外掛化框架總結

(4.6.29)Android外掛化框架總結

一、概述

所謂外掛化,就是讓我們的應用不必再像原來一樣把所有的內容都放在一個apk中,可以把一些功能和邏輯單獨抽出來放在外掛apk中,然後主apk做到[按需呼叫],這樣的好處是一來可以減少主apk的體積,讓應用更輕便,二來可以做到熱插拔,更加動態化

採集時間:2018年6月14日11:52:21

header 1 github-start lastUpadte 四大元件 元件無需在宿主manifest中預註冊 外掛可以依賴宿主 PendingIntent Android特性 相容性 外掛構建 使用者

為什麼需要外掛化?

  1. 應用體積越來越大,需要拆分APK完成模組化與熱部署
  2. 應用頻繁更新,導致使用者粘性降低
  3. 新功能的加入
    1. 不確定與使用者需求的匹配性,需要動態增加或者更新,增加靈活性
    2. 一旦不適用或發生嚴重問題,無法做平滑處理(動態切換),只能緊急釋出補丁版本升級
  4. 主應用使用者量較大,同系應用需要導流,傳統特性只能引導使用者下載安裝

外掛化的特定就是 “ 應用在執行的時候通過載入一些本地不存在的可執行檔案實現一些特定的功能”,這些可執行檔案是可以替換的( 更換靜態資源,比如換啟動圖、換主題、或者用伺服器引數開關控制廣告的隱藏現實等,不屬於動態載入)。

Android中動態載入的核心思想是動態呼叫外部的 dex檔案,極端的情況下,Android APK自身帶有的Dex檔案只是一個程式的入口(或者說空殼),所有的功能都通過從伺服器下載最新的Dex檔案完成

實現外掛化後,能夠:

  1. 減小主APK大小
  2. 降低新功能版本釋出的頻繁性
  3. 獨立開發 A/B Test 模組
  4. bug修復工具

1.1 發展歷史

很早之前已經有公司在研究外掛化這項技術,淘寶做得比較早,但淘寶的這項技術一直是保密的。直到2015年才陸續出現很多框架,Android外掛化分成很多技術流派,實現的方式都不太一樣

    • 用Fragment來實現。通過動態載入不同的Fragement,把想換的頁面都換掉。
    • 在這個專案中第一次看到了如何通過addAssetPath來讀取外掛中的資源
  • 2013.23Code。

    • 23Code提供了一個殼,在這個殼裡可以動態下載外掛,然後動態執行。可以在殼外編寫各種各樣的控制元件,放在這個框架下去執行。
  • 2014年初.阿里.Altas技術

    • 網上出現一個視訊,僅進行了大方向講解,並未開源
    • 這跟後續介紹的很多外掛化專案都不太一樣。它沒有Hook太多的系統底層方法,而是從上層,即App應用層解決問題,建立一個繼承自Activity的ProxyActivity類,然後讓外掛中的所有Activity都繼承自ProxyActivity,並重寫Activity所有的方法。
    • 之所以說這個專案是里程碑式的,是因為在2015年之前業界沒有太多資料可以參考
    • 這個框架參考了淘寶App的很多經驗,主要就是Hook的思想,同時,還首次提出來通過擴充套件AAPT來解決外掛與宿主的資源id衝突的問題。
    • 這個專案太牛了,能把任意的App都載入到宿主裡。可以基於這個框架寫一個宿主App,然後就可以把別人寫的App都當作外掛來載入。這個框架的功能的確很強大,但強大的代價就是要改寫很多Android系統的底層程式碼
    • 是 360 手機助手實現的一種外掛化框架,它可以直接執行第三方的獨立 APK 檔案,完全不需要對 APK 進行修改或安裝。一種新的外掛機制,一種免安裝的執行機制,是一個沙箱(但是不完全的沙箱。就是對於使用者來說,並不知道他會把 apk 怎麼樣), 是模組化的基礎
    • 說明文件較少,導致技術人員掌握這個框架不太容易
  • 201509.Andfix,Nuwa(女媧)…

    • 熱修復技術陸續登場
    • 這個機制不太一樣的地方就是,通過指令碼的方式來解決資源衝突的問題
    • 支援幾乎所有的 Android 特性,四大元件方面
    • 是一套完整的、穩定的、適合全面使用的,佔坑類外掛化方案,也是業內首個提出”全面外掛化“(全面特性、全面相容、全面使用)的方案
  • 201710.美團.仿InstantRun框架

1.2 需要知識

想完全明白外掛化技術,首先需要了解Android系統的底層實現

1.2.1 Binder

你可以認為它是一箇中介者模式,在客戶端和伺服器端之間,Binder就起到中介的作用,這是我這段時間對Binder的思考。

要實現四大元件的外掛化,就需要在Binder上做修改。

1.2.2 App打包流程

程式碼寫完了,執行一次打包操作,中途經歷了資源打包、dex生成、簽名等過程。其中最重要的就是資源的打包,即AAPT這一步,如果宿主和外掛的資源id衝突,一種解決辦法就是在這裡做修改

1.2.3 App安裝流程

熟悉安裝流程不僅對外掛化有幫助,在遇到安裝bug的時候也非常重要。手機安裝App的時候,經常會有下載異常,提示資源包不能解析,這時需要知道安裝App的這段程式碼在什麼地方,這只是第一步。第二步需要知道,App下載到本地後,具體要做哪些事情。手機有些目錄不能訪問,App下載到本地之後,放到哪個目錄下,然後會生成哪些檔案。外掛化有個增量更新的概念,如何下載一個增量包,從本地具體哪個位置取出一個包,這個包的具體命名規則是什麼,等等。這些細節都必須要清楚明白

1.2.4 App啟動流程

Activity啟動有幾種方式?一種是寫一個startActivity,第二種是點選手機App,通過手機系統裡的Launcher機制,啟動App裡預設的Activity。通常,App開發人員喜聞樂見的方式是第二種。那麼第一種方式的啟動原理是什麼呢?另外,啟動的時候,main函式在哪裡?這個main函式的位置很重要,我們可以對它所在的類做修改,從而實現外掛化。

1.2.5 資源載入機制

使用ClassLoader動態載入一個外部的類是非常容易的事情,所以很容易就能實現動態載入新的可執行程式碼的功能,但是比起一般的Java程式,在Android程式中使用動態載入主要有兩個麻煩的問題:

  1. [程式碼載入] 首先是外掛Dex的載入,如何把外掛Dex中的類載入到記憶體?

    • [載入]類的載入可以使用Java的ClassLoader機制
    • [代理]但是對於Android來說,並不是說類載入進來就可以用了,很多元件都是有“生命”的;因此對於這些有血有肉的類,必須給它們注入活力,也就是所謂的元件生命週期管理
  2. [資源載入] 外掛可能是apk也可能是so格式, 不管哪一種, 都不會生成 R.id.XX,從而沒辦法使用。

    • 一種解決方式是外掛裡需要用到的新資源都通過純Java程式碼的方式建立(包括XML佈局、動畫、點九圖等),麻煩但是有效,不多過描述;
    • 一種是是重寫Context的getAsset、getResource之類的方法,偷換概念,讓外掛讀取外掛裡的資源,但缺點就是宿主和外掛的資源id會衝突,需要重寫AAPT。
    • 另一種是重寫AMS中儲存的外掛列表,從而讓宿主和外掛分別去載入各自的資源而不會衝突。
    • 第三種方法,就是打包後,執行一個指令碼,修改生成包中資源id。

說到底,拋開虛擬機器的差別不說,一個Android程式和標準的Java程式最大的區別就在於他們的上下文環境(Context)不同。Android中,這個環境可以給程式提供元件需要用到的功能,也可以提供一些主題、Res等資源,其實上面說到的兩個問題都可以統一說是這個環境的問題,而現在的各種Android動態載入框架中,核心要解決的東西也正是“如何給外部的新類提供上下文環境”的問題

整體來說,需要以下知識點:

  1. ClassLoader類載入器

要想實現載入外部dex檔案(即外掛)來實現熱部署,那麼必然要把其中的class檔案載入到記憶體中。其中涉及到兩種ClassLoader:DexClassLoader和PathClassLoader。而DexClassLoader可以載入外部的jar,dex等檔案,正是我們需要的。

  1. Java反射

因為外掛apk與宿主apk不在一個apk內,那麼一些類的訪問必然要通過反射進行獲取。所以瞭解反射對外掛化的學習是必須的。

  1. 代理模式

外掛化實現的過程主要靠欺上瞞下,坑蒙拐騙來實現。想想雖然載入進來了Activity等元件,但也僅僅是最為一個物件而存在,並沒有在AndroidManifest中註冊,沒有生命週期的回撥,並不能實現我們想要的效果。因此無論是dynamic_load_apk通過代理activity來操控外掛activity的方式,還是DroidPlugin通過hook activity啟動過程來啟動外掛activity的方式,都是對代理模式的應用

  1. 外掛資源訪問

res裡的每一個資源都會在R.java裡生成一個對應的Integer型別的id,APP啟動時會先把R.java註冊到當前的上下文環境,我們在程式碼裡以R檔案的方式使用資源時正是通過使用這些id訪問res資源,然而外掛的R.java並沒有註冊到當前的上下文環境,所以外掛的res資源也就無法通過id使用了。

檢視原始碼,通過“addAssetPath”方法重新生成一個新的Resource物件來儲存外掛中的資源,避免衝突。

1.2.6 Gradle配置打包

在實施外掛化後,如何解決不同外掛的開發人員的工作區問題。比如,外掛1和外掛2,需要分別下載哪些程式碼,如何獨立執行?就像機票和火車票,如何只執行自己的外掛,而不執行別人的外掛?這是協同工作的問題。火車票和機票,這兩個Android團隊的各自工作區是不一樣的,這時候就要用到Gradle指令碼了,每個專案分別有各自的倉庫,有各自不同的打包指令碼,只需要把自己的外掛跟宿主專案一起打包執行起來,而不用引入其他外掛,還有更厲害的是,也可以把自己的外掛當作一個App來打包並執行

1.3 主流框架

外掛化框架各種各樣,大致可以劃分為:隔離型外掛與非隔離型外掛。

  • 隔離型外掛
    • 此類外掛是指:每個外掛都是相對獨立的個體,而且都執行在各自不同的沙盒中各個外掛及宿主之間,不能像同一個應用一樣直接共享資料。
    • 比如360的RePlugin與DroidPlugin。DroidPlugin是不同外掛有分別執行在不同的外掛程序。RePlugin是每個外掛都是使用的一個獨立的classLoader來類載入器。都實現了程式碼級別的隔離,這兩種都是隔離型外掛。
  • 非隔離型外掛:
    • 這種是對業務邏輯存在耦合的環境下,開發app最友好的外掛化方案。這種外掛框架,所有的外掛都是執行在同一個程序中且未做隔離。宿主與外掛、外掛與外掛之間可以直接共享資料。
    • 比如Small和VirtualAPK.

Dynamic-load-apk詳解

Dynamic-Load-Apk簡稱DL,這個開源框架作者是任玉剛,他的實現方式是,在宿主中埋一個代理Activity,更改ClassLoader後找到載入外掛中的Activity,使用宿主中的Activity作為代理,回撥給外掛中Activity所以對應的生命週期。這個思路與AndroidDynamicLoader有點像,都是做一個代理,只不過Dynamic-load-apk載入的外掛中的Activity

DroidPlugin詳解

DroidPlugin是張勇實現的一套外掛化方案,它的原理是Hook客戶端一側的系統Api

Small框架詳解

參考文獻