[Huawei Developers] Freemind R:
More articles like this, you can visit HUAWEI Developer Forum.​

Introduction:

Dynamic Ability is a service in which HUAWEI AppGallery implements dynamic loading based on the Android App Bundle technology.

Apps integrated with the Dynamic Ability SDK can dynamically download features or language packages from HUAWEI AppGallery as required, reducing the unnecessary consumption of network traffic and device storage space.

What is Android App Bundle?

It is a new upload format that includes all your app’s compiled code and resources, but defers APK generation and signing to AppGallery. Traditionally, Android apps are distributed using a special file called an Android Package (.apk).

Advantage of using App Bundle format:

· Dynamic Ability Feature: AppGallery’ s new app serving model, called Dynamic Ability, uses your app bundle to generate and serve optimized APKs for each user’s device configuration, so they download only the code and resources they need to run your app. For example, you don’t need other languages strings if you have set English as your default language.

· No need to manually manage multiple APKs: You no longer have to build, sign, and manage multiple APKs to support different devices, and users get smaller, more optimized downloads. For example, now you don’t have to create multiple APKs for devices with different screen resolutions.

· Dynamic Feature Module: These modules contain features and assets that you can decide not to include when users first download and install your app. using the Dynamic Ability SDK, your app can later request to download those modules as dynamic feature APKs. For example, video calling feature and camera filters can be downloaded later on demand.

· Reduced APK size: Using Split APK mechanism, AppGallery can break up a large app into smaller, discrete packages that are installed on a user’s device as required. On average, apps published with app bundles are 20% smaller in size.

Let’s start Development process:

We need to follow the below step in order to achieve Dynamic Ability.

1. Create an App on AppGallery.

2. Create an Android Studio Project.

3. Integrate Dynamic Ability SDK.

4. Launch the app.

Create an App on AppGallery:

We need to create an app:

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202447.03942357756412054084898048766978:50510721022455:2800:C3A4FBC048FB6E80DAF496595B692DEDD9D68FB2F32213D880C72F4875EF7B2F.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202447.03942357756412054084898048766978:50510721022455:2800:C3A4FBC048FB6E80DAF496595B692DEDD9D68FB2F32213D880C72F4875EF7B2F.png[/IMG]​

App package manually:

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202459.11731674294723034294480616441297:50510721022455:2800:8153648B1F9D4AC96B20E31C670C9DCFD0CA1632C0304838AE2766863CAE158F.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202459.11731674294723034294480616441297:50510721022455:2800:8153648B1F9D4AC96B20E31C670C9DCFD0CA1632C0304838AE2766863CAE158F.png[/IMG]​

Note: Download json file and add into your project.

Create an Android Studio Project:

Select project templet:

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202545.85609403044952163334994449070363:50510721022455:2800:B437904F01FD19203C113F5FB1CD4ED04CFA46B90061F5307E28B7A74E3BF311.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202545.85609403044952163334994449070363:50510721022455:2800:B437904F01FD19203C113F5FB1CD4ED04CFA46B90061F5307E28B7A74E3BF311.png[/IMG]​

Create app name, package name, and project location:

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202556.17933461618807857689660049494617:50510721022455:2800:4D9939917218D24A7F1D28C93D7610C3B70F8C1433C9280B2515923FC76B1F9A.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202556.17933461618807857689660049494617:50510721022455:2800:4D9939917218D24A7F1D28C93D7610C3B70F8C1433C9280B2515923FC76B1F9A.png[/IMG]​

Now we need to add Dynamic Ability module in our project:

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202641.52527670142897636453656066856696:50510721022455:2800:3C5FB9DFF50AA4C7F8CC69959A6A2991E9FFC890DECA950219DAE4F95CE9855D.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202641.52527670142897636453656066856696:50510721022455:2800:3C5FB9DFF50AA4C7F8CC69959A6A2991E9FFC890DECA950219DAE4F95CE9855D.png[/IMG]​

Create module name and package name:

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202704.73895897894444159856126751677647:50510721022455:2800:88E74B9053D7EFDAA3E6829A348262B6BB579A1FF59218FF4A203671EEED9385.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202704.73895897894444159856126751677647:50510721022455:2800:88E74B9053D7EFDAA3E6829A348262B6BB579A1FF59218FF4A203671EEED9385.png[/IMG]​

Create module download options:

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202721.22184144618743573074673723006065:50510721022455:2800:F8EB50B6E063F05635C946D2FCBAD4CA234C473057565F340F3BC40FC66C3E14.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202721.22184144618743573074673723006065:50510721022455:2800:F8EB50B6E063F05635C946D2FCBAD4CA234C473057565F340F3BC40FC66C3E14.png[/IMG]​

Sync project and add the following plugin in module’s Gradle file:

apply plugin: 'com.android.dynamic-feature'

Let’s see module’s manifest.xml file:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:dist="http://schemas.android.com/apk/distribution"

package="com.huawei.android.dynamicfeaturesplit.splitsamplefeature01">

<dist:module

dist:onDemand="true"

dist:title="@string/title_splitsamplefeature01">

<dist:fusing dist:include="true" />

</dist:module>

<application>

<activity android:name=".FeatureActivity"></activity>

</application>

</manifest>

<dist:module>: This new XML element defines attributes that determine how the module is packaged and distributed as APKs.

dist:onDemand="true|false": Specifies whether the module should be available as an on demand download.

dist:title="@string/feature_name": Specifies a user-facing title for the module.

<dist:fusing include="true|false" />: Specifies whether to include the module in multi-APKs that target devices running Android 4.4 (API level 20) and lower.

Integrate Dynamic Ability SDK:

1. Open the build.gradle file (usually in the root directory) of your project. Go to allprojects > repositories and configure the Maven repository address for the SDK.

allprojects{

repositories{

maven {url 'http://developer.huawei.com/repo/'}

...

}

}

2. Add the following code to the build.gradle file (usually app/build.gradle) in the app directory to integrate the Dynamic Ability SDK:

dependencies {

implementation 'com.huawei.hms:dynamicability:1.0.11.302'

...

}

· Let’s sync the project and start app implementation:

We have created following project structure.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202157.39555991484107968009869034459320:50510721022455:2800:7D51CF81283C91936074C4435DA06097AB8CD6C6F4407E5D8BA3E88DCADACEF5.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202157.39555991484107968009869034459320:50510721022455:2800:7D51CF81283C91936074C4435DA06097AB8CD6C6F4407E5D8BA3E88DCADACEF5.png[/IMG]

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202157.68822531212809018068508458808248:50510721022455:2800:BA9DDC53CC2FB1DB7CAC1C44523A655047594C08B520C6EC4E86AC515D08CC78.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709202157.68822531212809018068508458808248:50510721022455:2800:BA9DDC53CC2FB1DB7CAC1C44523A655047594C08B520C6EC4E86AC515D08CC78.png[/IMG]​

·Let’s see the implementation of Application class:

import android.app.Application;

import android.content.Context;

import android.util.Log;

import com.huawei.hms.feature.dynamicinstall.FeatureCompat;

public class DynamicFeatureSampleApplication extends Application {

public static final String TAG = DynamicFeatureSampleApplication.class.getSimpleName();

@Override

protected void attachBaseContext(Context base) {

super.attachBaseContext(base);

try {

FeatureCompat.install(base);

} catch (Exception e) {

Log.w(TAG, "", e);

}

}

}

Configure your app in your Android project, override the attachBaseContext() method in the project, and call FeatureCompat.install to initialize the Dynamic Ability SDK.

· Let’s the implementation of Activity:

package com.huawei.android.dynamicfeaturesplit;

import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.huawei.hms.feature.install.FeatureInstallManager;
import com.huawei.hms.feature.install.FeatureInstallManagerFactory;
import com.huawei.hms.feature.listener.InstallStateListener;
import com.huawei.hms.feature.model.FeatureInstallException;
import com.huawei.hms.feature.model.FeatureInstallRequest;
import com.huawei.hms.feature.model.FeatureInstallSessionStatus;
import com.huawei.hms.feature.model.InstallState;
import com.huawei.hms.feature.tasks.FeatureTask;
import com.huawei.hms.feature.tasks.listener.OnFeatureCompleteListener;
import com.huawei.hms.feature.tasks.listener.OnFeatureFailureListener;
import com.huawei.hms.feature.tasks.listener.OnFeatureSuccessListener;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

public class SampleEntry extends Activity {
private static final String TAG = SampleEntry.class.getSimpleName();
private ProgressBar progressBar;
private FeatureInstallManager mFeatureInstallManager;

private int sessionId = 10086;

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 onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = findViewById(R.id.progress_bar);
mFeatureInstallManager = FeatureInstallManagerFactory.create(this);
}

@Override
protected void onResume() {
super.onResume();
if (mFeatureInstallManager != null) {
mFeatureInstallManager.registerInstallListener(mStateUpdateListener);
}
}

@Override
protected void onPause() {
super.onPause();
if (mFeatureInstallManager != null) {
mFeatureInstallManager.unregisterInstallListener(mStateUpdateListener);
}
}

/**
* install feature
*
* @param view the view
*/
public void installFeature(View view) {
if (mFeatureInstallManager == null) {
return;
}
// start install
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();
}
}
});
task.addOnListener(new OnFeatureCompleteListener<Integer>() {
@Override
public void onComplete(FeatureTask<Integer> featureTask) {
if (featureTask.isComplete()) {
Log.d(TAG, "complete to start install.");
if (featureTask.isSuccessful()) {
Integer result = featureTask.getResult();
sessionId = result;
Log.d(TAG, "succeed to start install. session id :" + result);
} else {
Log.d(TAG, "fail to start install.");
Exception exception = featureTask.getException();
exception.printStackTrace();
}
}
}
});
Log.d(TAG, "start install func end");
}

/**
* start feature
*
* @param view the view
*/
public void startFeature01(View view) {
// test getInstallModules
Set<String> moduleNames = mFeatureInstallManager.getAllInstalledModules();
Log.d(TAG, "getInstallModules : " + moduleNames);
if (moduleNames != null && moduleNames.contains("SplitSampleFeature01")) {
try {
startActivity(new Intent(this, Class.forName(
"com.huawei.android.dynamicfeaturesplit.splitsamplefeature01.FeatureActivity")));
} catch (Exception e) {
Log.w(TAG, "", e);
}
}
}

/**
* cancel install task
*
* @param view the view
*/
public void abortInstallFeature(View view) {
Log.d(TAG, "begin abort_install : " + sessionId);
FeatureTask<Void> task = mFeatureInstallManager.abortInstallFeature(sessionId);
task.addOnListener(new OnFeatureCompleteListener<Void>() {
@Override
public void onComplete(FeatureTask<Void> featureTask) {
if (featureTask.isComplete()) {
Log.d(TAG, "complete to abort_install.");
if (featureTask.isSuccessful()) {
Log.d(TAG, "succeed to abort_install.");
} else {
Log.d(TAG, "fail to abort_install.");
Exception exception = featureTask.getException();
exception.printStackTrace();
}
}
}
});

}

/**
* get install task state
*
* @param view the view
*/
public void getInstallState(View view) {

Log.d(TAG, "begin to get session state for: " + sessionId);
FeatureTask<InstallState> task = mFeatureInstallManager.getInstallState(sessionId);
task.addOnListener(new OnFeatureCompleteListener<InstallState>() {
@Override
public void onComplete(FeatureTask<InstallState> featureTask) {
if (featureTask.isComplete()) {
Log.d(TAG, "complete to get session state.");
if (featureTask.isSuccessful()) {
InstallState state = featureTask.getResult();
Log.d(TAG, "succeed to get session state.");
Log.d(TAG, state.toString());
} else {
Log.e(TAG, "failed to get session state.");
Exception exception = featureTask.getException();
exception.printStackTrace();
}
}
}
});

}

/**
* get states of all install tasks
*
* @param view the view
*/
public void getAllInstallStates(View view) {
Log.d(TAG, "begin to get all session states.");
FeatureTask<List<InstallState>> task = mFeatureInstallManager.getAllInstallStates();
task.addOnListener(new OnFeatureCompleteListener<List<InstallState>>() {
@Override
public void onComplete(FeatureTask<List<InstallState>> featureTask) {
Log.d(TAG, "complete to get session states.");
if (featureTask.isSuccessful()) {
Log.d(TAG, "succeed to get session states.");
List<InstallState> stateList = featureTask.getResult();
for (InstallState state : stateList) {
Log.d(TAG, state.toString());
}
} else {
Log.e(TAG, "fail to get session states.");
Exception exception = featureTask.getException();
exception.printStackTrace();
}
}
});
}

/**
* deffer to install features
*
* @param view the view
*/
public void delayedInstallFeature(View view) {

List<String> features = new ArrayList<>();
features.add("SplitSampleFeature01");
FeatureTask<Void> task = mFeatureInstallManager.delayedInstallFeature(features);

task.addOnListener(new OnFeatureCompleteListener<Void>() {
@Override
public void onComplete(FeatureTask<Void> featureTask) {
if (featureTask.isComplete()) {
Log.d(TAG, "complete to delayed_Install");
if (featureTask.isSuccessful()) {
Log.d(TAG, "succeed to delayed_install");
} else {
Log.d(TAG, "fail to delayed_install.");
Exception exception = featureTask.getException();
exception.printStackTrace();
}
}
}
});
}

