Deexpreopt 및 <uses-library> 검사

Android 12에는 <uses-library> 종속성이 있는 Java 모듈에 대한 DEX 파일(dexpreopt)의 AOT 컴파일에 대한 빌드 시스템 변경 사항이 있습니다. 경우에 따라 이러한 빌드 시스템 변경으로 인해 빌드가 중단될 수 있습니다. 이 페이지를 사용하여 파손에 대비하고 이 페이지의 조리법에 따라 파손을 수정하고 완화하십시오.

Deexpreopt는 Java 라이브러리 및 앱의 사전 컴파일 프로세스입니다. Dexpreopt는 빌드 시 호스트에서 발생합니다(디바이스에서 발생하는 dexopt 와 반대). Java 모듈(라이브러리 또는 앱)에서 사용하는 공유 라이브러리 종속성의 구조를 클래스 로더 컨텍스트 (CLC)라고 합니다. dexpreopt의 정확성을 보장하려면 빌드 시간과 런타임 CLC가 일치해야 합니다. 빌드 시간 CLC는 dex2oat 컴파일러가 dexpreopt 시간에 사용하는 것이며(ODEX 파일에 기록됨) 런타임 CLC는 사전 컴파일된 코드가 장치에 로드되는 컨텍스트입니다.

이러한 빌드 시간 및 런타임 CLC는 정확성과 성능을 위해 일치해야 합니다. 정확성을 위해 중복 클래스를 처리해야 합니다. 런타임 시 공유 라이브러리 종속성이 컴파일에 사용된 것과 다른 경우 일부 클래스가 다르게 해결되어 미묘한 런타임 버그가 발생할 수 있습니다. 성능은 중복 클래스에 대한 런타임 검사의 영향도 받습니다.

영향을 받는 사용 사례

첫 번째 부팅은 이러한 변경 사항의 영향을 받는 주요 사용 사례입니다. ART가 빌드 시간과 런타임 CLC 간의 불일치를 감지하면 dexpreopt 아티팩트를 거부하고 대신 dexopt를 실행합니다. 후속 부팅의 경우 앱을 백그라운드에서 선택하여 디스크에 저장할 수 있으므로 괜찮습니다.

Android의 영향을 받는 영역

이는 다른 Java 라이브러리에 대한 런타임 종속성이 있는 모든 Java 앱 및 라이브러리에 영향을 줍니다. Android에는 수천 개의 앱이 있으며 그 중 수백 개는 공유 라이브러리를 사용합니다. 자체 라이브러리와 앱이 있는 파트너도 영향을 받습니다.

주요 변경 사항

빌드 시스템은 dexpreopt 빌드 규칙을 생성하기 전에 <uses-library> 종속성을 알아야 합니다. 그러나 빌드 시스템이 빌드 규칙을 생성할 때(성능상의 이유로) 임의의 파일을 읽을 수 없기 때문에 매니페스트에 직접 액세스하고 그 안의 <uses-library> 태그를 읽을 수 없습니다. 또한 매니페스트는 APK 또는 미리 빌드된 내부에 패키징될 수 있습니다. 따라서 <uses-library> 정보는 빌드 파일( Android.bp 또는 Android.mk )에 있어야 합니다.

이전에 ART는 공유 라이브러리 종속성을 무시하는 해결 방법을 사용했습니다( &-classpath 라고 함). 이것은 안전하지 않고 미묘한 버그를 일으켰으므로 Android 12에서 해결 방법이 제거되었습니다.

결과적으로 빌드 파일에 올바른 <uses-library> 정보를 제공하지 않는 Java 모듈은 빌드 중단 (빌드 시간 CLC 불일치로 인해 발생) 또는 최초 부팅 시간 회귀 (부트 시간 CLC로 인해 발생)를 유발할 수 있습니다. 불일치 다음에 dexopt).

마이그레이션 경로

