This codelab introduces the concept of Android App Bundles and dynamic delivery of optimized APKs.

What is an Android App Bundle?

The Android App Bundle is a new upload format that includes all your app's compiled code and resources, but defers APK generation and signing to Google Play.

What is Dynamic Delivery?

It is Google Play's new app serving model, and it 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. You no longer have to build, sign, and manage multiple APKs, and users get smaller, more optimized downloads.

What are dynamic feature modules?

These are modules you can add to your app project and include them in your app bundle. Through Dynamic Delivery, your app can download and install dynamic features on demand.

This functionality is still in beta.

Figure 1. On the left: a simple app that includes a base APK (B) and some configuration APKs (C). On the right: a more complex app that includes two dynamic feature APKs (D) and corresponding configuration APKs (C) for download on demand.

The following steps will help you get setup with the prerequisites for the codelab.

Getting Android Studio 3.2 Canary

The easiest way to start configuring your project and building app bundles is by using the latest version of Android Studio. If you haven't already done so, download Android Studio 3.2 Canary now.

Make sure you have the latest version of the Canary build. Using the top menu, expand the Android Studio menu and click on About Android Studio. You should be using Android Studio 3.2 Canary 14. Some of the tools described in this codelab only work in this version of Studio.

Getting started with the code

The benefits of Android App Bundles and dynamic features is best seen when working with complex real-world apps that target multiple languages, use multiple assets for different screen densities, and target different architectures. In such apps, App Bundles can substantially reduce the size of the APKs that are installed on a user's device.

In this codelab, though, we'll keep things simple for demonstration purposes. All you will need to do is create a generic Android project in Studio and modify that project as you go along.

A fundamental component of Dynamic Delivery is the split APK mechanism available on Android 5.0 (API level 21) and higher. With split APKs Google Play can break up a large app into smaller, discrete packages that are installed on a user's device as required.

Different types of split APKs

There are three kinds of split APKs that you'll learn about in this codelab.

Base APK

Contains code and resources that all other split APKs can access and provides the basic functionality for your app. When the user downloads the app, they always get this APK.

Configuration APKs

Includes only native libraries and resources for a given device configuration. Specifically, this means optimizing the APK content based on the following:

Dynamic feature APKs

Includes features that are not required when the app is first installed, but which may be downloaded and installed later.

OK, let's get started by launching Android Studio Canary 14.

Create a new Android Studio project using the following steps:

That's it. You've just created a minimal Android project that prints a "Hello, World!" to the screen.

Building your project

In this step, we'll compare and contrast two approaches to building the project: 1) building a monolithic APK, and 2) building bundles.

Building an APK should be familiar to most Android developers (go to Build > Build Bundle(s) / APK(s) and select Build APK(s)). Monolithic APKs include all the following:

Building Bundles

As an alternative to building a monolithic APK, you can instead opt to build bundles.

First, open app/build.gradle and add the following inside the android {} block:

bundle {
   language {
       enableSplit = true
   }
   density {
       enableSplit = true
   }
   abi {
       enableSplit = true
   }
}

This ensures that language, density, and abi configuration splits are all enabled.

Wait for the project to sync.

Now, you can build your App Bundle. Go to Build > Build Bundle(s) / APK(s) and select Build Bundle(s):

Bundles are stored in .aab format. You can see the generated .aab in your build directory. See app > build > outputs > bundle > debug > bundle.aab

Once you upload the bundle to Google Play, Play only installs the configurations required by the user device. This includes:

The developer benefits of this approach are significant. It keeps apps smaller and It removes the need to build and publish Multi-APKs.

After you build your Android App Bundle, you should test how Google Play uses it to generate APKs and how those APKs behave when deployed to a device. There are two ways you should consider testing your app bundle:

  1. Locally using the bundletool command line tool
  2. Through Google Play by uploading your bundle to the Play Console and using the new internal test track.

This step of the codelab explains how to use bundletool to test your app bundle locally.

What is bundletool?

Bundletool is the underlying tool that Gradle, Android Studio, and Google Play use to build an Android App Bundle or convert an app bundle into the various APKs that are deployed to devices. Bundletool is also available to you as a command line tool, so you can recreate, inspect, and verify Google Play's server-side build of your app's APKs.

Download bundletool

If you haven't already done so, download bundletool from the GitHub repository. You'll be downloading the bundletool-all-0.3.3.jar file.

Generate a set of APKs from your app bundle

