Dexpreopt и <uses-library> чеки

В Android 12 внесены изменения в систему сборки AOT-компиляции файлов DEX (dexpreopt) для модулей Java, имеющих зависимости <uses-library> . В некоторых случаях эти изменения в системе сборки могут привести к поломке сборки. Используйте эту страницу, чтобы подготовиться к поломкам, и следуйте рецептам на этой странице, чтобы исправить и смягчить их последствия.

Dexpreopt — это процесс предварительной компиляции библиотек и приложений Java. Dexpreopt происходит на хосте во время сборки (в отличие от dexopt , который происходит на устройстве). Структура зависимостей общей библиотеки, используемая модулем Java (библиотекой или приложением), известна как контекст загрузчика классов (CLC). Чтобы гарантировать корректность dexpreopt, CLC времени сборки и времени выполнения должны совпадать. CLC времени сборки — это то, что компилятор dex2oat использует во время dexpreopt (он записывается в файлах ODEX), а CLC времени выполнения — это контекст, в котором предварительно скомпилированный код загружается на устройство.

Эти CLC времени сборки и времени выполнения должны совпадать по соображениям корректности и производительности. Для корректности необходимо обрабатывать повторяющиеся классы. Если зависимости общей библиотеки во время выполнения отличаются от тех, которые используются для компиляции, некоторые классы могут быть решены по-другому, что приведет к тонким ошибкам во время выполнения. На производительность также влияют проверки во время выполнения на наличие повторяющихся классов.

Затронутые варианты использования

Первая загрузка — это основной вариант использования, на который влияют эти изменения: если ART обнаруживает несоответствие между CLC во время сборки и во время выполнения, он отклоняет артефакты dexpreopt и вместо этого запускает dexopt. Для последующих загрузок это нормально, поскольку приложения можно декодировать в фоновом режиме и сохранять на диске.

Затронутые области Android

Это влияет на все приложения и библиотеки Java, которые во время выполнения зависят от других библиотек Java. В Android есть тысячи приложений, сотни из которых используют общие библиотеки. Партнеры также затронуты, поскольку у них есть свои собственные библиотеки и приложения.

Прервать изменения

Системе сборки необходимо знать зависимости <uses-library> , прежде чем она будет генерировать правила сборки dexpreopt. Однако он не может получить прямой доступ к манифесту и прочитать в нем теги <uses-library> , поскольку системе сборки не разрешено читать произвольные файлы при создании правил сборки (по соображениям производительности). Более того, манифест может быть упакован в APK или в готовую сборку. Поэтому информация <uses-library> должна присутствовать в файлах сборки ( Android.bp или Android.mk ).

Ранее ART использовал обходной путь, который игнорировал зависимости общих библиотек (известный как &-classpath ). Это было небезопасно и приводило к незначительным ошибкам, поэтому в Android 12 обходной путь был удален.

В результате модули Java, которые не предоставляют правильную информацию <uses-library> в своих файлах сборки, могут привести к сбоям сборки (вызванным несоответствием CLC во время сборки) или регрессии времени первой загрузки (вызванной CLC во время загрузки). несоответствие, за которым следует dexopt).

Путь миграции

Выполните следующие действия, чтобы исправить сломанную сборку:

  1. Глобально отключите проверку времени сборки для конкретного продукта, установив

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    в make-файле продукта. Это исправляет ошибки сборки (за исключением особых случаев, перечисленных в разделе «Устранение неисправностей »). Однако это временное решение, и оно может привести к несоответствию CLC во время загрузки, за которым последует dexopt.

  2. Исправьте модули, которые вышли из строя до того, как вы глобально отключили проверку времени сборки, добавив необходимую информацию <uses-library> в их файлы сборки (подробнее см. в разделе Исправление ошибок ). Для большинства модулей это требует добавления нескольких строк в Android.bp или Android.mk .

  3. Отключите проверку во время сборки и dexpreopt для проблемных случаев для каждого модуля. Отключите dexpreopt, чтобы не тратить время и память на сборку артефактов, которые отклоняются при загрузке.

  4. Глобально повторно включите проверку времени сборки, отключив PRODUCT_BROKEN_VERIFY_USES_LIBRARIES , установленную на шаге 1; сборка не должна завершиться неудачей после этого изменения (из-за шагов 2 и 3).

  5. Исправьте модули, которые вы отключили на шаге 3, по одному, затем снова включите dexpreopt и проверку <uses-library> . При необходимости сообщите об ошибках.

Проверки <uses-library> во время сборки применяются в Android 12.

Устранить поломки

В следующих разделах рассказывается, как устранить определенные типы поломок.

Ошибка сборки: несоответствие CLC.

Система сборки выполняет проверку согласованности во время сборки между информацией в файлах Android.bp или Android.mk и манифестом. Система сборки не может читать манифест, но может генерировать правила сборки для чтения манифеста (при необходимости извлекая его из APK) и сравнивать теги <uses-library> в манифесте с информацией <uses-library> в файлы сборки. Если проверка не удалась, ошибка выглядит следующим образом:

error: mismatch in the <uses-library> tags between the build system and the manifest:
    - required libraries in build system: []
                     vs. in the manifest: [org.apache.http.legacy]
    - optional libraries in build system: []
                     vs. in the manifest: [com.x.y.z]
    - tags in the manifest (.../X_intermediates/manifest/AndroidManifest.xml):
        <uses-library android:name="com.x.y.z"/>
        <uses-library android:name="org.apache.http.legacy"/>

note: the following options are available:
    - to temporarily disable the check on command line, rebuild with RELAX_USES_LIBRARY_CHECK=true (this will set compiler filter "verify" and disable AOT-compilation in dexpreopt)
    - to temporarily disable the check for the whole product, set PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true in the product makefiles
    - to fix the check, make build system properties coherent with the manifest
    - see build/make/Changes.md for details

Как следует из сообщения об ошибке, существует несколько решений, в зависимости от срочности:

  • Для временного исправления для всего продукта установите PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true в make-файле продукта. Проверка согласованности во время сборки по-прежнему выполняется, но сбой проверки не означает сбой сборки. Вместо этого сбой проверки заставляет систему сборки понизить фильтр компилятора dex2oat для verify в dexpreopt, что полностью отключает AOT-компиляцию для этого модуля.
  • Для быстрого глобального исправления с помощью командной строки используйте переменную среды RELAX_USES_LIBRARY_CHECK=true . Он имеет тот же эффект, что и PRODUCT_BROKEN_VERIFY_USES_LIBRARIES , но предназначен для использования в командной строке. Переменная среды переопределяет переменную продукта.
  • Чтобы устранить основную причину ошибки, сообщите системе сборки о тегах <uses-library> в манифесте. Проверка сообщения об ошибке показывает, какие библиотеки вызывают проблему (как и проверка AndroidManifest.xml или манифеста внутри APK, который можно проверить с помощью ` aapt dump badging $APK | grep uses-library ).

Для модулей Android.bp :

  1. Найдите недостающую библиотеку в свойстве libs модуля. Если они есть, Сунг обычно добавляет такие библиотеки автоматически, за исключением следующих особых случаев:

    • Библиотека не является библиотекой SDK (она определена как java_library , а не java_sdk_library ).
    • Имя библиотеки (в манифесте) отличается от имени ее модуля (в системе сборки).

    Чтобы временно исправить это, добавьте provides_uses_lib: "<library-name>" в определение библиотеки Android.bp . Для долгосрочного решения устраните основную проблему: преобразуйте библиотеку в библиотеку SDK или переименуйте ее модуль.

  2. Если предыдущий шаг не дал решения, добавьте uses_libs: ["<library-module-name>"] для обязательных библиотек или optional_uses_libs: ["<library-module-name>"] для дополнительных библиотек в Android.bp определение модуля. Эти свойства принимают список имен модулей. Относительный порядок библиотек в списке должен совпадать с порядком в манифесте.

Для модулей Android.mk :

  1. Проверьте, отличается ли имя библиотеки (в манифесте) от имени ее модуля (в системе сборки). Если это так, исправьте это временно, добавив LOCAL_PROVIDES_USES_LIBRARY := <library-name> в файл Android.mk библиотеки или добавьте provides_uses_lib: "<library-name>" в файл Android.bp библиотеки (в обоих случаях возможны, поскольку модуль Android.mk может зависеть от библиотеки Android.bp ). Для долгосрочного решения устраните основную проблему: переименуйте библиотечный модуль.

  2. Добавьте LOCAL_USES_LIBRARIES := <library-module-name> для необходимых библиотек; добавьте LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> для дополнительных библиотек в определение модуля Android.mk . Эти свойства принимают список имен модулей. Относительный порядок библиотек в списке должен быть таким же, как в манифесте.

Ошибка сборки: неизвестный путь к библиотеке

Если система сборки не может найти путь к jar-файлу <uses-library> DEX (либо путь сборки на хосте, либо путь установки на устройстве), сборка обычно завершается неудачно. Невозможность найти путь может указывать на то, что библиотека настроена каким-то неожиданным образом. Временно исправьте сборку, отключив dexpreopt для проблемного модуля.

Android.bp (свойства модуля):

enforce_uses_libs: false,
dex_preopt: {
    enabled: false,
},

Android.mk (переменные модуля):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

Сообщите об ошибке, чтобы изучить любые неподдерживаемые сценарии.

Ошибка сборки: отсутствует зависимость библиотеки

Попытка добавить <uses-library> X из манифеста модуля Y в файл сборки для Y может привести к ошибке сборки из-за отсутствия зависимости X.

Это пример сообщения об ошибке для модулей Android.bp:

"Y" depends on undefined module "X"

Это пример сообщения об ошибке для модулей Android.mk:

'.../JAVA_LIBRARIES/com.android.X_intermediates/dexpreopt.config', needed by '.../APPS/Y_intermediates/enforce_uses_libraries.status', missing and no known rule to make it

Распространенным источником таких ошибок является то, что имя библиотеки отличается от имени соответствующего модуля в системе сборки. Например, если запись <uses-library> манифеста — com.android.X , но имя библиотечного модуля — просто X , это вызывает ошибку. Чтобы решить эту проблему, сообщите системе сборки, что модуль с именем X предоставляет <uses-library> с именем com.android.X .

Это пример библиотек Android.bp (свойство модуля):

provides_uses_lib: “com.android.X”,

Это пример библиотек Android.mk (переменная модуля):

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

Несоответствие CLC во время загрузки

При первой загрузке найдите в logcat сообщения, связанные с несоответствием CLC, как показано ниже:

$ adb wait-for-device && adb logcat \
  | grep -E 'ClassLoaderContext [a-z ]+ mismatch' -A1

Вывод может содержать сообщения вида, показанного здесь:

[...] W system_server: ClassLoaderContext shared library size mismatch Expected=..., found=... (PCL[]... | PCL[]...)
[...] I PackageDexOptimizer: Running dexopt (dexoptNeeded=1) on: ...

Если вы получите предупреждение о несоответствии CLC, найдите команду dexopt для неисправного модуля. Чтобы это исправить, убедитесь, что проверка времени сборки модуля пройдена. Если это не сработает, возможно, у вас особый случай, который не поддерживается системой сборки (например, приложение, которое загружает другой APK, а не библиотеку). Система сборки не обрабатывает все случаи, поскольку во время сборки невозможно точно знать, что приложение загружает во время выполнения.

Контекст загрузчика классов

CLC представляет собой древовидную структуру, описывающую иерархию загрузчиков классов. Система сборки использует CLC в узком смысле (она охватывает только библиотеки, а не APK или загрузчики пользовательских классов): это дерево библиотек, которое представляет собой транзитивное замыкание всех зависимостей <uses-library> библиотеки или приложения. Элементами верхнего уровня CLC являются прямые зависимости <uses-library> , указанные в манифесте (путь к классам). Каждый узел дерева CLC представляет собой узел <uses-library> , который может иметь свои собственные подузлы <uses-library> .

Поскольку зависимости <uses-library> представляют собой направленный ациклический граф, а не обязательно дерево, CLC может содержать несколько поддеревьев для одной и той же библиотеки. Другими словами, CLC — это граф зависимостей, «развёрнутый» в дерево. Дублирование происходит только на логическом уровне; фактические базовые загрузчики классов не дублируются (во время выполнения для каждой библиотеки существует один экземпляр загрузчика классов).

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

На устройстве (во время выполнения) CLC

PackageManagerframeworks/base ) создает CLC для загрузки модуля Java на устройство. Он добавляет библиотеки, перечисленные в тегах <uses-library> в манифесте модуля, как элементы CLC верхнего уровня.

Для каждой используемой библиотеки PackageManager получает все зависимости <uses-library> (указанные как теги в манифесте этой библиотеки) и добавляет вложенный CLC для каждой зависимости. Этот процесс продолжается рекурсивно до тех пор, пока все конечные узлы построенного дерева CLC не станут библиотеками без зависимостей <uses-library> .

PackageManager знает только об общих библиотеках. Определение общего в этом использовании отличается от его обычного значения (как в разделяемом или статическом). В Android общие библиотеки Java — это те, которые перечислены в конфигурациях XML, установленных на устройстве ( /system/etc/permissions/platform.xml ). Каждая запись содержит имя общей библиотеки, путь к ее файлу jar DEX и список зависимостей (другие общие библиотеки, которые эта библиотека использует во время выполнения и указывает в тегах <uses-library> в своем манифесте).

Другими словами, существует два источника информации, которые позволяют PackageManager создавать CLC во время выполнения: теги <uses-library> в манифесте и зависимости общей библиотеки в конфигурациях XML.

CLC на хосте (во время сборки)

CLC необходим не только при загрузке библиотеки или приложения, но и при их компиляции. Компиляция может происходить либо на устройстве (dexopt), либо во время сборки (dexpreopt). Поскольку dexopt выполняется на устройстве, он содержит ту же информацию, что и PackageManager (манифесты и зависимости общих библиотек). Однако Dexpreopt выполняется на хосте и в совершенно другой среде, и ему приходится получать ту же информацию от системы сборки.

Таким образом, CLC времени сборки, используемый dexpreopt, и CLC времени выполнения, используемый PackageManager , — это одно и то же, но вычисляется двумя разными способами.

CLC времени сборки и времени выполнения должны совпадать, иначе скомпилированный AOT код, созданный dexpreopt, будет отклонен. Чтобы проверить равенство CLC времени сборки и времени выполнения, компилятор dex2oat записывает CLC времени сборки в файлы *.odex (в поле classpath заголовка файла OAT). Чтобы найти сохраненный CLC, используйте следующую команду:

oatdump --oat-file=<FILE> | grep '^classpath = '

Несоответствие CLC во время сборки и во время выполнения сообщается в logcat во время загрузки. Найдите его с помощью этой команды:

logcat | grep -E 'ClassLoaderContext [az ]+ mismatch'

Несоответствие отрицательно сказывается на производительности, так как приводит к тому, что библиотеку или приложение либо декодируют, либо запускают без оптимизации (например, код приложения может потребоваться извлечь в память из APK, а это очень дорогостоящая операция).

Общая библиотека может быть дополнительной или обязательной. С точки зрения dexpreopt, необходимая библиотека должна присутствовать во время сборки (ее отсутствие является ошибкой сборки). Дополнительная библиотека может присутствовать или отсутствовать во время сборки: если она присутствует, она добавляется в CLC, передается в dex2oat и записывается в файл *.odex . Если дополнительная библиотека отсутствует, она пропускается и не добавляется в CLC. Если существует несоответствие между состоянием времени сборки и времени выполнения (в одном случае необязательная библиотека присутствует, а в другом нет), то CLC времени сборки и времени выполнения не совпадают, и скомпилированный код отклоняется.

Подробные сведения о расширенной системе сборки (исправитель манифеста)

Иногда теги <uses-library> отсутствуют в исходном манифесте библиотеки или приложения. Это может произойти, например, если одна из транзитивных зависимостей библиотеки или приложения начинает использовать другой тег <uses-library> , а манифест библиотеки или приложения не обновляется для его включения.

Сунг может автоматически вычислить некоторые недостающие теги <uses-library> для данной библиотеки или приложения, как библиотеки SDK при закрытии транзитивной зависимости библиотеки или приложения. Закрытие необходимо, поскольку библиотека (или приложение) может зависеть от статической библиотеки, которая зависит от библиотеки SDK, и, возможно, снова может транзитивно зависеть через другую библиотеку.

Не все теги <uses-library> можно вычислить таким образом, но, когда это возможно, предпочтительно, чтобы Сунг автоматически добавлял записи манифеста; он менее подвержен ошибкам и упрощает обслуживание. Например, когда многие приложения используют статическую библиотеку, которая добавляет новую зависимость <uses-library> , все приложения необходимо обновлять, что сложно поддерживать.