/**
* uninstall features
*
* @param view the view
*/
public void delayedUninstallFeature(View view) {
List<String> features = new ArrayList<>();
features.add("SplitSampleFeature01");
FeatureTask<Void> task = mFeatureInstallManager.delayedUninstallFeature(features);
task.addOnListener(new OnFeatureCompleteListener<Void>() {
@Override
public void onComplete(FeatureTask<Void> featureTask) {
if (featureTask.isComplete()) {
Log.d(TAG, "complete to delayed_uninstall");
if (featureTask.isSuccessful()) {
Log.d(TAG, "succeed to delayed_uninstall");
} else {
Log.d(TAG, "fail to delayed_uninstall.");
Exception exception = featureTask.getException();
exception.printStackTrace();
}
}
}
});

}

/**
* install languages
*
* @param view the view
*/
public void loadLanguage(View view) {
if (mFeatureInstallManager == null) {
return;
}
// start install
Set<String> languages = new HashSet<>();
languages.add("fr-FR");
FeatureInstallRequest.Builder builder = FeatureInstallRequest.newBuilder();
for (String lang : languages) {
builder.addLanguage(Locale.forLanguageTag(lang));
}
FeatureInstallRequest request = builder.build();

FeatureTask<Integer> task = mFeatureInstallManager.installFeature(request);

task.addOnListener(new OnFeatureSuccessListener<Integer>() {
@Override
public void onSuccess(Integer result) {
Log.d(TAG, "onSuccess callback result " + result);
}
});
task.addOnListener(new OnFeatureFailureListener<Integer>() {
@Override
public void onFailure(Exception exception) {
if (exception instanceof FeatureInstallException) {
Log.d(TAG, "onFailure callback "
+ ((FeatureInstallException) exception).getErrorCode());
} else {
Log.d(TAG, "onFailure callback ", exception);
}
}
});
task.addOnListener(new OnFeatureCompleteListener<Integer>() {
@Override
public void onComplete(FeatureTask<Integer> task) {
Log.d(TAG, "onComplete callback");
}
});

}

private void makeToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}

}

· We have integrated sdk based classes and callbacks to achieve Dynamic Ability feature in our activity based class.

· Let’s implement module based activity class:

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;
import android.widget.Toast;

import com.huawei.hms.feature.dynamicinstall.FeatureCompat;

public class FeatureActivity extends Activity {

private static final String TAG = FeatureActivity.class.getSimpleName();

static {
System.loadLibrary("feature-native-lib");
}

@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
try {
FeatureCompat.install(newBase);
} catch (Exception e) {
Log.w(TAG, "", e);
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_feature);
ImageView mImageView = findViewById(R.id.iv_load_png);
mImageView.setImageDrawable(getResources().getDrawable(R.mipmap.google));
Toast.makeText(this, "from feature " + stringFromJNI(), Toast.LENGTH_LONG).show();
}

/**
* String from jni string.
*
* @return the string
*/
public native String stringFromJNI();
}

· In an activity of a dynamic feature module, call FeatureCompat.install to initialize the Dynamic Ability SDK.

Launch the app:

· Let’s see the result-

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709201538.07676546022873921618882062454930:50510721022455:2800:EAB441157033DFF1EE24E47B160093BE812D89544A771B47ACF91E589B2E9386.jpg"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709201538.07676546022873921618882062454930:50510721022455:2800:EAB441157033DFF1EE24E47B160093BE812D89544A771B47ACF91E589B2E9386.jpg[/IMG]

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709201538.85605217136087099400131007378734:50510721022455:2800:5C2DC2267C5FAE9CA529CD74A3C068FF30A36DF015B7C9DFC225972976F74B49.jpg"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709201538.85605217136087099400131007378734:50510721022455:2800:5C2DC2267C5FAE9CA529CD74A3C068FF30A36DF015B7C9DFC225972976F74B49.jpg[/IMG]

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709201538.78973854601043145086912271798414:50510721022455:2800:88E019C4E9CF210065581C1500C3CBA1BEC55C5A86BAD1DD0C4B0D6E07F44CC0.jpg"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/001/768/541/2640091000001768541.20200709201538.78973854601043145086912271798414:50510721022455:2800:88E019C4E9CF210065581C1500C3CBA1BEC55C5A86BAD1DD0C4B0D6E07F44CC0.jpg[/IMG]​

If you have any doubts or queries. Please leave your valuable comment in the comment section