Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android--動態加載、插件化

android--動態加載、插件化

編輯:關於Android編程

何為動態加載、插件化?

需求驅動

隨著業務發展需要和無線部門的拆分,各業務產品模塊歸屬到各業務BU,原有無線App開發團隊被分為基礎框架、業務A、業務B、業務C等多個開發團隊,從此App的開發和發布進入了一個全新模式。在這種模式下,開發溝通成本大大提高,之前的協作模式難以為繼,需要新的開發模式和技術解決需求問題。

另一方面,從技術上來說65535方法數問題。舊方案是把所有第三方庫放到第二個dex中,並且利用Facebook當年發現的hack方法擴大點LinearAllocHdr分配空間(5M提升到8M),但隨著代碼的膨脹,舊方案也逐漸捉襟見肘。拆or不拆,根本不是可考慮問題,繼續拆分dex是我們的唯一出路。問題在於:怎麼拆才比較聰明?

其次,隨著組織架構調整的影響,給我們的App質量控制帶來極高的挑戰,這種緊張和壓力讓我們的開發團隊心力憔悴。此時除了流著口水羨慕前端同事們的在線更新持續發布能力之外,難道就沒有辦法解決Native架構這一根本性缺陷了嗎?NO!插件化動態加載帶來的額外好處就是客戶端的熱部署能力。

從以上幾點根本性需求可以看出,插件化動態加載架構方案會為我們帶來多麼巨大的收益,除此之外還有諸多好處

為什麼開發者有需要?

 

contextimpl初始微程序裝載

  1. 模塊解耦
  2. 動態升級
  3. 高效並行開發(編譯速度更快)
  4. 按需加載,內存占用更低等等。
  5. 宿主程序與插件完全獨立
  6. 宿主程序開放部分接口供插件與之通信
  7. 宿主程序耦合插件的部分業務邏輯

三種開發模式都可以在 demo 中看到。

1.2 核心概念

(1) 宿主:主 App,可以加載插件,也稱 Host。
(2) 插件:插件 App,被宿主加載的 App,也稱 Plugin,可以是跟普通 App 一樣的 Apk 文件。

(3) 組件:指 Android 中的Activity、Service、BroadcastReceiver、ContentProvider,目前 DL 支持Activity、Service以及動態的BroadcastReceiver。

(4) 插件組件:插件中的組件。

(5) 代理組件:在宿主的 Manifest 中注冊,啟動插件組件時首先被啟動的組件。目前包括 DLProxyActivity(代理 Activity)、DLProxyFragmentActivity(代理 FragmentActivity)、DLProxyService(代理 Service)。

(6) Base 組件:插件組件的基類,目前包括 DLBasePluginActivity(插件 Activity 的基類)、DLBasePluginFragmentActivity(插件 FragmentActivity 的基類)、DLBasePluginService(插件 Service 的基類)。

DynamicLoadApk 原理的核心思想可以總結為兩個字:代理。通過在 Manifest 中注冊代理組件,當啟動插件組件時首先啟動一個代理組件,然後通過這個代理組件來構建、啟動插件組件。

2. 總體設計

總體設計圖
上面是 DynamicLoadApk 的總體設計圖,DynamicLoadApk 主要分為四大模塊:
(1) DLPluginManager
插件管理模塊,負責插件的加載、管理以及啟動插件組件。
(2) Proxy
代理組件模塊,目前包括 DLProxyActivity(代理 Activity)、DLProxyFragmentActivity(代理 FragmentActivity)、DLProxyService(代理 Service)。
(3) Proxy Impl
代理組件公用邏輯模塊,與(2)中的 Proxy 不同的是,這部分並不是一個組件,而是負責構建、加載插件組件的管理器。這些 Proxy Impl 通過反射得到插件組件,然後將插件與 Proxy 組件建立關聯,最後調用插件組件的 onCreate 函數進行啟動。
(4) Base Plugin
插件組件的基類模塊,目前包括 DLBasePluginActivity(插件 Activity 的基類)、DLBasePluginFragmentActivity(插件 FragmentActivity 的基類)、DLBasePluginService(插件 Service 的基類)。

3. 流程圖

流程圖
上面是調用插件 Activity 的流程圖,其他組件調用流程類似。
(1) 首先通過 DLPluginManager 的 loadApk 函數加載插件,這步每個插件只需調用一次。
(2) 通過 DLPluginManager 的 startPluginActivity 函數啟動代理 Activity。
(3) 代理 Activity 啟動過程中構建、啟動插件 Activity。

4. 詳細設計

4.1 類關系圖

類關系圖
以上是 DynamicLoadApk 主要類的關系圖,跟總體設計中介紹的一樣大致分為三部分。
(1) 對於 Proxy 部分,每個組件都存在 DLAttachable 接口,方便統一該組件不同類,如 Activity、FragmentActivity。每個組件的公共實現部分都統一放到了對應的 DLProxyImpl 中。
(2) 對於 Base Plugin 部分,每個組件都存在 DLPlugin 接口,同樣是方便統一該組件不同類。

4.2 類功能介紹

4.2.1 DLPluginManager.java

DynamicLoadApk 框架的核心類,主要功能包括:
(1) 插件的加載和管理;
(2) 啟動插件的組件,目前包括 Activity、Service。

主要屬性:
mNativeLibDir為插件 Native Library 拷貝到宿主中後的存放目錄路徑。
mPackagesHolderHashMap,key 為包名,value 為表示插件信息的DLPluginPackage,存儲已經加載過的插件信息。

主要函數:
(1) getInstance(Context context)
獲取 DLPluginManager 對象的單例。
在私有構造函數中將mNativeLibDir變量賦值為宿主 App 應用程序數據目錄下名為pluginlib子目錄的全路徑。

(2) loadApk(String dexPath)
加載插件。參數 dexPath 為插件的文件路徑。
這個函數直接調用 loadApk(final String dexPath, boolean hasSoLib)。

(3) loadApk(final String dexPath, boolean hasSoLib)
加載插件 Apk。參數 dexPath 為插件的文件路徑,hasSoLib 表示插件是否含有 so 庫。

注意:在啟動插件的組件前,必須先調用上面兩個函數之一加載插件,並且只能在宿主中調用。

流程圖如下:
load-apk-flow-chart
loadApk 函數調用 preparePluginEnv 函數加載插件,圖中虛線框為 preparePluginEnv 的流程圖。

(4) preparePluginEnv(PackageInfo packageInfo, String dexPath)
加載插件及其資源。流程圖如上圖。
調用createDexClassLoader(…)、createAssetManager(…)、createResources(…)函數完成相應初始化部分。

(5) createDexClassLoader(String dexPath)
利用DexClassLoader加載插件,DexClassLoader 初始化函數如下:

public DexClassLoader (String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)

其中dexPath為插件的路徑。
optimizedDirectory優化後的dex存放路徑。這裡將路徑設置為當前 App 應用程序數據目錄下名為dex的子目錄中。
libraryPath為 Native Library 存放的路徑。這裡將路徑設置為mNativeLibDir屬性,其在getInstance(Context)函數中已經初始化。
parent父 ClassLoader,ClassLoader 采用雙親委托模式查找類,具體加載方式可見ClassLoader 基礎。

(6) createAssetManager(String dexPath)
創建 AssetManager,加載插件資源。
在 Android 中,資源是通過 R.java 中的 id 來調用訪問的。但是實現插件化之後,宿主是無法通過 R 文件訪問插件的資源,所以這裡使用反射來生成屬於插件的AssetManager,並利用addAssetPath函數加載插件資源。

    private AssetManager createAssetManager(String dexPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, dexPath);
            return assetManager;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

AssetManager 的無參構造函數以及addAssetPath函數都被hide了,通過反射調用。

(7) createResources(AssetManager assetManager)
利用AssetManager中已經加載的資源創建Resources,代理組件中會從這個Resources中讀取資源。
關於AssetManager、Resources深入的信息可參考:Android 應用程序資源的查找過程分析。

(8) copySoLib(String dexPath)
調用SoLibManager拷貝 so 庫到 Native Library 目錄。

(9) startPluginActivity(Context context, DLIntent dlIntent)
啟動插件 Activity,會直接調用startPluginActivityForResult(…)函數。
插件自己內部 Activity 啟動依然是調用Context#startActivity(…)方法。

(10) startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode)
啟動插件 Activity,流程圖如下:
load-apk-flow-chart

(11) startPluginService(final Context context, final DLIntent dlIntent)
啟動插件 Service。
主要邏輯在函數fetchProxyServiceClass(…)中,流程與startPluginActivity(…)類似,只是換成了回調的方式,在各種條件成立後調用原生方式啟動代理 Service,不再贅述。

