framework插件化技術-類加載

近兩年,Android的熱升級技術成爲Android領域的一個熱點。因爲快速迭代的須要,熱修復,插件化的技術已經深刻app及framework的各個研究領域。java

背景技術

簡單的介紹一下熱修復技術的背景: 以往的app項目管理流程以下:android

以上流程有版本週期長,用戶安裝成本高,bug修復不及時用戶體驗差等諸多缺點。 爲了改變這樣的現狀,各大互聯網公司爲此投入了不少研究,熱修復技術應運而生,把更新以補丁的方式上傳到雲端,app從雲端直接下載補丁即時生效,流程以下:

可見使用熱修復技術以後可以實現用戶無感知的修復。 在Android的熱修復主要分三大領域: 代碼修復,資源修復,so修復。
代碼修復有兩大主要方案:

  1. 阿里系的底層替換方案
  2. 騰訊系的類加載方案

兩類方案的優劣:
底層替換方案限制多,但時效性最好,加載輕快,當即見效。
類加載方案時效性差,須要app的冷啓動才能見效,可是修復範廣,限制少。安全

關於底層替換方案,比較出色的應該是阿里的Sophix了。核心原理是替換java方法對應的底層虛擬機的ArtMethod,達到即時修復的效果。這個不是本文介紹的重點,詳情你們能夠參看《深刻探索Android熱修復技術原理》一書。 而冷啓動的方式則是將要修改的代碼打成dex經過插包或者是合併的方式打入dexElements裏。這種方式可以突破底層的諸多限制,可是一樣也會碰到一些Android原有校驗規則的限制,好比:CLASS_ISPREVERIFIED問題。bash

framework特性插件化

一樣的,在Android手機的framework層也遇到相似的問題。目前,各大手機廠商基本都會對Google的原生framework進行或多或少的定製。而若是framework的特性須要升級,以往的流程是:架構

而framework層有不少特性,在framework層的客戶端,本質上是app依賴的一些系統級lib。爲了縮短髮布週期,讓用戶更快的體驗到咱們的新特性,咱們也但願可以使用熱升級技術,將特性lib從framework層脫離出來,成爲一個獨立的個體存在:app

將系統的lib從系統中解耦,成爲一個獨立於平臺的lib,將會帶來如下好處:

  1. 特性更新快,熱升級
  2. 跨平臺,不依賴於系統rom
  3. 向後兼容

support包

Google的support包就是Google對framework向後兼容的一個實現。將framework的部分特性抽離,作成support包的形式,單獨發佈,讓新特性得以向後兼容,不依賴於系統rom,能夠橫跨多個Android版本。主要的實現方式是將support包做爲靜態jar一塊兒打包至app,特性跟着app走而不跟隨系統:模塊化

如上圖所示是AndroidStudio裏編譯生成的一個demo apk的apk結構,從圖中咱們能夠看到在apk生成的classes.dex裏已經包含了support包的各個類。support包已經成爲了app的一部分。這樣的方式帶來的一個缺陷就是support包特性的更新必須依賴於app的更新。固然,咱們也能夠採起以上介紹的各類熱修復技術去更新support包特性。可是做爲framework層,咱們更但願去尋找一種更基礎的方案,讓特性以一種的新的形式去加載。爲此,咱們須要看一下Android的類加載。

類加載

咱們都知道Java的類加載是經過ClassLoader來加載的。函數

而ClassLoader的類加載又是雙親代理模式,也就是樹形結構。Android雖然對ClassLoader在具體的實現上有些改變,可是結構是不變的。

而通常app的class關係樹如圖:

預加載

BootClassLoader是全部classLoader的parent,加載的優先級最高,負責加載一些須要預加載的類。類定義在 /libcore/ojluni/src/main/java/java/lang/ClassLoader.javapost

class BootClassLoader extends ClassLoader {

    private static BootClassLoader instance;

    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }
    ...
}
複製代碼

從以上代碼可見,BootClassLoader是ClassLoader的內部類,訪問權限是包內可見,因此能夠知道僅僅只對部分系統開放。那麼BootClassLoader是在哪建立的,前面所說的預加載的類,又是在哪加載的?這個就要從系統啓動時的zygote進程的初始化提及了。
zygote進程初始化在/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java執行,在該類的main函數裏會執行一些系統的zygote進程的初始化操做,並預加載操做:ui

public static void main(String argv[]) {
    ...
    preload(bootTimingsTraceLog);
    ...
}

複製代碼

繼續看preload方法:

static void preload(TimingsTraceLog bootTimingsTraceLog) {
    ...
    preloadClasses();
    ...
}
複製代碼

preload方法裏會執行preloadClass()方法進行類的預加載:

private static void preloadClasses() {
    ...
    InputStream is;
        try {
            is = new FileInputStream(PRELOADED_CLASSES);
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
            return;
        }
    ...
    try {
            BufferedReader br
                = new BufferedReader(new InputStreamReader(is), 256);

            int count = 0;
            String line;
            while ((line = br.readLine()) != null) {
                // Skip comments and blank lines.
                line = line.trim();
                ...
                try {
                    ...
                    Class.forName(line, true, null);
                    ...
                } catch (ClassNotFoundException e) {
                    Log.w(TAG, "Class not found for preloading: " + line);
                } catch (UnsatisfiedLinkError e) {
                    Log.w(TAG, "Problem preloading " + line + ": " + e);
                } catch (Throwable t) {
                    ...
                }
            }
    ...
}
複製代碼

能夠看到,在preloadClass方法中,首先會去逐行讀取PRELOADED_CLASSES文件,看下該文件指向的路徑:

private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";
複製代碼

咱們看下preload-classes文件:

...
android.app.INotificationManager
android.app.INotificationManager$Stub
android.app.INotificationManager$Stub$Proxy
android.app.IProcessObserver
android.app.IProcessObserver$Stub
android.app.ISearchManager
android.app.ISearchManager$Stub
android.app.IServiceConnection
android.app.IServiceConnection$Stub
android.app.ITransientNotification
android.app.ITransientNotification$Stub
android.app.IUiAutomationConnection
...
複製代碼

能夠看到該文件每一行基本都是framework的類,則Zygote進程經過BufferedReader逐行讀取文件裏的每個類,經過Class.forName方法加載到Zygote進程的內存中。這裏咱們注意到,Class.forName的第三個傳參爲null

Class.forName(line, true, null);
複製代碼

那麼咱們再來看/libcore/ojluni/src/main/java/java/lang/Class.java文件:

public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)throws ClassNotFoundException {
        if (loader == null) {
            loader = BootClassLoader.getInstance();
        }
        ...
}
複製代碼

能夠看到第三個參數是ClassLoader,而且當傳參爲null時,會構造BootClassLoader。到這裏咱們能夠看到,預加載的類,最終是會交給BootClassLoader來加載。當app運行時,若是須要用到系統的類時,則能夠經過訪問他們父進程內存空間中在系統初始化時就已加載的類定義來訪問framework的類了,而不須要在使用到時才從新進行加載。而且系統公共的類定義只存在與zygote進程的內存當中,而不須要每一個app進程加載一份,能夠同時達到空間和時間上的節省。

插件加載

從上述過程咱們能夠知道,framework裏的特性,都是在系統啓動時就經過BootClassLoader加載到zygote進程當中了,那麼若是咱們須要更新那些特性,則須要更新系統配置,系統jar包,而且重啓系統。整個過程很是麻煩。若是咱們但願將framework裏的特性抽取出來,做爲一種可插拔式的插件存在如何作到呢?上述ClassLoader的樹形關係則給了咱們啓發:

將app的classloader與BootClassLoader的直接父子關係切斷,中間加入爲咱們抽離出來的特性構建的CloassLoader做爲app的父ClassLoader,而BootClassLoader則做爲插件CloassLoader的parent,當插件不存在時,BootClassLoader仍然是app classloader的直接parent。這樣咱們就能夠實現插件可插拔了,代碼實現也很簡單:

public void addPluginLoader(Application app) {
        if(!checkToDownloadPlugin(app)) {
            return;
        }

        PathClassLoader parent = new PathClassLoader(mDexPath, app.getClassLoader().getParent());

        try {
            Field parentField = ClassLoader.class.getDeclaredField("parent");
            parentField.setAccessible(true);
            parentField.set(app.getClassLoader(), parent);
        } catch (NoSuchFieldException e) {
            ...
        } catch (IllegalAccessException e) {
            ...
        }
}
複製代碼

其中的mDexPath則是插件特性的路徑,此方法可放在Application的onCreate方法裏執行,實現插件的加載。 通常來講,卸載插件,不能影響到app的其餘特性,因此在總體架構設計上咱們還須要有一個接口做爲沒有plugin的默認實現打包到apk當中。以下圖所示:

咱們能夠將plugin的默認實現(在系統上沒有下載plugin實現時)以靜態lib的方式做爲plugin的interface與app一塊兒編譯打包,一樣的,plugin加載器(plugin loader)也做爲靜態lib打包至app內部,其中的PluginApplication是一個繼承自Application的類,實現plugin包的加載。加載流程:

  1. 檢查當前是否存在plugin或plugin是不是最新,若是不是則從雲端下載,並校驗其安全性。
  2. 若是plugin已經ready,則構建plugin的ClassLoader並進行app classLoader的parent重定向。

例如plugin裏有一個叫PluginFeature的類,若是classLoader已經重定向好,則根據ClassLoader的雙親代理特性,雖說interface裏也存在一樣的PluginFeature的類,可是interface是由app的ClassLoader加載的,因爲plugin的ClassLoader優先級更高,會去加載plugin的PluginFeature類,這樣,就能夠達到接口與實現的分離。當咱們須要更新特性時,只須要更新plugin,而不須要更新app。以達到特性更好的模塊化開發,下降特性與app的耦合度。
固然這種方式,咱們得保證對應用開放的接口不變,若是接口須要改變,應用仍是須要更新本身的apk。 與Google提供的的support方案相比,這種動態加載的插件化方案可以給咱們帶來如下好處:

  1. 在接口不變的狀況下實現熱更新,不須要從新安裝apk。
  2. 系統能夠只存在一份插件,而不須要每一個apk一份,節省rom空間。

固然,Android的plugin特性裏不只僅只是代碼,還有資源。咱們知道已安裝的apk資源是在apk之間互相訪問的,可是咱們下載的plugin並不但願在系統裏安裝,僅僅只是做爲一個文件存在於手機系統中,那plugin裏資源的加載如何實現呢?感興趣的同窗能夠看下下一篇:《framework插件化技術-資源加載(免安裝)》

相關文章
相關標籤/搜索