В 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).
Путь миграции
Чтобы исправить сломанную сборку, выполните следующие действия:
Глобально отключить проверку времени сборки для определенного продукта, установив
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
в makefile продукта. Это исправляет ошибки сборки (за исключением особых случаев, перечисленных в разделе «Исправление ошибок »). Однако это временное решение, которое может привести к несоответствию CLC при загрузке и последующему запуску dexopt.
Исправьте модули, вызвавшие сбой до глобального отключения проверки во время сборки, добавив необходимую информацию
<uses-library>
в их файлы сборки (подробнее см. в разделе «Исправление сбоев »). Для большинства модулей это требует добавления нескольких строк в файлAndroid.bp
илиAndroid.mk
.Отключите проверку во время сборки и dexpreopt для проблемных случаев для каждого модуля. Отключите dexpreopt, чтобы не тратить время сборки и дисковое пространство на артефакты, которые отклоняются при загрузке.
Повторно включите проверку во время сборки в глобальном масштабе, отменив настройку
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES
, которая была установлена на шаге 1; сборка не должна завершиться ошибкой после этого изменения (из-за шагов 2 и 3).По одному исправьте модули, отключенные на шаге 3, затем снова включите dexpreopt и проверку
<uses-library>
. При необходимости сообщите об ошибках.
В Android 12 проверки <uses-library>
во время сборки стали обязательными.
Устранение поломок
В следующих разделах рассказывается, как устранить конкретные типы поломок.
Ошибка сборки: несоответствие 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
:
Найдите отсутствующую библиотеку в свойстве
libs
модуля. Если она там есть, Soong обычно добавляет её автоматически, за исключением следующих особых случаев:- Библиотека не является библиотекой SDK (она определена как
java_library
, а неjava_sdk_library
). - Имя библиотеки (в манифесте) отличается от имени ее модуля (в системе сборки).
Чтобы временно решить эту проблему, добавьте
provides_uses_lib: "<library-name>"
в определение библиотекиAndroid.bp
. Для долгосрочного решения устраните основную проблему: преобразуйте библиотеку в библиотеку SDK или переименуйте её модуль.- Библиотека не является библиотекой SDK (она определена как
Если предыдущий шаг не помог решить проблему, добавьте
uses_libs: ["<library-module-name>"]
для обязательных библиотек илиoptional_uses_libs: ["<library-module-name>"]
для необязательных библиотек в определение модуля в файлеAndroid.bp
. Эти свойства принимают список имён модулей. Относительный порядок библиотек в списке должен совпадать с порядком в манифесте.
Для модулей Android.mk
:
Проверьте, отличается ли имя библиотеки (в манифесте) от имени её модуля (в системе сборки). Если это так, временно исправьте это, добавив
LOCAL_PROVIDES_USES_LIBRARY := <library-name>
в файлAndroid.mk
библиотеки или добавьтеprovides_uses_lib: "<library-name>"
в файлAndroid.bp
библиотеки (оба варианта возможны, поскольку модульAndroid.mk
может зависеть от библиотекиAndroid.bp
). Для долгосрочного решения устраните основную проблему: переименуйте модуль библиотеки.Добавьте
LOCAL_USES_LIBRARIES := <library-module-name>
для обязательных библиотек; добавьтеLOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>
для необязательных библиотек в определение модуля вAndroid.mk
. Эти свойства принимают список имён модулей. Относительный порядок библиотек в списке должен соответствовать порядку в манифесте.
Ошибка сборки: неизвестный путь к библиотеке
Если система сборки не может найти путь к DEX-jar-файлу <uses-library>
(путь сборки на хосте или путь установки на устройстве), сборка обычно завершается неудачей. Отсутствие пути может указывать на непредвиденную конфигурацию библиотеки. Временно исправьте сборку, отключив 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
PackageManager
(в frameworks/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>
, а манифест библиотеки или приложения не обновляется для включения этого тега.
Soong может автоматически вычислить некоторые из недостающих тегов <uses-library>
для заданной библиотеки или приложения, используя библиотеки SDK в замыкании транзитивной зависимости библиотеки или приложения. Замыкание необходимо, поскольку библиотека (или приложение) может зависеть от статической библиотеки, которая, в свою очередь, зависит от библиотеки SDK, и, возможно, может снова транзитивно зависеть через другую библиотеку.
Не все теги <uses-library>
можно вычислить таким образом, но по возможности предпочтительнее позволить Soong автоматически добавлять записи манифеста; это снижает вероятность ошибок и упрощает поддержку. Например, когда множество приложений используют статическую библиотеку, добавляющую новую зависимость <uses-library>
, необходимо обновить все приложения, что сложно поддерживать.