(12) bindPluginService(…) unBindPluginService(…)
bind 或是 unBind 插件 Service。邏輯與startPluginService(…)類似,不再贅述。

4.2.2 DLPluginPackage

插件信息對應的實體類,主要屬性如下:

    public String packageName;

    public String defaultActivity;

    public DexClassLoader classLoader;

    public AssetManager assetManager;

    public Resources resources;

    public PackageInfo packageInfo;

packageName為插件的包名;
defaultActivity為插件的 Launcher Main Activity;
classLoader為加載插件的 ClassLoader;
assetManager為加載插件資源的 AssetManager;
resources利用assetManager中已經加載的資源創建的Resources,代理組件中會從這個Resources中讀取資源。
packageInfo被PackageManager解析後的插件信息。
這些信息都會在DLPluginManager#loadApk(…)時初始化。

4.2.3 DLAttachable.java/DLServiceAttachable.java

DLServiceAttachable 與 DLAttachable 類似,下面先分析 DLAttachable.java。

DLAttachable 是一個接口,主要作用是以統一所有不同類型的代理 Activity,如DLProxyActivity、DLProxyFragmentActivity,方便作為同一接口統一處理。
DLProxyActivity和DLProxyFragmentActivity都實現了這個類。

DLAttachable 目前只有一個

attach(DLPlugin pluginActivity, DLPluginManager pluginManager)

抽象函數,表示將插件Activity和代理Activity綁定在一起,其中的pluginActivity參數就是指插件Activity。

同樣 DLServiceAttachable 類似,作用是統一所有不同類型的代理 Service,實現插件Service和代理Service的綁定。雖然目前只有DLProxyService。

4.2.4 DLPlugin.java/DLServicePlugin.java

DLPlugin 與 DLServicePlugin 類似,下面先分析 DLPlugin.java。

DLPlugin 是一個接口,包含Activity生命周期、觸摸、菜單等抽象函數。
DLBase*Activity 都實現了這個類,這樣插件的 Activity 間接實現了此類。
主要作用是統一所有不同類型的插件 Activity,如Activity、FragmentActivity,方便作為同一接口統一處理,所以這個類叫DLPluginActivity更合適。

同樣 DLServicePlugin 主要作用是統一所有不同類型的插件 Service,方便作為統一接口統一處理,目前包含Service生命周期等抽象函數。

4.2.5 DLProxyActivity.java/DLProxyFragmentActivity.java

代理 Activity,他們是在宿主 Manifest 中注冊的組件,也是啟動插件 Activity 時,真正被啟動的 Activity,他們的內部會完成插件 Activity 的初始化和啟動。

這兩個類大同小異,所以這裡只分析DLProxyActivity。

首先來看下它的成員變量。
(1). DLPlugin mRemoteActivity
表示真正需要啟動的插件Activity。這個屬性名應該叫做pluginActivity更合適。
上面我們已經介紹了,DLPlugin是所有插件Activity都間接實現了的接口。

接下來在代理Activity的生命周期、觸摸、菜單等函數中我們都會同時調用 mRemoteActivity 的相關函數,模擬插件Activity的相關功能。

(2). DLProxyImpl impl
主要封裝了插件Activity的公用邏輯,如初始化插件 Activity 並和代理 Activity 綁定、獲取資源等。

4.2.6 DLProxyImpl.java/DLServiceProxyImpl.java

DLProxyImpl 與 DLServiceProxyImpl 類似,下面先分析 DLProxyImpl.java。

DLProxyImpl 主要封裝了插件Activity的公用邏輯,如初始化插件 Activity 並和代理 Activity 綁定、獲取資源等,相當於把DLProxyActivity和DLProxyFragmentActivity的公共實現部分提出出來,核心邏輯位於下面介紹的 onCreate() 函數。

主要函數:
(1) DLProxyImpl(Activity activity)
構造函數,參數為代理 Activity。

(2) public void onCreate(Intent intent)
onCreate 函數,會在代理 Activity onCreate 函數中被調用,流程圖如下:
oncreate-flow-chart.png
其中第一步設置 intent 的 ClassLoader是用於 unparcel Parcelable 數據的

(3) protected void launchTargetActivity()
加載待啟動插件 Activity 完成初始化流程,並通過DLPlugin和DLAttachable接口的 attach 函數實現和代理 Activity 的雙向綁定。流程圖見上圖虛線框部分。

(4) private void initializeActivityInfo()
獲得待啟動插件的 ActivityInfo。

