天天看點

Android動态化架構App Bundles

Android App Bundles

在今年的Google I/O大會上,Google向 Android 引入了新 App 動态化架構(即Android App Bundle,縮寫為AAB),與Instant App不同,AAB是借助Split Apk完成動态加載,使用AAB動态下發方式,可以大幅度減少應用體積。現在隻須在 Android Studio 中建構一個應用束 (app bundle),就可以将應用所需的全部内容 (适用于所有裝置) 都涵蓋在内:所有語言、所有裝置螢幕大小、所有硬體架構。

下面是Dynamic Delivery示意效果圖:

Android動态化架構App Bundles

不過要想體驗Dynamic Delivery,需要先下載下傳

​​Android Studio 3.2​​

Android動态化架構App Bundles

學習Android App Bundles可以将它和Split Apks來對比學習。

Split Apks

split apks是Android 5.0開始提供多apk建構機制,借助split apks可以将一個apk基于ABI和螢幕密度兩個次元拆分城多個apk,這樣可以有效減少apk體積。當使用者下載下傳應用程式安裝包時,隻會包含對應平台的so和資源。因為需要google play支援,是以國内就沒戲了。針對不同cpu架構問題,國内應用開發商大部分都會将so檔案隻放在armabi目錄下,如此做雖然可以有效減少包體積,但可能帶來性能問題。split apks詳細的内容可以通路下面的連結:​​https://link.zhihu.com/?target=https%3A//developer.android.com/studio/build/configure-apk-splits%3Fauthuser%3D2​​

Split Apks的運作原理有點類似于Android的元件化,安裝應用程式時,首先安裝base apk,然後安裝split apks。為了說明splite apks運作原理,來看一下Android 5.0關于splite apks的源碼。

打開​​ApplicationInfo​​類中,可以看到如下資訊:

/**
     * Full paths to zero or more split APKs that, when combined with the base
     * APK defined in {@link #sourceDir}, form a complete application.
     */
    public String[] splitSourceDirs;

    /**
     * Full path to the publicly available parts of {@link #splitSourceDirs},
     * including resources and manifest. This may be different from
     * {@link #splitSourceDirs} if an application is forward locked.
     */
    public      

​​LoadeApk​​中有PathClassLoader和Resources建立過程。LoadedApk#mClassLoader是PathClassLoader執行個體引用,接着看PathClassLoader的建立過程。

