博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
系统级插件化?Google全新的动态化框架Android App Bundles分析
阅读量:2357 次
发布时间:2019-05-10

本文共 9913 字,大约阅读时间需要 33 分钟。

作者:*家伟

Android App Bundles

Android App Bundles(以下简称AAB)是今年Google I/O大会带来的一款全新动态化框架,与Instant App不同,AAB是借助Split Apk完成动态加载。介绍AAB之前,先来了解下SplitApk。

Split Apks

split apks是Android 5.0开始提供多apk构建机制,借助split apks可以将一个apk基于ABI和屏幕密度两个维度拆分城多个apk,这样可以有效减少apk体积。当用户下载应用程序安装包时,只会包含对应平台的so和资源。因为需要google play支持,所以国内就没戏了。针对不同cpu架构问题,国内应用开发商大部分都会将so文件只放在armabi目录下,如此做虽然可以有效减少包体积,但可能带来性能问题。

640?wx_fmt=jpeg

安装应用程序时,首先安装base apk,然后安装split apks。为了解splite apks运作原理,我们还是结合源码做简要分析。因为splite apks是Android 5.0开始支持,所以我们以5.0版本开始分析。

在爱奇艺组件化探索之原理篇文中有介绍相关动态化知识,因此本文不再赘述。

在ApplicationInfo中,增加splites apk相关字段。