(5) private void handleActivityInfo()
設置代理 Activity 的主題等信息。

其他的 get* 函數都是獲取一些插件相關信息,會被代理 Activity 調用。

同樣 DLServiceProxyImpl 主要封裝了插件Service的公用邏輯,如初始化插件 Service 並和代理 Activity 綁定。

4.2.7 DLBasePluginActivity.java/DLBasePluginFragmentActivity.java

插件 Activity 基類,插件中的Activity都要繼承 DLBasePluginActivity/DLBasePluginFragmentActivity 之一(目前尚不支持 ActionBarActivity)。

主要作用是根據是否被代理,確定一些函數直接走父類邏輯還是代理 Activity 或是空邏輯。

DLBasePluginActivity繼承自Activity,同時實現了DLPlugin接口。這兩個類大同小異,所以這裡只分析DLBasePluginActivity。
主要變量:

    protected Activity mProxyActivity;

    protected Activity that;

    protected DLPluginManager mPluginManager;

    protected DLPluginPackage mPluginPackage;

mProxyActivity為代理 Activity,通過attach(…)函數綁定。
that與mProxyActivity等同,只是為了和this指針區分,表示真實的Context,這裡真實指的是被代理情況下為代理 Activity,未被代理情況下等同於 this。

4.2.8 DLBasePluginService.java

插件 Service 基類,插件中的 Service 要繼承這個基類,主要作用是根據是否被代理,確定一些函數直接走父類邏輯還是代理 Service 或是空邏輯。

主要變量含義與DLBasePluginActivity類似,不重復介紹。
PS:截止目前這個類還是不完善的,至少和DLBasePluginActivity對比,還不支持非代理的情況

4.2.9 DLIntent.java

繼承自 Intent,封裝了待啟動組件的 PackageName 和 ClassName。

4.2.10 SoLibManager.java

調用SoLibManager拷貝 so 庫到 Native Library 目錄。

主要函數:
(1) copyPluginSoLib(Context context, String dexPath, String nativeLibDir)
函數中以ZipFile形式加載插件,循環讀取其中的文件,如果為.so結尾文件、符合當前平台 CPU 類型且尚未拷貝過最新版,則新建Runnable拷貝 so 文件。

4.2.11 DLUtils.java

這個類中大都是無用或是不該放在這裡的函數,也許是大版本升級及維護人過多後對工具函數的維護不夠所致。

5. 雜談

5.1 插件不能打包 dl-lib.jar

原因是插件和宿主屬於不同的 ClassLoader,如果同時打包 dl-lib.jar,會因為 ClassLoader 隔離導致類型轉換錯誤

Eclipse 打包解決方式見項目主頁;
Android Studio 打包解決方式見 5.2;
Ant 打包需要修改 build.xml 中 dex target 引用到的 compileclasspath 屬性。

5.2 在 Android Studio 下使用 DynamicLoadApk

在使用 DynamicLoadApk 時有個地方要注意,就是插件 Apk 在打包的時候不能把 dl-lib.jar 文件打包進去,不然會報錯(java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation)。換句話說,dl-lib.jar 要參與編譯,但不參與打包。該框架作者已經給出了 Eclipse 下的解決方案。我這裡再說下怎麼在 Android Studio 裡使用。

    dependencies {
        provided fileTree(dir: 'dl-lib', include: ['*.jar'])
    }

5.3 DynamicLoadApk 待完善的問題

(1) 還未支持廣播;
(2) Base Plugin 中的 that 還未去掉,需要覆寫 Activity 的相關方法;
(3) 插件和宿主資源 id 可能重復的問題沒有解決,需要修改 aapt 中資源 id 的生成規則;
(4) 不支持自定義主題,不支持系統透明主題;
(5) 插件中的 so 處理有異常;
(6) 不支持靜態 Receiver;
(7) 不支持 Provider;
(8) 插件不能直接用 this;

5.4 其他插件化方案

除了 DynamicLoadApk 用代理的方式實現外,目前還有兩種插件化方案:
(1) 用 Fragment 以及 schema 的方式實現。
(2) 利用字節碼庫動態生成一個插件類 A 繼承自待啟動插件 Activity,啟動插件 A。這個插件 A 名稱固定且已經在 Manifest 中注冊。

最後 混合式開發的模式越來越多,也能解決插件化解決的自動升級這部分功能,硬件、網絡也在改善,未來何如?

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved