App Bundle是一种新的安卓编译打包方式,编译工具可以根据CPU架构类型、屏幕分辨率、语言等维度将一个传统的App打包成一个App集合。用户下载App时,AppGallery Connect(简称AGC)会根据终端三种维度的类型提供仅适配此终端的App子集,从而在提供相同业务功能的前提下,节省用户的网络流量与终端设备空间。
集成Dynamic Ability SDK到Android项目中后,开发者可以与AGC交互,利用App Bundle技术,对App中的某些模块实现动态的加载。
build.gradle
文件。在该文件中,android
闭包中存在如下配置:android {
// 省略不相关配置... ...
// 表示Base App关联了Dynamic Feature Module
dynamicFeatures = [":demofeature"]
}
build.gradle
文件。在该文件中,dependencies
闭包中存在如下配置:dependencies {
// 省略不相关配置... ...
// 表示该module依赖了Base App
implementation project(':app')
}
build.gradle
文件中,加入如下依赖:dependencies {
implementation 'com.huawei.hms:dynamicability:1.0.14.302'
...
}
Application
,并override其中的attachBaseContext()
方法,加入SDK的启动代码。public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// 启动Dynamic Ability SDK
FeatureCompat.install(base);
}
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// 启动Dynamic Ability SDK
FeatureCompat.install(base);
}
引入并启动Dynamic Ability SDK之后,便需要开发者根据业务需求,调用该SDK将Dynamic Feature Module加载起来,即实现按需加载的功能。
FeatureInstallManager
,统一管理加载的整个过程。private FeatureInstallManager mFeatureInstallManager;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
mFeatureInstallManager = FeatureInstallManagerFactory.create(this);
}
FeatureInstallRequest
,明确指定加载的相关信息。通过FeatureInstallRequest
类可以构造一个加载的请求。在该请求中,可以指定一个或多个feature的名称。例如:FeatureInstallRequest request = FeatureInstallRequest.newBuilder()
.addModule("SplitSampleFeature01")
.build();
final FeatureTask<Integer> task = mFeatureInstallManager.installFeature(request);
task.addOnListener(new OnFeatureSuccessListener<Integer>() {
@Override
public void onSuccess(Integer integer) {
Log.d(TAG, "load feature onSuccess.session id:" + integer);
}
});
task.addOnListener(new OnFeatureFailureListener<Integer>() {
@Override
public void onFailure(Exception exception) {
if (exception instanceof FeatureInstallException) {
int errorCode = ((FeatureInstallException) exception).getErrorCode();
Log.d(TAG, "load feature onFailure.errorCode:" + errorCode);
} else {
exception.printStackTrace();
}
}
});
FeatureInstallManager
对象注册监听器,监听加载的多种状态。FeatureInstallManager
注册的监听器InstallStateListener
中会返回一个FeatureInstallSessionState
对象。该对象会根据实际的运行情况,包含当前不同的status。因此,在实际开发中,开发者需要注册一次listener,然后在listener中根据state的值进行判断,再做出合适的动作。这样就无需注册多个listener,减少冗余的代码。具体实现如下:InstallStateListener
实例。private InstallStateListener mStateUpdateListener = new InstallStateListener() {
@Override
public void onStateUpdate(InstallState state) {
Log.d(TAG, "install session state " + state);
if (state.status() == FeatureInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
try {
mFeatureInstallManager.triggerUserConfirm(state, SampleEntry.this, 1);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
return;
}
if (state.status() == FeatureInstallSessionStatus.REQUIRES_PERSON_AGREEMENT) {
try {
mFeatureInstallManager.triggerUserConfirm(state, SampleEntry.this, 1);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
return;
}
if (state.status() == FeatureInstallSessionStatus.INSTALLED) {
Log.i(TAG, "installed success ,can use new feature");
makeToast("installed success , can test new feature ");
return;
}
if (state.status() == FeatureInstallSessionStatus.UNKNOWN) {
Log.e(TAG, "installed in unknown status");
makeToast("installed in unknown status ");
return;
}
if (state.status() == FeatureInstallSessionStatus.DOWNLOADING) {
long process = state.bytesDownloaded() * 100 / state.totalBytesToDownload();
Log.d(TAG, "downloading percentage: " + process);
makeToast("downloading percentage: " + process);
return;
}
if (state.status() == FeatureInstallSessionStatus.FAILED) {
Log.e(TAG, "installed failed, errorcode : " + state.errorCode());
makeToast("installed failed, errorcode : " + state.errorCode());
return;
}
}
};
然后,对监听器进行注册和解注册。
@Override
protected void onResume() {
super.onResume();
if (mFeatureInstallManager != null) {
mFeatureInstallManager.registerInstallListener(installStateListener);
}
}
@Override
protected void onPause() {
super.onPause();
if (mFeatureInstallManager != null) {
mFeatureInstallManager.unregisterInstallListener(installStateListener);
}
}
startActivity(new Intent(this,Class.forName("com.huawei.android.demofeature.TestActivity")));
点击Build > Build Bunldes / APK(s) > Build Bundle(s)
会在 build>outputs>bundle>debug 目录生成 app-debug.aab 文件
默认情况下,Android Studio会自动根据 CPU 架构、屏幕分辨率、语言这三个维度将App分拆;如果希望自由控制分拆维度,可以在app/build.gradle文件android.bundle块中加控制开关
bundle {
language {
enableSplit = false
}
density {
enableSplit = true
}
abi {
enableSplit = true
}
}
若将language的enableSplit
属性设置为false
,那么编译时,Android Studio会将所有的语言资源打入base.apk中(下一节我们会关注aab文件的格式)
通过上一步编译出的aab文件,并不能在本地进行安装测试,需要通过BundleTool转换为apk集合,BundleTool是Google开源的一个命令行工具。aab文件可以上传到AGC(AGC后台会自动调用BundleTool工具转换)。本节主要讨论使用BundleTool本地转换aab文件。
java -jar bundletool-all-0.10.2.jar build-apks --bundle=D:\demo\app-debug.aab --output=D:\demo\aab.apks
其中–bundle表示aab文件路径;–output表示生成apks文件的路径
生成apks文件后将apks文件后缀名修改为zip,然后解压,获取各apk信息如下:
找到主包以及适配自己手机的分辨率包、语言包、CPU架构包,进行安装测试;本例相关包如下:base-master.apk(主包)、base-xxxhdpi.apk(分辨率相关包)、base-zh.apk(语言包)
,没有SO文件(CPU架构相关包),安装到手机上验证功能
adb install-multiple D:\demo\aab\splits\base-master.apk D:\demo\aab\splits\base-xxxhdpi.apk D:\demo\aab\splits\base-zh.apk
由于部分版本(Android 5.0 以下) 不支持App Bundle功能,个别场景我们需要返回全量包;BundleTool工具提供了将aab转换为全量包的能力,使用方法如下:
java -jar bundletool-all-0.10.2.jar build-apks --bundle= D:\demo\app-debug.aab --output=D:\demo\aab-un.apks --mode=universal
将apks文件后缀名修改为zip,解压后apk信息如下:
adb install D:\demo\aab-un\universal.apk
对接AGC,可以点击本链接进行学习
恭喜你,你已经完成了App Bundle的第一个项目,主要学习了如下内容:
Dynamic Ability的相关API介绍请参见 Dynamic AbilityAPI参考。
本次Codelab使用的Demo源码: