Приложение иногда сбой с ресурсами $ NotFoundException после переключения на дистрибутив приложений для Android

Приложение имеет более 20000 ежемесячных активных пользователей. Он был доступен в Google Play в течение нескольких месяцев. После того, как я недавно переключился с дистрибутива с .apk на дистрибутив с .aab, я начал получать случайные сбои на crashlytics и магазине Google Play. Других существенных изменений в сборке, которые привели к сбоям, не было.

Авария происходит на самом первом экране приложения, когда надувается разметка XML. Рассматриваемый макет xml представляет собой простой экран-заставку, который содержит только одно представление изображения и одно представление текста. Imageview - это android.widget.ImageView, а не версия для сравнения, и оно отображает PNG-изображение, а не векторное изображение. Изображение присутствует во всех доступных для рисования папках: 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)

Часть XML файла, которая вызывает сбой:

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

Авария происходит на всех версиях Android, от 4.1.2 до 9.0.

Помимо других устройств, я также получил сбои от Google Pixel и Nexus 5X, оба без рута. Мне довелось владеть обоими устройствами. Я пытался установить на них свое приложение из Google Play и из сторонних сервисов, таких как pureapk, но не смог воспроизвести сбой.

Этот вопрос аналогичен Android App Bundle, который приводит к сбою Resource Not found в приложении Android, но в этом вопросе автору удалось решить свои проблемы с помощью векторного рисованного компата. Это не мой случай.

Похоже, что вся папка ресурсов отсутствует в apk как-то, хотя проверить это предположение сложно. Я не могу воспроизвести проблему, поэтому мне придется перераспределить приложение и подождать пару дней, чтобы увидеть, как изменился сбой, и я бы не хотел проводить тестирование на живых пользователях.

Когда эта ошибка возникает, она обычно происходит несколько раз подряд для одних и тех же устройств, что позволяет мне полагать, что пользователи, получившие эту ошибку, никогда не смогут запустить приложение. Кроме того, я знаю, что некоторые пользователи, которые ранее использовали приложение, больше не могут его использовать.

Итак, подведем итог:
1. Сбой начал появляться сразу после перехода на дистрибутив Android-приложений в Google Play
2. Приложение аварийно завершает свою работу с первой попытки восстановить нарисованный ресурс - простое изображение в формате png
3. Сбой не зависит от версии Android; это происходит как на рутованных, так и на некорневых устройствах
4. Если пользователь получает эту ошибку, он, вероятно, застрял навсегда

Что является причиной этого сбоя? Есть ли обходной путь?

==========

Обновление: после прочтения приведенного ниже ответа я пришел к выводу, что единственным обходным решением является обнаружение установки с боковой загрузкой, а затем открытие активности без каких-либо доступных ресурсов или стилей со ссылками на Google Play и официальный сайт с файлом старой школы apk. Затем пользователь может повторно загрузить приложение из другого источника.

Это код, который я использую, чтобы определить, было ли приложение загружено (возможно, вам придется удалить часть nativeLibrariesPresent если в вашем приложении нет собственных библиотек):

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
}

Вы захотите начать альтернативную деятельность, прежде чем делать что-то внутри своей основной деятельности:

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 может использовать макет xml и строковые ресурсы, если вы не разбиваете свой aab по языку (language { enableSplit = false }), но не может использовать никакие ресурсы для рисования.

Ответ 1

Это почти наверняка пользователи, разделяющие приложение, либо через программы обмена P2P, либо загружающие APK в Интернет, чем другие пользователи, загружающие и устанавливающие из Интернета.

Люди, привыкшие иметь дело с приложениями, не относящимися к Android App Bundle, просто передают и делятся основным APK. Но в вашем приложении есть множество "разделенных APK" для таких вещей, как ресурсы, именно так происходит экономия размера. Вы можете прочитать все об этом процессе на странице помощи. Если пользователь устанавливает основной APK без установки правильных разделенных APK, то при первой попытке приложения загрузить ресурс произойдет сбой "Ресурсы не найдены".

Если вы хотите, чтобы пользователи загружали ваше приложение и только основной APK, вы можете попытаться обнаружить эту ситуацию и показать пользователю (без использования каких-либо ресурсов) сообщение "Пожалуйста, установите из Google Play". Или вы можете просто решить, что не собираетесь поддерживать пользователей, которые делятся APK файлами таким образом.

Я подозреваю, что в долгосрочной перспективе веб-сайты и программы обмена P2P станут лучше в правильном обмене такими APK, поэтому я бы не стал слишком долго беспокоиться об этом.

Ответ 2

@Ник Фортескью

Как насчет этого вопроса? Мы только начинаем реализовывать функцию Dynamic для нескольких модулей в нашем приложении.

Тем не менее, я сталкиваюсь с ResourcesNotFoundException, когда динамический объект пытается накачать разметку res, которая была помещена в res/drawable динамического объекта.

Я разархивировал aab файл и обнаружил, что пакет XML был в папке res динамического_модуля. И наверняка я использую SplitInstallHelper.updateAppInfo(), чтобы обновить контекст после того, как мы загрузили и установили функцию, которая является динамической доставкой.

Можете ли вы помочь мне разобраться с этой проблемой?