public ClassLoader getClassLoader() {
        synchronized (this) {
            if (mClassLoader != null) {
                return mClassLoader;
            }

            if (mIncludeCode && !mPackageName.equals("android")) {

                ......

                final ArrayList<String> zipPaths = new ArrayList<>();
                final ArrayList<String> libPaths = new ArrayList<>();

                .......

                zipPaths.add(mAppDir);
                //将split apk路徑追加到zipPaths中
                if (mSplitAppDirs != null) {
                    Collections.addAll(zipPaths, mSplitAppDirs);
                }

                libPaths.add(mLibDir);

                ......

                final String zip = TextUtils.join(File.pathSeparator, zipPaths);
                final String lib = TextUtils.join(File.pathSeparator, libPaths);

                ......
                //如果mSplitAppDirs不為空,則zip将包含split apps所有路徑。
                mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib,
                        mBaseClassLoader);

                StrictMode.setThreadPolicy(oldPolicy);
            } else {
                if (mBaseClassLoader == null) {
                    mClassLoader = ClassLoader.getSystemClassLoader();
                } else {
                    mClassLoader = mBaseClassLoader;
                }
            }
            return      

在建立PathClassLoader時,dex檔案路徑包含base app和split apps路徑,LoadedApk#mResources是Resources執行個體引用,Resources的源碼如下:

public Resources getResources(ActivityThread mainThread) {
        if (mResources == null) {
            mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
                    mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
        }
        return      

可以發現:split apks資源路徑(LoadedApk#mSplitResDirs)也會被增加至Resources中。

Android App Bundles

下面再來看Android App Bundles,Android App Bundle 支援子產品化,通過Dynamic Delivery with split APKs,将一個apk拆分成多個apk,按需加載(包括加載C/C++ libraries),這樣開發者可以随時按需傳遞功能,而不是僅限在安裝過程中。

Android App Bundle 通常會包括以下幾個檔案:

  • Base Apk:首次安裝的apk,公共代碼和資源,是以其他的子產品都基于Base Apk;
  • Configuration APKs:native libraries 和适配目前手機螢幕分辨率的資源;
  • Dynamic feature APKs:不需要在首次安裝就加載的子產品。
Android動态化架構App Bundles

AAB并不是一個插件化架構,它利用的是Android Framework提供的split apks技術來完成的,而所有安裝split apk工作均是通過IPC交由google play完成。

具體使用時,在Android Studio新增一項module——Dynamic Feature Module。

Android動态化架構App Bundles

在建立dynamic_feature時,有兩個選項是預設勾選的,當然我們也可以更改其狀态。

Android動态化架構App Bundles
  • Enable on-demand: 是否支援按需下載下傳模式。如果不支援,那麼該feature則在安裝app時被安裝。
  • Fusing: 如果app運作在Android 5.0(不包括5.0)以下,勾選Fusing則表示該feature會被一起打包至完整apk中。

下面看一個簡單的執行個體程式。

Android動态化架構App Bundles

在示例中,有四個feature,通過module名很清楚這些feature是舉例介紹如何通路代碼、資源、so等。

dynamic feature module編譯所使用的插件com.android.dynamic-feature,那麼該插件有何獨特之處,通過編譯産物分析,運作示例後,發現在所有dynamic feature子產品build目錄下均會生成apk檔案。

接着反編譯主apk(com.android.application插件生成産物),會發現兩個有趣的現象:

  • 所有dynamic feature module的代碼、資源、so并未打包至主apk中。
  • 主apk manifest資訊包括所有dynamic feature module的manifest,即feature manifest會被合并至主apk manifest中。

Build Bundle(s)

Android App Bundle提供一種全新編譯産物格式檔案aab,使用Android Studio提供的App Bundle即可。

Android動态化架構App Bundles

如上圖,當選擇Build Bundle(s)時,在主工程build目錄下回生成bundle.aab檔案,該檔案是壓縮格式檔案,解壓該aab檔案内容如下。

Android動态化架構App Bundles

從aab檔案内容,可知其包含base和feature的代碼、資源、so等,同時還有BundleConfig.pb這一配置檔案,該配置檔案是google play用于拆分apk。如果我們需要在google play上支援動态釋出,隻需要上傳aab檔案即可,後續工作交給google play完成。

Play Core Library

Play Core Library是AAB提供的核心庫,用于下載下傳、安裝dynamic feature子產品。另外,我們也可以用這些API下載下傳on-demand子產品用于instant app。關于Play Core Library具體如何使用,大家可以檢視相關文檔。

相容性問題處理

6.0以下版本

當app運作裝置版本不高于6.0時,需要使用SplitCompat庫才能立即通路下載下傳子產品代碼和資源。AAB提供SplitCompatApplication類用于開啟SplitCompat。

public class SplitCompatApplication extends Application
    public SplitCompatApplication() {
    }

    protected void attachBaseContext(Context var1) {
        super.attachBaseContext(var1);
        SplitCompat.install(this);
    }
}      

在Application#attachBaseContext(Context)中調用SplitCompat.install(Context)。在該方法中主要完成split apks代碼(dex和so)和資源的安裝。下面是一些相容的條件分支語句:

public static a a() {
        if (VERSION.SDK_INT == 21) {
        //com.google.android.play.core.splitcompat.b.c
            return new c();
        } else if (VERSION.SDK_INT == 22) {
        //com.google.android.play.core.splitcompat.b.f
            return new f();
        } else if (VERSION.SDK_INT == 23) {
        //com.google.android.play.core.splitcompat.b.g
            return new g();
        } else {
            throw new AssertionError();      

高于8.0版本

public static void updateAppInfo(Context var0) {
        if (VERSION.SDK_INT > 25) {
            a.a("Calling dispatchPackageBroadcast!", new Object[0]);

            try {
                Class var1;
                Method var2;
                (var2 = (var1 = Class.forName("android.app.ActivityThread")).getMethod("currentActivityThread")).setAccessible(true);
                Object var3 = var2.invoke((Object)null);
                Field var4;
                (var4 = var1.getDeclaredField("mAppThread")).setAccessible(true);
                Object var5;
                (var5 = var4.get(var3)).getClass().getMethod("dispatchPackageBroadcast", Integer.TYPE, String[].class).invoke(var5, 3, new String[]{var0.getPackageName()});
                a.a("Calling dispatchPackageBroadcast", new Object[0]);
            } catch (Exception var6) {
                a.a(var6, "Update app info with dispatchPackageBroadcast failed!", new Object[0]);
            }
        }
    }      
  • 重新建立mClassLoader
  • 重新建立mResources
  • 更新applicationInfo(調用LoadedApk#setApplicationInfo完成)。