When bundletool generates APKs from your app bundle, it includes them in a container called an APK set archive, which uses the .apks file extension. To generate an APK set for all device configurations your app supports from your app bundle, use the bundletool build-apks command, as shown below.

The bundletool build-apks command has the following syntax:

bundletool build-apks --bundle=<path to .aab> --output=<out.apks>

Only two arguments are required, 1) the path the .aab file and, 2) the directory where the generated apks are stored.

For 1), recall that the debug Android app bundle (the .aab file) was generated at the following location:

./app/build/outputs/bundle/debug/bundle.aab

For 2), create a directory to store the generated apks:

mkdir out.apks

Now you can run build-apks. If bundletool is in your path, run the following:

java -jar ~/Downloads/bundletool-all-0.3.3.jar build-apks --bundle=./app/build/outputs/bundle/debug/bundle.aab --output=out.apks

When the command executes, you should see output similar to this:

WARNING: The APKs won't be signed and thus not installable unless you also pass a keystore via the flag --ks. See the command help for more information.

As the output makes clear, if you want to deploy the APKs to a device, you need to also include your app's signing information. We'll skip that step in this codelab and work. Instead, we'll generate the apks from the app bundle and examine the output.

Examining the generated APKs

Running bundletool build-apks generates numerous split apks based on language, density, and abi. Now you'll unzip the contents of out.apk and examine the generated apks.

Create an apks/ directory to store the unzipped apks:

mkdir apks

Then, unzip the contents of out.apks into apks/:

unzip out.apks -d apks

The apks/ directory now contains numerous apks. Your output should look like this:

shailentuli:~/work/android/DynamicAppsCodelab (master)$ ls -l apks
total 4584
-rw-r--r--  1 shailentuli  eng    97167 May  7 15:37 base-arm64_v8a.apk
-rw-r--r--  1 shailentuli  eng    69911 May  7 15:37 base-armeabi_v7a.apk
-rw-r--r--  1 shailentuli  eng    40332 May  7 15:37 base-hdpi.apk
-rw-r--r--  1 shailentuli  eng    36156 May  7 15:37 base-ldpi.apk
Archive:  out.apks
 extracting: apks/base-xhdpi.apk     
 extracting: apks/base-hdpi.apk      
 extracting: apks/base-mdpi.apk      
 extracting: apks/base-ldpi.apk      
 extracting: apks/base-xxxhdpi.apk   
 extracting: apks/base-xxhdpi.apk    
 extracting: apks/base-ca.apk        
 extracting: apks/base-da.apk        
 extracting: apks/base-ja.apk        
 extracting: apks/base-fa.apk        
 extracting: apks/base-tvdpi.apk     
 extracting: apks/base-ka.apk        
 extracting: apks/base-pa.apk        
 extracting: apks/base-ta.apk        
 extracting: apks/base-nb.apk        
 extracting: apks/base-be.apk        
 extracting: apks/base-de.apk        
 extracting: apks/base-ne.apk        
 extracting: apks/base-te.apk        
 extracting: apks/base-af.apk        
 extracting: apks/base-th.apk        
 extracting: apks/base-bg.apk        
 extracting: apks/base-fi.apk        
 extracting: apks/base-si.apk        
 extracting: apks/base-hi.apk        
 extracting: apks/base-vi.apk        
 extracting: apks/base-kk.apk        
 extracting: apks/base-mk.apk        
 extracting: apks/base-sk.apk        
 extracting: apks/base-uk.apk        
 extracting: apks/base-el.apk        
 extracting: apks/base-gl.apk        
 extracting: apks/base-ml.apk        
 extracting: apks/base-nl.apk        
 extracting: apks/base-pl.apk        
 extracting: apks/base-tl.apk        
 extracting: apks/base-sl.apk        
 extracting: apks/base-am.apk        
 extracting: apks/base-km.apk        
 extracting: apks/base-bn.apk        
 extracting: apks/base-in.apk        
 extracting: apks/base-kn.apk        
 extracting: apks/base-mn.apk        
 extracting: apks/base-ko.apk        
 extracting: apks/base-lo.apk        
 extracting: apks/base-ro.apk        
 extracting: apks/base-sq.apk        
 extracting: apks/base-fr.apk        
 extracting: apks/base-ar.apk        
 extracting: apks/base-master.apk    
 extracting: apks/base-sr.apk        
 extracting: apks/base-hr.apk        
 extracting: apks/base-mr.apk        
 extracting: apks/base-tr.apk        
 extracting: apks/base-cs.apk        
 extracting: apks/base-ur.apk        
 extracting: apks/base-bs.apk        
 extracting: apks/base-et.apk        
 extracting: apks/base-es.apk        
 extracting: apks/base-ms.apk        
 extracting: apks/base-is.apk        
 extracting: apks/base-lt.apk        
 extracting: apks/base-eu.apk        
 extracting: apks/base-it.apk        
 extracting: apks/base-pt.apk        
 extracting: apks/base-zu.apk        
 extracting: apks/base-hu.apk        
 extracting: apks/base-ru.apk        
 extracting: apks/base-gu.apk        
 extracting: apks/base-lv.apk        
 extracting: apks/base-iw.apk        
 extracting: apks/base-sv.apk        
 extracting: apks/base-sw.apk        
 extracting: apks/base-my.apk        
 extracting: apks/base-az.apk        
 extracting: apks/base-hy.apk        
 extracting: apks/base-ky.apk        
 extracting: apks/base-uz.apk        
 extracting: apks/base-zh.apk        
 extracting: apks/base-en.apk        
 extracting: apks/base-jp.apk        
 extracting: apks/base-armeabi_v7a.apk  
 extracting: apks/base-arm64_v8a.apk  
 extracting: apks/base-x86_64.apk    
 extracting: apks/base-x86.apk 

That's a lot of apks! Let's break down the contents. Note that all apks are prefixed with base-, since our app only contains the one base module.

base-master.apk

Contains the code and resources for the base module

base-armeabi_v7a.apk, base-arm64_v8a.apk, etc.

ABI configuration splits

base-xhdpi.apk, base-mdpi.apk, etc.

Screen density configuration splits

base-ko.apk, base-fr.apk, etc.

Language configuration splits

Dynamic feature modules allow you to separate certain features and resources from the base module of your app and include them in your app bundle. Through Dynamic Delivery, users can later download and install those components on demand after they've already installed the base APK of your app.

Create a dynamic feature module

To add a dynamic feature module to your app, do the following:

Select File > New > New Module from the menu bar. In the Create New Module dialog, select Dynamic Feature Module.

On the Configure your new module screen, give your module a name. Note that app has been selected for you as the Base application module.

On the Configure your new module screen, specify the module title.

Check the Enable on-demand box if you want the module to be available for on demand downloads. If this is unchecked, the dynamic feature is available when a user first downloads and installs your app.

Check the Fusing box if you want this module to be available to devices running Android 4.4 (API level 20) and lower and include it in multi-APKs. This option is available only if you checked the box next to Enable on-demand. That means you can enable on demand behavior for this module and disable fusing to omit it from devices that don't support downloading and installing split APKs.

Hit Finish and wait for the project to sync. Your should see the newly created Payments module and your project structure should look like this:

You should quickly notice that the default code, resources, and organization are similar to that of the standard app module.

Looking at generated content

The platform uses the title you just specified for your feature module identify the module to users when, for example, confirming whether the user wants to download the module.

For this reason, your app's base module (app) must include the module title as a string resource, which can be translated. Android Studio creates this resource in the base module for you and injects it into the manifest of the feature module (payments):

Open payments/src/main/AndroidManifest.xml and you should see the following XML representing the module configuration:

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

Dynamic feature module build configuration

When you create a new dynamic feature module using Android Studio, the IDE applies the following Gradle plugin to the dynamic feature module's build.gradle file.

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

The plugin includes the Gradle tasks and properties required to configure and build an app bundle that includes your dynamic feature module.

When Android Studio creates your dynamic feature module, it makes it visible to the base module by adding the android.dynamicFeatures property to the base module's build.gradle file.

Open app/build.gradle and you should see the following:

dynamicFeatures = [":payments"]

Additionally, Android Studio includes the base module as a dependency of the dynamic feature module. Open payments/build.gradle and you should see the following:

dependencies {
   ...
   implementation project(':app')
}

Many of the properties available to the standard application plugin are also available to your dynamic feature module. Because each dynamic feature module depends on the base module, it also inherits certain configurations. So, you should omit the following in the dynamic feature module's build.gradle file:

Congratulations! You've completed all the steps. In this codelab, you learned about the Android App Bundle format, examined generated APKs using bundletool and created a dynamic feature module.

Throughout this codelab, you worked with debug builds. Visit https://developer.android.com/guide/app-bundle/ for more details about creating signed bundles and how to deploy them on the Play Store.