App sometimes crashes with Resources$NotFoundException after switching to android app bundle distribution

AndroidGoogle Play

Android Problem Overview


App has over 20000 monthly active users. It's been available on google play for months. After I've recently switched from distribution with .apk to distribution with .aab, I've started receiving random crashes on crashlytics and google play store. No other significant changes were made in the build that introduced the crashes.

The crash happens on the very first screen of the app, while inflating xml layout. The xml layout in question is a simple splash screen that only contains one image view and one textview. The imageview is android.widget.ImageView, not compat version, and it displays png image, not a vector image. The image is present in all drawable folder variations: drawable, drawable-mdpi, ..., drawable-xxxhdpi.

Fatal Exception: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.company/com.company.ui.splash.SplashActivity}: android.view.InflateException: Binary XML file line #14: Binary XML file line #14: Error inflating class ImageView
          at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
          at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
          at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
          at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
          at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
          at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
          at android.os.Handler.dispatchMessage(Handler.java:106)
          at android.os.Looper.loop(Looper.java:193)
          at android.app.ActivityThread.main(ActivityThread.java:6669)
          at java.lang.reflect.Method.invoke(Method.java)
          at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:495)
          at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

   Caused by android.view.InflateException: Binary XML file line #14: Binary XML file line #14: Error inflating class ImageView


   Caused by android.view.InflateException: Binary XML file line #14: Error inflating class ImageView


   Caused by android.content.res.Resources$NotFoundException: Drawable (missing name) with resource ID #0x7f0800b2


   Caused by android.content.res.Resources$NotFoundException: Unable to find resource ID #0x7f0800b2
          at android.content.res.ResourcesImpl.getResourceName(ResourcesImpl.java:255)
          at android.content.res.ResourcesImpl.loadDrawableForCookie(ResourcesImpl.java:785)
          at android.content.res.ResourcesImpl.loadDrawable(ResourcesImpl.java:631)
          at android.content.res.Resources.loadDrawable(Resources.java:897)
          at android.content.res.TypedArray.getDrawableForDensity(TypedArray.java:955)
          at android.content.res.TypedArray.getDrawable(TypedArray.java:930)
          at android.widget.ImageView.<init>(ImageView.java:189)
          at android.widget.ImageView.<init>(ImageView.java:172)
          at android.support.v7.widget.AppCompatImageView.<init>(AppCompatImageView.java:71)
          at android.support.v7.widget.AppCompatImageView.<init>(AppCompatImageView.java:67)
          at android.support.v7.app.AppCompatViewInflater.createImageView(AppCompatViewInflater.java:181)
          at android.support.v7.app.AppCompatViewInflater.createView(AppCompatViewInflater.java:105)
          at android.support.v7.app.AppCompatDelegateImplV9.createView(AppCompatDelegateImplV9.java:1035)
          at android.support.v7.app.AppCompatDelegateImplV9.onCreateView(AppCompatDelegateImplV9.java:1092)
          at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:772)
          at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730)
          at android.view.LayoutInflater.rInflate(LayoutInflater.java:863)
          at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824)
          at android.view.LayoutInflater.inflate(LayoutInflater.java:515)
          at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
          at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
          at android.support.v7.app.AppCompatDelegateImplV9.setContentView(AppCompatDelegateImplV9.java:287)
          at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:139)
          at com.company.ui.splash.SplashActivity.onCreate(SplashActivity.java:58)
          at android.app.Activity.performCreate(Activity.java:7136)
          at android.app.Activity.performCreate(Activity.java:7127)
          at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
          at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
          at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
          at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
          at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
          at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
          at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
          at android.os.Handler.dispatchMessage(Handler.java:106)
          at android.os.Looper.loop(Looper.java:193)
          at android.app.ActivityThread.main(ActivityThread.java:6669)
          at java.lang.reflect.Method.invoke(Method.java)
          at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:495)
          at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Part of xml file that causes the crash:

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_above="@id/center"
    android:layout_centerHorizontal="true"
    android:src="@drawable/logo" />

The crash happens on all android versions, from 4.1.2 up to 9.0.

Aside from other devices, I've also received crashes from Google Pixel and Nexus 5X, both non-rooted. I happen to own both devices. I've tried installing my app on them from google play and from sideloading services such as pureapk, but I wasn't able to reproduce the crash.