손상된 빌드를 수정하려면 다음 단계를 따르세요.

  1. 설정하여 특정 제품에 대한 빌드 시간 확인을 전역적으로 비활성화

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    제품 메이크파일에서 이것은 빌드 오류를 수정합니다( 고정 파손 섹션에 나열된 특별한 경우 제외). 그러나 이것은 임시 해결 방법이며 부팅 시 CLC 불일치와 dexopt가 뒤따를 수 있습니다.

  2. 빌드 파일에 필요한 <uses-library> 정보를 추가하여 빌드 시간 검사를 전역적으로 비활성화하기 전에 실패한 모듈을 수정하십시오(자세한 내용은 파손 수정 참조). 대부분의 모듈의 경우 Android.mk 또는 Android.bp 에 몇 줄을 추가해야 합니다.

  3. 모듈별로 문제가 있는 경우 빌드 시간 확인 및 dexpreopt를 비활성화합니다. 부팅 시 거부되는 아티팩트에 빌드 시간과 스토리지를 낭비하지 않도록 dexpreopt를 비활성화합니다.

  4. 1단계에서 설정한 PRODUCT_BROKEN_VERIFY_USES_LIBRARIES 를 설정 해제하여 빌드 시간 검사를 전역적으로 다시 활성화합니다. 이 변경 후에 빌드가 실패하지 않아야 합니다(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

오류 메시지에서 알 수 있듯이 긴급성에 따라 여러 솔루션이 있습니다.

  • 임시 제품 전체 수정 의 경우 제품 makefile에서 PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true 를 설정하십시오. 빌드 시간 일관성 검사는 계속 수행되지만 검사 실패가 빌드 실패를 의미하지는 않습니다. 대신, 확인 실패는 빌드 시스템이 dex2oat 컴파일러 필터를 다운그레이드하여 dexpreopt에서 verify 하도록 하며, 이는 이 모듈에 대해 AOT 컴파일을 완전히 비활성화합니다.
  • 빠른 전역 명령줄 수정 을 위해 RELAX_USES_LIBRARY_CHECK=true 환경 변수를 사용합니다. PRODUCT_BROKEN_VERIFY_USES_LIBRARIES 와 동일한 효과를 갖지만 명령줄에서 사용하기 위한 것입니다. 환경 변수는 제품 변수를 재정의합니다.
  • 오류의 근본 원인을 수정 하려면 빌드 시스템이 매니페스트의 <uses-library> 태그를 인식하도록 하십시오. 오류 메시지 검사는 문제를 일으키는 라이브러리를 보여줍니다( AndroidManifest.xml 또는 ` aapt dump badging $APK | grep uses-library `로 확인할 수 있는 APK 내부 ​​매니페스트 검사와 마찬가지로).

Android.bp 모듈의 경우:

  1. 모듈의 libs 속성에서 누락된 라이브러리를 찾으십시오. 있는 경우 Soong은 일반적으로 다음과 같은 특별한 경우를 제외하고 이러한 라이브러리를 자동으로 추가합니다.

    • 라이브러리는 SDK 라이브러리가 아닙니다( java_library 가 아닌 java_sdk_library 로 정의됨).
    • 라이브러리의 모듈 이름(빌드 시스템)과 다른 라이브러리 이름(매니페스트)이 있습니다.

    이 문제를 일시적으로 수정하려면 Android.bp 라이브러리 정의에 provide_use_lib provides_uses_lib: "<library-name>" 을 추가하세요. 장기적인 솔루션의 경우 근본적인 문제를 수정하십시오. 라이브러리를 SDK 라이브러리로 변환하거나 해당 모듈의 이름을 바꾸십시오.

  2. 이전 단계에서 해결 방법을 제공하지 않은 경우 필수 라이브러리의 경우 uses_libs: ["<library-module-name>"] 를 추가하거나 선택적 라이브러리의 경우 optional_uses_libs: ["<library-module-name>"]Android.bp 모듈의 Android.bp 정의. 이러한 속성은 모듈 이름 목록을 허용합니다. 목록에 있는 라이브러리의 상대적 순서는 매니페스트의 순서와 동일해야 합니다.

Android.mk 모듈의 경우:

  1. 라이브러리의 모듈 이름(빌드 시스템)과 다른 라이브러리 이름(매니페스트)이 있는지 확인합니다. 그렇다면 라이브러리의 Android.mk 파일에 LOCAL_PROVIDES_USES_LIBRARY := <library-name> 을 추가하여 임시로 수정하거나 라이브러리의 Android.mk 파일에 provide_uses_lib 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 정의에 추가합니다. 이러한 속성은 모듈 이름 목록을 허용합니다. 목록에 있는 라이브러리의 상대적 순서는 매니페스트에서와 동일해야 합니다.

빌드 오류: 알 수 없는 라이브러리 경로

빌드 시스템이 <uses-library> DEX jar에 대한 경로(호스트의 빌드 시간 경로 또는 장치의 설치 경로)를 찾을 수 없으면 일반적으로 빌드에 실패합니다. 경로를 찾는 데 실패하면 라이브러리가 예상치 못한 방식으로 구성되었음을 나타낼 수 있습니다. 문제가 있는 모듈에 대해 dexpreopt를 비활성화하여 빌드를 임시로 수정합니다.

Android.bp(모듈 속성):

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

Android.mk(모듈 변수):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

지원되지 않는 시나리오를 조사하려면 버그를 신고하세요.

빌드 오류: 라이브러리 종속성 누락

모듈 Y의 매니페스트에서 Y에 대한 빌드 파일에 <uses-library> X를 추가하려고 하면 누락된 종속성 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 라는 모듈이 com.android.X 라는 <uses-library> 제공한다고 빌드 시스템에 알립니다.

다음은 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> 노드입니다.

<uses-library> 종속성은 방향성 비순환 그래프이고 반드시 트리가 아니므로 CLC는 동일한 라이브러리에 대한 여러 하위 트리를 포함할 수 있습니다. 즉, CLC는 트리에 "펼쳐진" 종속성 그래프입니다. 복제는 논리적 수준에서만 이루어집니다. 실제 기본 클래스 로더는 복제되지 않습니다(런타임에 각 라이브러리에 대해 단일 클래스 로더 인스턴스가 있음).

CLC는 라이브러리 또는 앱에서 사용하는 Java 클래스를 확인할 때 라이브러리의 조회 순서를 정의합니다. 라이브러리에 중복 클래스가 포함될 수 있고 클래스가 첫 번째 일치 항목으로 확인되기 때문에 조회 순서가 중요합니다.

장치(런타임) CLC

PackageManager ( frameworks/base 에 있음)는 Java 모듈을 디바이스에 로드하는 CLC를 생성합니다. 모듈 매니페스트의 <uses-library> 태그에 나열된 라이브러리를 최상위 CLC 요소로 추가합니다.

사용된 각 라이브러리에 대해 PackageManager 는 모든 <uses-library> 종속성(해당 라이브러리의 매니페스트에 태그로 지정됨)을 가져오고 각 종속성에 대해 중첩된 CLC를 추가합니다. 이 프로세스는 구성된 CLC 트리의 모든 리프 노드가 <uses-library> 종속성이 없는 라이브러리가 될 때까지 재귀적으로 계속됩니다.

PackageManager 는 공유 라이브러리만 인식합니다. 이 사용법에서 shared의 정의는 일반적인 의미와 다릅니다(shared vs. static). Android에서 Java 공유 라이브러리는 기기에 설치된 XML 구성에 나열된 라이브러리입니다( /system/etc/permissions/platform.xml ). 각 항목에는 공유 라이브러리의 이름, DEX jar 파일에 대한 경로, 종속성 목록(런타임에 이 항목이 사용하고 매니페스트의 <uses-library> 태그에 지정하는 다른 공유 라이브러리) 목록이 포함됩니다.

즉, PackageManager 가 런타임에 CLC를 구성할 수 있도록 하는 두 가지 정보 소스가 있습니다. 매니페스트의 <uses-library> 태그와 XML 구성의 공유 라이브러리 종속성입니다.

호스트(빌드 타임) CLC

CLC는 라이브러리나 앱을 로드할 때만 필요한 것이 아니라 컴파일할 때도 필요합니다. 컴파일은 장치에서(dexopt) 또는 빌드 중에(dexpreopt) 발생할 수 있습니다. dexopt는 장치에서 발생하므로 PackageManager 와 동일한 정보(매니페스트 및 공유 라이브러리 종속성)를 갖습니다. 그러나 Deexpreopt는 호스트와 완전히 다른 환경에서 발생하며 빌드 시스템에서 동일한 정보를 가져와야 합니다.

따라서 dexpreopt에서 사용하는 빌드 시간 CLC와 PackageManager 에서 사용하는 런타임 CLC는 동일하지만 두 가지 다른 방식으로 계산됩니다.

빌드 타임 및 런타임 CLC 일치해야 합니다. 그렇지 않으면 dexpreopt에 의해 생성된 AOT 컴파일 코드가 거부됩니다. 빌드 시간 및 런타임 CLC의 동등성을 확인하기 위해 dex2oat 컴파일러는 *.odex 파일(OAT 파일 헤더의 classpath 필드)에 빌드 시간 CLC를 기록합니다. 저장된 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은 라이브러리 또는 앱의 전이적 종속성 클로저에 있는 SDK 라이브러리로서 주어진 라이브러리 또는 앱에 대해 누락된 일부 <uses-library> 태그를 자동으로 계산할 수 있습니다. 라이브러리(또는 앱)가 SDK 라이브러리에 종속된 정적 라이브러리에 종속될 수 있고 다른 라이브러리를 통해 전이적으로 다시 종속될 수 있기 때문에 클로저가 필요합니다.

모든 <uses-library> 태그를 이런 식으로 계산할 수 있는 것은 아니지만 가능한 경우 Soong이 매니페스트 항목을 자동으로 추가하도록 하는 것이 좋습니다. 오류가 발생하기 쉽고 유지 관리가 간편합니다. 예를 들어 많은 앱이 새로운 <uses-library> 종속성을 추가하는 정적 라이브러리를 사용하는 경우 모든 앱을 업데이트해야 하므로 유지 관리가 어렵습니다.