В 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. Для последующих загрузок это нормально, поскольку приложения могут быть обработаны с помощью 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в файле makefile продукта. Проверка согласованности во время сборки по-прежнему будет выполняться, но сбой проверки не означает сбой сборки. Вместо этого, сбой проверки заставляет систему сборки понизить уровень фильтра компилятора 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 (она определена как
Если предыдущий шаг не помог решить проблему, добавьте в определение модуля в файле
Android.bpuses_libs: ["<library-module-name>"]для обязательных библиотек илиoptional_uses_libs: ["<library-module-name>"]для необязательных библиотек. Эти свойства принимают список имен модулей. Относительный порядок библиотек в списке должен совпадать с порядком в манифесте.
Для модулей Android.mk :
Проверьте, отличается ли имя библиотеки (в манифесте) от имени модуля (в системе сборки). Если да, временно исправьте это, добавив
LOCAL_PROVIDES_USES_LIBRARY := <library-name>в файлAndroid.mkбиблиотеки или добавивprovides_uses_lib: "<library-name>"в файлAndroid.bpбиблиотеки (возможны оба случая, поскольку модульAndroid.mkможет зависеть от библиотекиAndroid.bp). В качестве долгосрочного решения устраните основную проблему: переименуйте модуль библиотеки.Добавьте в определение модуля в файле
Android.mkLOCAL_USES_LIBRARIES := <library-module-name>для обязательных библиотек; добавьтеLOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>для необязательных библиотек. Эти свойства принимают список имен модулей. Относительный порядок библиотек в списке должен совпадать с порядком в манифесте.
Ошибка сборки: неизвестный путь к библиотеке.
Если система сборки не может найти путь к DEX-файлу <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 ). Каждая запись содержит имя разделяемой библиотеки, путь к её DEX-файлу jar и список зависимостей (другие разделяемые библиотеки, которые эта библиотека использует во время выполнения и указывает в тегах <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 = '
В логах logcat во время загрузки сообщается о несоответствии CLC, возникшем во время сборки и выполнения. Найти это можно с помощью следующей команды:
logcat | grep -E 'ClassLoaderContext [az ]+ mismatch'
Несоответствие негативно сказывается на производительности, поскольку вынуждает библиотеку или приложение либо проходить оптимизацию (dexopting), либо работать без оптимизации (например, код приложения может потребоваться извлечь из APK-файла в память, что является очень ресурсоемкой операцией).
Разделяемая библиотека может быть как необязательной, так и обязательной. С точки зрения dexpreopt, обязательная библиотека должна присутствовать во время сборки (ее отсутствие является ошибкой сборки). Необязательная библиотека может присутствовать или отсутствовать во время сборки: если она присутствует, она добавляется в CLC, передается в dex2oat и записывается в файл *.odex . Если необязательная библиотека отсутствует, она пропускается и не добавляется в CLC. Если возникает несоответствие между состоянием во время сборки и во время выполнения (необязательная библиотека присутствует в одном случае, но отсутствует в другом), то CLC во время сборки и во время выполнения не совпадают, и скомпилированный код отклоняется.
Подробная информация о системе сборки (средство для исправления манифеста)
Иногда в манифесте исходного кода библиотеки или приложения отсутствуют теги <uses-library> . Это может произойти, например, если одна из транзитивных зависимостей библиотеки или приложения начинает использовать другой тег <uses-library> , а манифест библиотеки или приложения не обновлен для его включения.
Soong может автоматически вычислять некоторые отсутствующие теги <uses-library> для данной библиотеки или приложения, как это происходит с библиотеками SDK в рамках транзитивной зависимости этой библиотеки или приложения. Эта зависимость необходима, поскольку библиотека (или приложение) может зависеть от статической библиотеки, которая, в свою очередь, зависит от библиотеки SDK, и, возможно, может снова зависеть транзитивно через другую библиотеку.
Не все теги <uses-library> можно вычислить таким образом, но по возможности предпочтительнее позволить Soong автоматически добавлять записи в манифест; это менее подвержено ошибкам и упрощает обслуживание. Например, когда многие приложения используют статическую библиотеку, которая добавляет новую зависимость <uses-library> , все приложения необходимо обновить, что сложно поддерживать.