The question is similar to https://stackoverflow.com/questions/50471888/android-app-bundle-introduces-resource-not-found-crash-in-android-app , but in that question, author was able to solve his problems by using vector drawable compat. This is not my case.

It appears that the entire resource folder is missing from the apk somehow, although testing this assumption is hard. I cannot reproduce the issue so I'll have o re-destribute the app and wait for a couple of days to see how the crash has changed, and I'd rather not do testing on live users.

When this bug happens, it usually happens several times in the row for the same devices, which leads me to believe that users who get this bug cannot ever launch the app. Also, I know that some of the users who previously used the app cannot use it anymore.

So, to sum this up:

  1. Crash started to appear right after switching to android application bundle distribution on google play
  2. App crashes on its first attempt to recover drawable resource - a simple png image
  3. The crash is not android-version specific; it happens on both rooted and non-rooted devices
  4. If the user gets this bug, he's probably stuck forever

What is causing this crash? Is there a workaround?

==========

Update: After reading the answer below I've concluded that the only workaround is to detect sideload installation and then open activity without any drawable resources or styles with links to Google Play and official site with old-school apk file. The user can then re-download app from another source.

This is the code I use to detect if app was sideloaded (you might need to remove nativeLibrariesPresent part if there are no native libraries in your app):

private fun isValidInstallation(): Boolean {
    var resourcesPresent: Boolean
    try {
        // Any drawable id will suffice
        val logo = ResourcesCompat.getDrawable(resources, R.drawable.logo_white, null)
        resourcesPresent = logo != null
    } catch (e: Exception) {
        resourcesPresent = false
    }

    if (!resourcesPresent) {
        Timber.e("No drawable resources detected inside app")
    }

    var nativeLibrariesPresent: Boolean
    try {
        val nativeLibraryDir = File(applicationInfo.nativeLibraryDir)
        val primaryNativeLibraries = nativeLibraryDir.list()
        nativeLibrariesPresent = primaryNativeLibraries.isNotEmpty()
    } catch (e: Exception) {
        nativeLibrariesPresent = false
    }

    if (!nativeLibrariesPresent) {
        Timber.e("No native libraries detected inside app")
    }

    return resourcesPresent && nativeLibrariesPresent
}

You'll want to start alternative activity before you do anything inside your main activity:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    if (!isValidInstallation()) {
        val intent = Intent(this, InvalidInstallationActivity::class.java)
        startActivity(intent)
        finish()
        return
    }

    setContentView(R.layout.activity_main)
    ...

InvalidInstallationActivity can use xml layout and it can use string resources if you don't split your aab by language (language { enableSplit = false }), but it cannot use any drawable resources.

Android Solutions


Solution 1 - Android

This is almost certainly users sharing the app, either via P2P sharing programs, or uploading the APK to the web then other users downloading and installing from the web.

People used to dealing with non Android App Bundle apps just transfer and share the main APK. But your App bundle app has lots of "split APKs" for things like the resources, that is how the size saving happens. You can read all about this process on the help page. If a user installs the main APK without installing the right split APKs, then a "Resources Not found" crash will occur the first time the app tries to load a resource.

If you want to support users sideloading your app and just the main APK you could try to detect this situation and display a message to the user (without using any resources) that says "Please install from Google Play". Or you could just decide you aren't going to support users who share APKs in this way.

I suspect in the long run the websites and P2P sharing programs will get better at sharing such APKs properly, so I wouldn't spend too long worrying about it.

Solution 2 - Android

You can solve this by either using com.google.android.play.core.missingsplits.MissingSplitsDetectingApplication as your application or by doing this in your own application:

class MyApplication : Application() {
    override fun onCreate() {
        if (MissingSplitsManagerFactory.create(this).disableAppIfMissingRequiredSplits()) {
            // Skip app initialization when the app is missing required splits.
            return
        }
        super.onCreate()
        // ...
    }
}

You need the google play core dependency for this. FYI: it's already deprecated: missing required splits is already automatically caught with Google Play protect and on devices with Android 10 or higher.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAlexeyView Question on Stackoverflow
Solution 1 - AndroidNick FortescueView Answer on Stackoverflow
Solution 2 - AndroidCristanView Answer on Stackoverflow