0%

App Bundle预研

1 介绍

Google引入新Android App Bundle应用文件格式。这种文件包括所有的应用程序的编译代码和资源,Google Play 根据用户的手机信息生成所需资源和SO的APK,用户只需要下载运行应用所需的资源。而且Android App Bundle提供功能动态加载的功能(类似插件化的概念),开发者可用将不重要或者独立的功能在用户需要的时候加载。

图片

Android5.0(API 20)及以上,包含应用程序支持的所有功能和设备配置的代码和资源的APK,分解为安装在用户设备上的更小的独立包,并根据需要进行安装。拆分的APK与正常APK非常相似,它们包括编译的DEX字节码,资源和Android清单。以下描述了三种拆分APK关系。

  • Base APK:这个APK包含所有其他拆分APK可以访问的代码和资源,并为应用提供基本功能。这是用户下载应用时安装的第一个APK。
  • Configuration APKs: 这些APK中的每一个都包含用于特定屏幕像素密度,CPU架构库和资源。当设备下载一个基本或动态功能APK时,它只下载它需要的库和资源。对于大多数应用项目,无需重构应用即可支持Configuration APK,Google Play会根据您的应用包中包含的代码和资源生成。
  • Dynamic feature APKs:这些APK主要是动态功能的代码和资源,不是用户第一次必须安装。

图片

apk关系图

Android5.0(API 20)以下不支持动态加载,Google Play为手机直接生成一个完整功能和对应CUP架构、资源的APK。

2 创建Demo

*Android Stuidio 3.2

1).配置工程

①.创建一个正常的工程。

②. 新建Dynamic Feature Module。

图片

③. 创建完成以后可以看到Dynamic Feature Module和普通的项目目录一样,在build.gradle中声明的是全新的module类型apply plugin: ‘com.android.dynamic-feature’,而且dynamic-feature模块中可以引用app模块,之前的版本中Application模块是无法作为lib被其他模块引用的。

1
2
3
4
5
apply plugin: 'com.android.dynamic-feature'

dependencies {
implementation project(':app')
}

④. AndroidManifest中声明了一些动态功能模块属性。onDemand指定模块是否应作为按需下载提供,Title 指定模块向用户的展示的标题, fusing指定是否将模块包含在运行Android 4.4(API级别20)或更低的设备。

1
2
3
4
5
6
7
8
9
10
11
manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.example.dynamic_feature">

<dist:module
dist:onDemand="true"
dist:title="@string/title_dynamic_feature">
<dist:fusing include="true" />
</dist:module>

</manifest>

⑤.在App module build.gradle 中添加配置。

1
2
3
android {
dynamicFeatures = [":dynamic-feature"]
}

2) 功能模块动态加载

①App项目中添加Google加载类库

1
implementation 'com.google.android.play:core:1.2.0'

②按需加载动态模块,首先创建SplitInstallRequest,然后通过SplitInstallManager执行一个加载请求。在下载和安装中会回调处理状态,可根据状态做一些处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
  SplitInstallRequest request = SplitInstallRequest.newBuilder()
.addModule(DYNAMIC_FEATURE)
.build();
if (splitInstallManager.getInstalledModules().contains(DYNAMIC_FEATURE)) {
onSuccessfulLoad(DYNAMIC_FEATURE);
return;
}

