Google官方插件化解决方案—全新的动态化框架Android App Bundles分析 Seasoninthesun
热文导读 | 点击标题阅读
金九银十跳槽季如何进阶找到合适满意的工作?
身为程序员碰到最奇葩的需求是怎样的?
2018 年 8 月面试路:6 天 21 家公司
作者:*家伟
来源:https://zhuanlan.zhihu.com/p/36902641
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目录下,如此做虽然可以有效减少包体积,但可能带来性能问题。
安装应用程序时,首先安装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<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 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支持,国内开发者依旧无缘。
从上图可以看出,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。
在创建dynamic_feature时,有两个选项是默认勾选的,当然我们也可以更改其状态。
Enable on-demand: 是否支持按需下载模式。如果不支持,那么该feature则在安装app时被安装。
Fusing: 如果app运行在Android 5.0(不包括5.0)以下,勾选Fusing则表示该feature会被一起打包至完整apk中。
接着分析AAB示例。
在示例中,有四个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中。
Build Bundle(s)
Android App Bundle提供一种全新编译产物格式文件aab。
如上图,当选择Build Bundle(s)时,在主工程build目录下回生成bundle.aab文件,该文件是压缩格式文件,因此将其解压分析。
从aab文件内容,可知其包含base和feature的代码、资源、so等,同时还有BundleConfig.pb这一配置文件,该配置文件是google play用于拆分apk。如果我们需要在google play上支持动态发布,只需要上传aab文件即可,后续工作交给google play完成。
另外需要注意的是,如果app运行在4.4及以下设备,则用户下载的将是完成的apk文件,不支持任何split apks特性。
上图是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.splitSourceDirsfor ( item in splits) {toastAndLog("split dir : " + item.toString())}}
打印结果如下:
D/DynamicFeatures: installed module : native
D/DynamicFeatures: installed module : java
D/DynamicFeatures: installed module : kotlin
D/DynamicFeatures: installed module : assets
D/DynamicFeatures: split dir : /data/app/com.google.android.samples.dynamicapps.ondemand-1/split_assets.apk
D/DynamicFeatures: split dir : /data/app/com.google.android.samples.dynamicapps.ondemand-1/split_java.apk
D/DynamicFeatures: split dir : /data/app/com.google.android.samples.dynamicapps.ondemand-1/split_kotlin.apk
D/DynamicFeatures: split dir : /data/app/com.google.android.samples.dynamicapps.ondemand-1/split_native.apk
从运行结果可知,split apks(即使是on-demand模块)在debug模式下,是紧接着base apk安装完成后安装。
SplitInstallManager类提供获取已安装模块方法。
Set<String> 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审核,这对用户来说是非常有利的。
你有什么想说的?欢迎留言区留言讨论!
如你有好的文章想和大家分享欢迎投稿,直接向我投递文章链接即可
最后,欢迎大家加入我们的知识星球,这期是到2019年3月10日结束,所以越早加入越好,现在加入的球友快1000人了,到1000人时将大幅提价(还有最后几十个名额了),所以快上车!
微信扫描或者点击上方二维码领取Android\Python\AI\Java等高级进阶资源
更多学习资料点击下面的“阅读原文”获取
Google官方插件化解决方案—全新的动态化框架Android App Bundles分析 Seasoninthesun相关推荐
- Android App Bundles(谷歌官方动态化框架)
提纲 是什么(Android App Bundles简介) 为什么使用(aab的四个新功能及实用性) 怎么使用(怎么进行aab的改造) 怎么测试(可以通过google市场测试 而本文主要介绍aab本地 ...
- 是时候来一波Android插件化了
是时候来一波Android插件化了 是时候来一波Android插件化了 前言 Android开发演进 模块化介绍 插件化介绍 前提技术介绍 APK构成 Manifest Application 四大组 ...
- 携程Android App插件化和动态加载实践
转载自:http://www.infoq.com/cn/articles/ctrip-android-dynamic-loading?email=947091870@qq.com 编者按:本文为携程无 ...
- 58同城Android端-最小插件化框架实战和原理分析
目录 背景 插件化需要了解的知识 2.1 类加载过程和类加载器 2.2 ClassLoader 的 findClass.findLibrary.findResource 2.3 DexClassLoa ...
- 美团App 插件化实践
背景 在Android开发行业里,插件化已经不是一门新鲜的技术了,在稍大的平台型App上早已是标配.进入2017年,Atlas.Replugin.VirtualAPK相继开源,标志着插件化技术进入了成 ...
- 美团App插件化实践
点击上方"公众号"可以订阅哦 背景 在Android开发行业里,插件化已经不是一门新鲜的技术了,在稍大的平台型App上早已是标配.进入2017年,Atlas.Replugin.Vi ...
- 【Android 插件化】插件化原理 ( 类加载器 )
Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...
- (4.6.29.3)插件化之代码加载:启动Activity等四大组件之hook方式
文章目录 一.代理模式和Hook原理 1.1 Hook 原理 1.2 代理模式 二.Binder Hook 2.1 分析:系统服务的获取过程 2.2 寻找Hook点 2.3 hook Binder示例 ...
- 插件化知识储备-Binder和AIDL原理
前言 插件化技术火热已久,为什么会有插件化,时势造英雄吧,随着移动互联网的快速发展,业务的飞速增长,如何在有限时间给用户提供高质量的APP,当线上出现各种BUG,如何快速修复并发布上线,插件化的意义也 ...
最新文章
- 关于MongDB数据迁移方案的研究
- android蓝牙串口通讯
- 负载均衡原理与实践详解 第三篇 服务器负载均衡的基本概念-网络基础
- 遗落在时光里的静态链表(线性表的静态存储)---C语言版
- Picture Control控件图象保存为bmp,jpg,emf,tif,gif
- Microsoft Dynamics AX 2012
- java实现递归算法
- 【IIS】修改注册表键值提升IIS的性能
- Python基础+数据科学入门(四)程序控制结构
- opencv分量法、加权平均法、最大值法、平均值法灰度化
- python绘制爱心气球_婚房布置气球爱心教程-婚房气球布置心形方法简单【蜜匠婚礼】...
- 服务器显示器超分辨率,显示器分辨率超频1080超到2K屏方法
- Shader 常用函数
- Luat模块应用手册-指南-Luat二次开发教程指南-功能开发教程-长连接超低功耗方案
- 中国象棋游戏Chess(3) - 实现走棋规则
- 第八届蓝桥杯个人赛赛后总结
- 关于预答辩研究生汇报内容及方式的几点要求
- 政府OA办公系统实施时需要关注的五个环节
- 从Python到TensorFlow,差点把我 六年的电脑砸了,哈哈哈哈(详细安装入门步骤)
- C# Winform 计算机原理模型机的设计——带超前进位加法器
热门文章
- Beta冲刺提交—星期五
- 一般邮箱的邮件群发数量限制是多少?
- 怎么修改邮箱服务器类型,邮箱登录手动修改服务器配置
- vue报错 运行npm run dev报cjs.js缺失
- EndNote使用手册(无图版)
- 计算机未检测到无线接收器,wifi接收器插上没反应 wifi接收器插上没反应怎么办 - 云骑士一键重装系统...
- 评价计算机主机电源的标准主要有哪些,PC电源好不好?这项参数很重要
- C语言系统功能的描述,C语言20操作系统介绍ppt课件
- 物联网+大数据+云计算+人工智能
- 对“判断内六角规格项目”一些改进与完善