Should You Consider Android App Bundles?Previously, Android users were required to install the same monolithic APK, which meant that they downloaded everything -- including things they may not have needed. Users downloaded resources for all possible device configurations and code for features they may not even use, which is a waste of both device storage and data usage. With over 5 screen densities, 40 languages and several CPU architectures being supported by Android, users have been losing memory on their phones to unncessary code and resources.

Android App Bundles were announced at Google I/O 2018. AABs are a new upload format for the Google Play Store that allow developers to "generate and serve optimized APKs for each user's device configuration." Simply put, users of apps that use Android App Bundles download only the code and resources they need to run your app. With Android App Bundles, the Play Store will provide an APK to users that is optimized for:

  • Installed device languages - Only resources for installed languages will be included
  • Screen density - Only image assets for the device's screen density will be included
  • Hardware architecture - Only native libraries for the device's CPU architecture will be included

App Bundles also allow for Dynamic Feature Modules which allow the user to load the code and resources for features at runtime -- when they first use the feature. The application developer has the flexibility to determine if they should use Dynamic Feature Modules, and what to package in these modules.

When uploading an App Bundle to the Play Store, users will download a base APK (common to every device) as well as a set of configuration APK's (device-specific). Dynamic Feature Modules allow for an additional set of APK's to be installed at run-time.

Configuration APKs and Dynamic Feature APKsLeft: Configuration APKs, Right: Dynamic Feature APKs

In our evaluation of Android App Bundles, we sought to answer the following questions:

  1. How do you deliver APK's with Android App Bundles?
  2. How much size savings is there with Android App Bundles?
  3. Are there any caveats that would stop me from using Android App Bundles?
  4. How much coding is involved in building Dynamic Features Modules?
  5. How will Dynamic Feature modules affect your build and testing process?

The Experiment

We decided to build an Android application as a proof of concept to test how much memory Android App Bundles could save a user. We built this application so that its APK would contain a large amount of images and text across various densities and languages. We knew that this use case could potentially highlight a significant savings from App Bundles.

From an end-user perspective, the application was a travel guide that provided information on popular travel destinations. Each of our 17 travel destinations contained 3 images with 6 screen densities for each image, as well as a lengthy description for each city that was defined in 11 different languages. When we built our monotholic APK, the size turned out to be a whopping 181.2 MB.

City selectionsDubaiDubai

Following Google's documentation, we built an Android App Bundle (AAB) in Android Studio. This was a simple process that involved creating and executing a new run configuration. Once built, the AAB turned out to be 180.4 mb in size, very similar to the monolithic APK.

Google provides a tool to test AAB files called bundletool. Using bundletool, you can generate an APK set (APKS) from an AAB file. We ran the following command:

java -jar bundletool-all-0.3.3.jar build-apks --bundle=bundle.aab-- output=app.apks --ks=my-release-key.keystore --ks-key-alias=traveldestinations --ks-pass=pass:password

This generated an APKS file that contained a base APK as well as APK's for each screen denstiy. Unfortunately, it looks like bundletool currently only supports optimization for screen density (default), language or abi -- but not all three at the same time. From Google's documentation on how the Play Store optimizes APK's, all three aspects will be optimized in parallel.

We generated an additional APKS optimized for languages with the following command:

java -jar bundletool-all-0.3.3.jar build-apks --bundle=bundle.aab --output=app_optimized_for_language.apks --ks=my-release-key.keystore --ks-key-alias=traveldestinations --ks-pass=pass:password --optimize-for=LANGUAGE

From here, we generated APK's for a few different device specifications in order to get an idea of what the actual installation size would be after these optimizations. You can write device specifications on your own, or generate these JSON files with the following command (which should create a device spec for your current emulator):

java -jar bundletool-all-0.3.3.jar get-device-spec --output=device-spec.json

We created a few of these device specifications and ran the following command to generate an APKS for each:

java -jar bundletool-all-0.3.3.jar extract-apks --apks=app.apks --output-dir=device-apks --device-spec=device-spec.json

From here, we were able to review our results and see how much size savings App Bundles provided.

Analyzing the Results

It became quickly apparent that the extract-apks command would install two APK's: the base APK, as well as a configuration APK. For our APKs optimized for screen density, here's what a total installation size might look like:

  • MDPI: 8.9 MB
  • HDPI: 16.1 MB
  • XHDPI: 25.9 MB
  • XXHDPI: 51.6 MB
  • XXXHDPI: 85.8 MB

(Sidenote: Because our application was very simple code-wise, the images were a significant contributor to the application size -- Most production applications probably won't have xxxhdpi images be 46% of the app.)

Drawable memory usage

Memory Usage of Image Resources by Screen Density

And for our APK's optimized for language, our base APK was similar to the monolithic APK at 180.1 MB. Each language-specific configuration APK was around 100 KB. Thus, if a user were to only have one language installed (out of 11 that we supported), they would save close to a MB of memory. Remember, AAB's uploaded to the Play Store will be optimized for both screen density and language.

Our application did not contain any native libraries. But if it did, this would have represented an additional source of savings. All in all, users would have really benefited from App Bundles, saving anywhere from 55% - 97% in memory. Wow! Granted, we designed our application with App Bundle savings in mind, but it was nice to see results that were this dramatic.

Google compiled data on average savings for popular applications in the Play Store and shared the following graphic at Google I/O. It's interesting that the savings they calculated came mostly from language support because from our results we would have guessed it would be density.

Average savings from config splits

Average Savings from Config Splits for the apps in PlayStore with over 1 million downloads, from the Google I/O 2018 session

Building a Dynamic Feature Module

We took our experiment one step further by creating a dynamic feature module for one of our travel destinations. Following Google's documentation, we simply created a new module, selected "Dynamic Feature Module", gave it a name and a base package and et voila -- we had a new dynamic feature module in our application. Android Studio had generated a new module, including all of the necessary build and configuration files. From here, we moved all of the images and content for our destination into the new module's resource folders.

But how do you use a dynamic feature module in your application? Google's Play Core Library allows you to programmatically download Dynamic Feature Modules at runtime. Before navigating the user to one of these features, you must ensure that the user has downloaded the module first. This brings about user experience concerns: how do you signify to the user that they need to download code in order to navigate to the feature? And, what do you show the user if the downloading process fails?

We decided to show a simple full-screen progress indicator when the user is downloading our module. However, if the amount of code and resources in a feature is enough to warrant its own feature module, this will probably be a download that will take a signficant amount of time. You may want to take extra care with messaging to the user what is actually happening behind the scenes.

Our installation code followed Google's example and was rather simple, with callback handling for when the download is successfull or when it fails due to an error.

SplitInstallRequest request =
 SplitInstallRequest
 .newBuilder()
 .addModule("venice")
 .build();

splitInstallManager
 .startInstall(request)
 .addOnSuccessListener(new OnSuccessListener() {
 @Override
 public void onSuccess(Integer integer) {
 // Successful download; navigate to the Venice destination
 hideLoadingFeature();
 navigateToVenice(position);
 }
 })
 .addOnFailureListener(new OnFailureListener() {
 @Override
 public void onFailure(Exception e) {
 // Failed download; let the user know
 showLoadingFeatureError();
 }
 });

After a successful installation, we fired off an Intent to start an Activity within this feature.

Intent intent = new Intent().setClassName(getPackageName(), "examples.android.captech.venice.activity.VeniceDestinationActivity");
intent.putExtra("destination_extra", position);
startActivity(intent);

Eventually, we got everything working and our application was able to load our travel destination that was included in the Dynamic Feature Module.

Thoughts On Dynamic Feature Modules

Overall, implementing our dynamic feature module was pretty simple. However, in a real-world scenario there needs to be signficiant thought around the user experience of downloading a module. This might be a difficult experience to design, because there is currently no analytics data on user satisfaction for applications that have feature downloads at runtime. For existing large applications, there will need to be thought placed into which features are good candidates to become dynamic feature modules. Ideally, these features will not necessarily be "large" feature in terms of user experience, but rather features that contain a lot of code and resources. For instance, if a rarely-used feature is dependent on a large library that is not needed by the rest of the application, it is a candidate for becoming a dynamic feature module.

Our Dynamic Feature Module's entry point was by way of the Intent system; but this requires your feature to live in its own Activity. But what if your application architecture has your feature living inside another component, such as a Fragment? Google's sample did not demonstrate if there was a way to dynamically load features that live inside Fragments. This means that you may have to architect and possibly refactor part of your application around supporting Dynamic Feature Modules.

Another caveat to Dynamic Feature Modules is that users running on Android 6.0 or lower devices will have to completely restart your app if they install a module. But, you can get around this if you include support for the SplitCompat library in your Manifest.

Testing Dynamic Feature Modules

We were curious as to how you would go about testing an application that has Dynamic Feature Modules. If the Play services serves up these modules, wouldn't your application need to be in the Play Store in order to be tested? It appears that Google's answer to this is the new Play Store Test Track. This new Play Store feature allows app developers to upload test builds that precede Alpha or Beta builds. These builds can be distributed to a closed set of internal testers.

You can still build non-optimized (monolithic) APK's for your testers. These APK's can be distributed via your existing test build distribution channel. However, these APK's will not allow you to test the loading (and loading error) behavior of these modules.

Final Thoughts

We feel that Android App Bundles are going to be incredibly beneficial to the Android application ecosystem. An overall reduction in the installation size for Android applications should help to reduce a barrier that must always be overcome with new user adoption: convincing a user to commit to downloading your application. At this point, if you are enrolled in Google Play App Signing there is no reason not to move forward with adopting App Bundles.

Dynamic Feature Modules must be well-designed with the user experience in mind. However, they can provide Android applications with an advantage that the web has had over native applications in the past: downloading what you need, when you need it. Application developers should absolutely take advantage of Dynamic Feature Modules if they have use cases that could benefit from this functionality; this is assuming they plan to enroll in the Play Store Test Track.

We're excited to see how App Bundles play out, and hope that they serve the Android community well.

Helpful Resources

Check out the Google IO Session on Android App Bundles!

Also, here's the Android Developers Documentation on Android App Bundles!

Here's the sample application's code!