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

2

65

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 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.

Shitty answered 23/9, 2018 at 20:16 Comment(12)
I'm not fully familiar with how split apks work, but I wonder if @drawable/logo is only present for certain screen densities, and therefore isn't included for e.g. xxhdpi devicesFreezedrying
@BenP. logo is present for all densities. Furthermore, I've already tried installing all versions of the app on different devices and never got a crash. I've also verified that even if you only put 1 version of image in your aab (say only xxhdpi version) the resulting apk will still work on lower and higher density devices.Shitty
I'm getting about 20 similar crashes per day. One user even have contacted me, and told be that app crashes from the start. It turned out he had rooted devices and its OS have been reinstalled. Moto G4 Plus with LinageOS 7.1.2. Maybe Google Play has problems with detecting the devices without original OS?Overgrow
Possible duplicate of Android App Bundle introduces Resource Not found crash in Android appColunga
@Colunga I specifically mentioned the question you are pointing to in my original question. It has different solution so it's not the same.Shitty
@Shitty as of right now, the accepted answer to both questions is identical almost verbatim, and even written by the same author. I suppose this may not have been the case originally. Thanks for referring to it in the original question!Colunga
@Colunga You are right, the other question was asked earlier, but received the same answer one month after my question received answer. I'll close my question then.Shitty
Strange. I cannot close my own question. I can only vote to close it.Shitty
@Shitty after the "sideload" apk detection, would you able to reduce the crashlytics log about "Resource not found"?Rajab
@Rajab Yes, this crash disappeared from crashlytics after I've added InvalidInstallationActivity.Shitty
Awesome!!! thanks Alexey & @nick-fortescueRajab
I also get reports of crashes specifically from Nexus 5X and Pixel 3 XL. On the Pixel the app crashes further in the app, so it's apparently only missing some assets. I doubt it has to do with sideloading. Anyway I think I'll just stick to APK for now, it's a small app so it doesn't make much of a difference anyway.Predecessor
H
58

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.

Hass answered 24/9, 2018 at 7:57 Comment(10)
I don't think it works this way. If resulting apk is missing specific density, the device will use another available density. I've tested this with bundletool. If you install ldpi version on xxhdpi device it works. The images are fuzzy, but it doesn't crash. Same for xxxhdpi version on mdpi device. And I don't think google play actually uses "split APKs". If you tap on the app in google play console you can see that it actually uses "standalone" APKs, so each user still gets only 1 apk file in the end, it just contains less resources. This doesn't explain why an image is completely missingShitty
Trust me, Google Play does use split APKs (I work on Google Play). Those standalone APKs are for debugging, not what gets delivered to a user. See this help page for more details: developer.android.com/guide/app-bundle/#dynamic_deliveryHass
Ok, it makes a lot of sense then. It has nothing to do with dpi difference in different apks, its just a sideloading software problem. I'll make my splash screen redirect those users either to google play or official site. Thank you!Shitty
Whilst I thought that this was the most likely cause, I'm seeing a lot of these crashes even with Vector Drawables, which aren't bucketed by density in any way. IMO there's some kind of issue with the new format.Disallow
Bucketing by density is irrelevant. Resources are still delivered by Split APKs in the new format.Hass
I have checked this issue is happening mainly in rooted devices. Launch screen, first line of XML layout crashes with Resources$NotFoundException.Whatnot
@NickFortescue , how about the following situation: I've uploaded my app for the first time for Internal Testing only, it has been rejected due to some missing descriptions that the Family Program requires, so nobody could have tested it yet. However, I've already received some Crash logs on Crashlytics with the ResourcesNotFoundException (two different pngs, two different devices, Android 8 and 6). Could you explain this to me somehow? I'm not sure whether I should try to fix anything or not, whether to use aab in the release, or stay with apk..Wallenstein
I suspect that is something going wrong in Google's pre-launch report. developer.android.com/distribute/best-practices/launch/…. I'll investigate. @KasiaK.Hass
Hi @NickFortescue. We see the same issue in our app. It seems, that the OEM backup tools (from Samsung and Huawei) are the problem, because the crash rate was increased after xmas (new devices!?). What's the best practice for that issue? Because we need PNGs for assets, so we can't switch completely to VectorDrawables. I didn't know, if the recovered app shows PlayStore as install source. If not, I can show an AlertDialog to the user to reinstall the app, but without the knowledge, I can't do anything. - PS: In our app we disallowed the backup.Pearliepearline
Faced the same issue, faulty APK was published on a public forum, which resulted in a series of spikes of similar crashes. To reproduce it I used APK Extractor tool available on Google Play. The result of its work works on the devices with the same density, but crashes on the rest.Dietitian
W
4

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.

Wantage answered 27/5, 2021 at 13:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.