/**     * 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 String[] splitPublicSourceDirs;

LoadeApk中有PathClassLoader和Resources创建过程。LoadedApk#mClassLoader是PathClassLoader实例引用,接着分析PathClassLoader创建过程。

public ClassLoader getClassLoader() {        synchronized (this) {            if (mClassLoader != null) {                return mClassLoader;            }            if (mIncludeCode && !mPackageName.equals("android")) {                ......                final ArrayList
zipPaths = new ArrayList<>();                final ArrayList
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 mClassLoader;        }    }

在创建PathClassLoader时,dex文件路径包含base app和split apps路径。LoadedApk#mResources是Resources实例引用,其创建过程如下。

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

该方法中,split apks资源路径(LoadedApk#mSplitResDirs)也会被增加至Resources中。

以上简要介绍split apks加载过程,包括code和resources加载。split apks并不支持动态加载split apk,即base apk 和split apks在app安装时,全部安装。但通过split apks工作原理,可以发现其是能够支持按需加载。

初识Android App Bundles

针对split apks的不足,Google今年在其I/O大会上推出AAB,AAB提供动态安装apk功能,这样可以进一步减少apk体积,首先来看张图。

很遗憾,AAB需要google play支持,国内开发者依旧无缘。

640?wx_fmt=jpeg

从上图可以看出,AAB较split apks多出on-demand模式。AAB也是基于spilt apks来完成按需加载功能,split apks安装需要借助google play来完成。

  • Base APK: 当用户下载你的app时,base apk是首个被安装,所有split apks都能访问base apk代码和资源。

  • Configuration APKs: 这些apks包含特定屏幕密度、CPU架构so文件、语言。当下载base或者feature apk时,google play会根据当前设备特征下载指定configuration apks。

  • Dynamic feature APKs: 这些apks包括代码和资源,当app首次安装时,它们不会被安装,在用户需要使用该feature功能时才会被加载。

更多关于AAB说明,大家可以阅读官方文档。下载最新Preview release版Android Studio和AAB Sample,接着我们根据官方Samples来看看如何玩转AAB。

AAB功能介绍

AAB并不是一个插件化框架,它是利用Android Framework提供的split apks功能完成。所有安装split apk工作均是通过IPC交由google play完成。(第三方app是没有安装split apk能力,只有platform签名应用才行,或者被Root手机)。

Dynamic Feature Module

Android Studio新增一项module——Dynamic Feature Module。

640?wx_fmt=jpeg

在创建dynamic_feature时,有两个选项是默认勾选的,当然我们也可以更改其状态。

640?wx_fmt=jpeg

  • Enable on-demand: 是否支持按需下载模式。如果不支持,那么该feature则在安装app时被安装。

  • Fusing: 如果app运行在Android 5.0(不包括5.0)以下,勾选Fusing则表示该feature会被一起打包至完整apk中。

接着分析AAB示例。

640?wx_fmt=jpeg

在示例中,有四个feature,通过module名很清楚这些feature是举例介绍如何访问代码、资源、so等。

dynamic feature module编译所使用的插件com.android.dynamic-feature,那么该插件有何独特之处。我们通过编译产物分析,运行示例后,发现在所有dynamic feature模块build目录下均会生成apk文件。com.android.dynamic-feature独特之处编译目标是apk文件。

接着反编译主apk(com.android.application插件生成产物),发现:

  • 所有dynamic feature module的代码、资源、so并未打包至主apk中。

  • 主apk manifest信息包括所有dynamic feature module的manifest,即feature manifest会被合并至主apk manifest中。

640?wx_fmt=jpeg

Build Bundle(s)

Android App Bundle提供一种全新编译产物格式文件aab。

640?wx_fmt=jpeg

如上图,当选择Build Bundle(s)时,在主工程build目录下回生成bundle.aab文件,该文件是压缩格式文件,因此将其解压分析。

640?wx_fmt=jpeg

从aab文件内容,可知其包含base和feature的代码、资源、so等,同时还有BundleConfig.pb这一配置文件,该配置文件是google play用于拆分apk。如果我们需要在google play上支持动态发布,只需要上传aab文件即可,后续工作交给google play完成。

另外需要注意的是,如果app运行在4.4及以下设备,则用户下载的将是完成的apk文件,不支持任何split apks特性。

640?wx_fmt=jpeg

上图是AAB文档提供的一张关于aab文件结构图,蓝色方框区域就是configuration apks支持的配置项。

在之前split apks打包中,是不支持语言配置。

以上简要介绍了AAB相关知识点,如有兴趣深入了解还是查阅官方文档。

Play Core Library

Play Core Library是AAB提供的核心库,用于下载、安装dynamic feature模块。另外,我们也可以用这些API下载on-demand模块用于instant app。

关于Play Core Library具体如何使用,大家看下文档,本文主要讲解一些原理性知识点。

主工程模块app,首先分析MainActivity.kt文件。该类是用kotlin编写,如果没有接触过不要紧,大体上还是能看懂。

在MainActivity.kt的onCreate方法中,增加如下逻辑:

override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        manager = SplitInstallManagerFactory.create(this)        initializeViews()        val installedList = manager.installedModules.toList()        for (item in installedList) {            toastAndLog("installed module : " + item.toString())        }        val splits = applicationInfo.splitSourceDirs        for ( item in splits) {            toastAndLog("split dir : " + item.toString())        }    }

打印结果如下:

D/DynamicFeatures: installed module : nativeD/DynamicFeatures: installed module : javaD/DynamicFeatures: installed module : kotlinD/DynamicFeatures: installed module : assetsD/DynamicFeatures: split dir : /data/app/com.google.android.samples.dynamicapps.ondemand-1/split_assets.apkD/DynamicFeatures: split dir : /data/app/com.google.android.samples.dynamicapps.ondemand-1/split_java.apkD/DynamicFeatures: split dir : /data/app/com.google.android.samples.dynamicapps.ondemand-1/split_kotlin.apkD/DynamicFeatures: split dir : /data/app/com.google.android.samples.dynamicapps.ondemand-1/split_native.apk

从运行结果可知,split apks(即使是on-demand模块)在debug模式下,是紧接着base apk安装完成后安装。

SplitInstallManager类提供获取已安装模块方法。

Set
getInstalledModules();

因为Play Core Library非对外暴露接口都是混淆过的,因此就不直接附源码分析。但通过追踪分析源码可知,获取已安装模块的核心过程是:

private final String[] a() {        try {            PackageInfo var1;            return (var1 = this.d.getPackageManager().getPackageInfo(this.e, 0)) != null ? var1.splitNames : null;        } catch (NameNotFoundException var2) {            a.c("App is not found in PackageManager", new Object[0]);            return null;        }    }

通过PackageInfo#splitNames字段获取。

在示例中,每当我们需要启动dynamic feature模块时,都要判断该模块是否安装。如果没有安装,则启动下载,Play Core Library提供了比较完善的下载状态回调,比如下载进度,下载失败原因等等。

通过粗略分析这些混淆源码可知,下载与安装on-demand模块均是通过ipc交由google play完成。

兼容性问题

OS版本不高于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)和资源的安装。

因为代码都是混淆过的,因此只能大概知道SplitCompat做了哪些操作。在SplitCompat#a(boolean)方法调用了

com.google.android.play.core.splitcompat.b.b.a()方法,其中有对不同版本OS兼容性处理。

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();        }    }

分别查看com.google.android.play.core.splitcompat.b.c、com.google.android.play.core.splitcompat.b.f、com.google.android.play.core.splitcompat.b.g,得知其主要做so加载和dex加载(dex前插,与mutil-dex类似)。split apks资源加载在SplitCompat#a(boolean)方法有反射调用AssetManager#addAssetPath(String)

OS版本不低于8.0

在Android 8.0中,Instant Apps相关代码嵌入至Framework。因此如果on-demand模块用于Instant Apps中,需要在on-demand下载成功中,调用SplitInstallHelper.updateAppInfo(Context)

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]);            }        }    }

从上述代码得知其反射调用ActivityThread#dispatchPackageBroadcast方法。最终是调用至LoadedApk#updateApplicationInfo。该方法做了如下事情

  • 重新创建mClassLoader

  • 重新创建mResources

  • 更新applicationInfo(调用LoadedApk#setApplicationInfo完成)。

加载C/C++库

Play Core库提供SplitInstallHelper#loadLibrary用于加载C/C++库。具体用法可以查看示例。

结语

Android App Bundles的出现,相当是官方提供的一套动态化框架,所有的on-demand模块也会被google play审核,这对用户来说是非常有利的。

                        喜欢 就关注吧,欢迎投稿!

640?wx_fmt=jpeg

转载地址:http://iydtb.baihongyu.com/

你可能感兴趣的文章
敏捷测试用例设计
查看>>
校企合作意在解决招聘、就业两大人才问题
查看>>
我在阅读NodeJS文档中读出的19个套路
查看>>
CSS专家、阿里巴巴前端技术专家大漠:纵览布局演变史
查看>>
source insight代码格式化(Astyle)
查看>>
Makefile常用函数
查看>>
配置Linux异构网络下的ntp时间服务器
查看>>
SVN客户端和服务端安装 详解
查看>>
Android Studio 出现 error: cannot find symbol variable abc_ic_ab_back_mtrl_am_alpha
查看>>
Android AndroidManifest.xml 详解
查看>>
Eclipse修改Format不换行 详解
查看>>
Android导入工程提示 Invalid project description 详解
查看>>
Eclipse 历史纪录 详解
查看>>
Android StrictMode 详解
查看>>
Tomcat的优化
查看>>
Android Studio 出现 gradle DSL method not found: android() 错误
查看>>
Fragment 增加高德地图的 com.amap.api.maps.SupportMapFragment
查看>>
《你的投资机会在哪里》
查看>>
Bridge(桥接)模式
查看>>
生成器模式
查看>>