App Bundle and Sideloading: how to prevent crashes

Sideloading can cause crashes when used with Android App Bundle. Google is pushing Android developers to publish their apps to Google Play using the new Android App Bundle format. While it comes with benefits, it can also cause your app to crash, if users sideload your app. We’ll explore why this happens and how to fix it.

Android App Bundle (AAB) enables smaller app downloads and updates, increasing the chances that more people will install your app. Developers benefit as well by only having to build a single artifact instead of multiple APKs tailored to various types of devices.

App Bundle and Sideloading

The Play Store will take care of installing the right set of split APKs from the App Bundle for each user’s device configuration. But what happens, if users bypass Google Play and sideload the app? Sideloading has been popular (APKMirror anyone?) to get early access to new versions of an app or to not waste expensive data volume on app downloads (relevant e.g. in various parts of Africa and India).

To explain how sideloading can break your app, let’s first have a look at what Google Play would actually install onto a device when you publish an App Bundle. For this we use bundletool, which Android Studio and Google Play also use, to convert an App Bundle to a set of APKs for a specific device. We’ll build an App Bundle for our ObjectBox Kotlin example app and start an emulator running Android Pie (you can connect a real device as well). Then we can run the command:

java -jar bundletool-all-0.10.0.jar build-apks \ --bundle=build/outputs/bundle/release/android-app-kotlin.aab \ --output=connected.apks \ --connected-device 1 2 3 4 java - jar bundletool - all - 0.10.0.jar build - apks \ -- bundle = build / outputs / bundle / release / android - app - kotlin . aab \ -- output = connected . apks \ -- connected - device

If we extract the created connected.apks file (e.g. using unzip or 7-Zip) we find three split APKs. A master APK containing the app’s manifest and all of its code. An x86 APK containing the ObjectBox native library. And a xxhdpi APK with some resources specific for the screen density of the emulator device. Depending on your app (if it uses dynamic feature modules or includes translations) and connected device there might be more or less split APKs. But let’s stick with these three.

Out of the three only the master APK can be installed on its own. The others will fail to install. You can try this yourself by dropping the APKs onto an emulator.

Crashing with LinkageError

Now how does this affect sideloading? You can probably already see the issue: due to lack of awareness that you are using the cool new App Bundle format, users only share the master APK. And as mentioned it installs just fine. However, at runtime your app might access a native library like ObjectBox or some density-specific resources. As these are not part of the master APK your app will crash. Bummer.

E/AndroidRuntime: FATAL EXCEPTION: main<br>Process: io.objectbox.example.kotlin, PID: 5291 java.lang.LinkageError: Loading ObjectBox native library failed: vendor=The Android Project,os=linux,arch=null,android=true,linux=true at io.objectbox.internal.NativeLibraryLoader.<clinit>(NativeLibraryLoader.java:108) at io.objectbox.internal.NativeLibraryLoader.ensureLoaded(Unknown Source:0) at io.objectbox.BoxStore.<init>(BoxStore.java:197) at io.objectbox.BoxStoreBuilder.build(BoxStoreBuilder.java:383) at io.objectbox.example.kotlin.ObjectBox.init(ObjectBox.kt:17) at io.objectbox.example.kotlin.App.onCreate(App.kt:13)</init></clinit> 1 2 3 4 5 6 7 8 9 E / AndroidRuntime : FATAL EXCEPTION : main < br > Process : io . objectbox . example . kotlin , PID : 5291 java . lang . LinkageError : Loading ObjectBox native library failed : vendor = The Android Project , os = linux , arch = null , android = true , linux = true at io . objectbox . internal . NativeLibraryLoader . < clinit > ( NativeLibraryLoader . java : 108 ) at io . objectbox . internal . NativeLibraryLoader . ensureLoaded ( Unknown Source : 0 ) at io . objectbox . BoxStore . < init > ( BoxStore . java : 197 ) at io . objectbox . BoxStoreBuilder . build ( BoxStoreBuilder . java : 383 ) at io . objectbox . example . kotlin . ObjectBox . init ( ObjectBox . kt : 17 ) at io . objectbox . example . kotlin . App . onCreate ( App . kt : 13 ) < / init > < / clinit >

Detecting and fixing broken installations

But fret not! To make sure your users can enjoy a working app Google provides the Play Core library that can detect incorrect installs (since version 1.6.0). Simply add the dependency:

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

And add a check in your Application class:

override fun onCreate() { if (MissingSplitsManagerFactory.create(this) .disableAppIfMissingRequiredSplits()) { return // Skip app initialization. } super.onCreate() ObjectBox.init(this) } 1 2 3 4 5 6 7 8 override fun onCreate ( ) { if ( MissingSplitsManagerFactory . create ( this ) . disableAppIfMissingRequiredSplits ( ) ) { return // Skip app initialization. } super . onCreate ( ) ObjectBox . init ( this ) }

If you do not have an Application class or are using content providers check the full documentation. To see this as part of a working app look at the ObjectBox Kotlin example.

Et voilà, instead of your app crashing users will see a dialog asking them to reinstall the app from the Google Play Store (or “an official store” if the library can’t detect it).

How it works

So how does Play Core know if parts of your app are missing? The library is obfuscated – as most Play libraries are. However, decompiling (just open the class files in Android Studio) allows some basic analysis.

At first, it verifies that the device is API level 21 or higher. Older versions of Android do not support split APKs, so no need to check on these devices.

Then it makes sure that the currently installed version of your app actually requires APK splits. After all, you might still distribute your app as a full blown APK to some devices. This check just looks for a special meta-data manifest tag. This tag is added by bundletool (read Play Store) when converting an App Bundle to a set of split APKs (drop the master APK onto Android Studio and check AndroidManifest.xml).

<meta-data android:name="com.android.vending.splits.required" android:value="true" /> 1 2 3 < meta - data android : name = "com.android.vending.splits.required" android : value = "true" / >

Now that it is certain your app requires multiple APKs for installation it asks PackageManager for the PackageInfo of your app. Starting with API level 21 this has a splitNames property which has the names of any installed split APKs for your app package. If it is empty one or more APKs were not installed and the user is warned to reinstall the app. Straightforward.

Curiously it also warns you, if there is only one entry with an empty name. Please let us know in the comments why you think that is.

And that is it. Make sure to add the Play Core detection if your app is using ObjectBox and App Bundle to avoid sideloading crashes and keep your users happy.