splitInstallManager.registerListener(new SplitInstallStateUpdatedListener() {
@Override
public void onStateUpdate(SplitInstallSessionState splitInstallSessionState) {
if (splitInstallSessionState.status() == SplitInstallSessionStatus.INSTALLED) {
onSuccessfulLoad(DYNAMIC_FEATURE);
} else if (splitInstallSessionState.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
//需要用户确认
try {
startIntentSender(splitInstallSessionState.resolutionIntent().getIntentSender(),
null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
}
});

splitInstallManager.startInstall(request)
.addOnSuccessListener(new OnSuccessListener<Integer>() {
@Override
public void onSuccess(Integer integer) {
onSuccessfulLoad(DYNAMIC_FEATURE);
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.d(TAG, e.getMessage());
}
})
.addOnCompleteListener();

③调用动态功能模块

1
2
3
4
5
6
7
8
private void onSuccessfulLoad(String dynamicFeature) {
try {
Intent intent = new Intent(this, Class.forName("com.example.dynamic_feature.MainActivity2"));
startActivity(intent);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

3)编译文件格式的处理流程

Google这次为App Bundles推出.aab新的文件格式, 包含了应用所有模块的代码和资源。Google Play根据用户手机信息通过这个.aab的文件生成拆分APK文件。

图片

① 使用Android Studio生成 aab文件。

  • 从菜单栏中选择Build> Generate Signed Bundle / APK。
  • 在Generate Signed Bundle or APK对话框中,选择 Android App Bundle 并单击Next。 然后继续后面步骤完成后就会生成一个.aab的文件。

② 文件拆分

首先从Github下载命令工具bundletool(https://github.com/google/bundletool/releases)

执行下面的命令后会生成一个my_app.apks的文件。

1
2
java -jar bundletool-all-0.3.3.jar build-apks --bundle=C:\code\PP\app\release\bundle.aab --output=C:\code\PP\app\rel
ease\my_app.apks --ks=C:\key_store.jks --ks-pass=pass:****** --ks-key-alias=****

③ 提取拆分APK

首先通过命令获取设备信息。

1
java -jar bundletool-all-0.3.3.jar get-device-spec --output=C:\code\PP\app\release\device.json

执行命令后可以获取到设备信息如下device.json

1
2
3
4
5
6
7
8
9
10
11
{

"supportedAbis": ["arm64-v8a", "armeabi-v7a", "armeabi"],

"supportedLocales": ["en-US"],

"screenDensity": 320,

"sdkVersion": 22

}

然后根据设备信息生成APK,最终生成4个APK。

1
java -jar bundletool-all-0.3.3.jar extract-apks --apks=C:\code\PP\app\release\my_app.apks --output-dir=C:\code\PP\app\release\ --device-spec=C:\code\PP\app\release\device.json

图片

base-mastet指Base APK,base-xhdpi指Config APK和介绍中的文件结构图对应。

④文件资源分析

将base-mastet.apk拖到Android Studio中,可以看见文件的差别,里面不包含xhdpi的资源。然后再分析base-xhdpi.apk的文件,里面包含base-mastet.apk所缺的xhdpi资源。

图片

base-mastet资源图

图片

base-xhdpi资源图

⑤动态功能代码分析

可以看到master-base中没有包含dynamic_feature-master中的代码。

图片

base-mastet代码

图片

dynamic_feature-master代码

4)动态加载原理浅析

因为Google库代码混淆,整体的流程无法分析,但是基本的一些原理关键点可以找到。主要集中在com.google.android.play.core.splitcompat.b包下。可以发现基本都是插件化中使用到的技术。

1
2
3
4
5
com.google.android.play.core.splitcompat.c.b.a(var14, "addAssetPath", Integer.class, String.class, var9.getPath())

public final Object[] a(Object var1, ArrayList<File> var2, File var3, ArrayList<IOException> var4) {
return (Object[])b.a(var1, "makeDexElements", Object[].class, ArrayList.class, var2, File.class, var3, ArrayList.class, var4);
}

5) App Bundle总结

优势

通过设备信息动态生成用户设备所需SO和资源的APK,并支持功能动态化加载,开发者可以根据用户按需加载功能模块,而不是仅限在安装过程中。最终使得安装包体积更小,下载速度也越快,同时也节省了设备存储空间。

App Bundle的实现技术方案类似于插件化,但是有官方的支持,兼容和稳定性上能做的更好。

劣势 :

暂时只有Google Play支持Android App Bundle,其他应用市场无法使用,而且需要上传私钥。

动态化功能虽然类似于插件化,但是编译打包必须在同一个项目下,来解决资源文件冲突和四大组件清单声明问题。