런타임 리소스 오버레이(RRO)

RRO(런타임 리소스 오버레이)는 런타임에 타겟 패키지의 리소스 값을 변경하는 패키지입니다. 예를 들어 시스템 이미지에 설치된 앱은 리소스 값에 따라 동작을 변경할 수 있습니다. 빌드 시간에 리소스 값을 하드코딩하는 대신 다른 파티션에 설치된 RRO가 런타임에 앱의 리소스 값을 변경할 수 있습니다.

RRO를 사용 설정하거나 중지할 수 있습니다. 사용 설정/중지 상태를 프로그래매틱 방식으로 설정하여 리소스 값을 변경하는 RRO 기능을 전환할 수 있습니다. RRO는 기본적으로 중지되지만 정적 RRO는 기본적으로 사용 설정됩니다.

리소스 오버레이

오버레이는 오버레이 패키지에 정의된 리소스를 타겟 패키지에 정의된 리소스에 매핑하는 방식으로 작동합니다. 앱이 타겟 패키지에 있는 리소스 값을 확인하려고 하면 타겟 리소스가 매핑된 오버레이 리소스의 값이 대신 반환됩니다.

매니페스트 설정

패키지에 <overlay> 태그가 <manifest> 태그의 하위 요소로 포함되어 있으면 RRO 패키지로 간주합니다.

  • 필수 android:targetPackage 속성 값은 RRO가 오버레이해야 하는 패키지의 이름을 지정합니다.

  • 선택적 android:targetName 속성 값은 RRO가 오버레이해야 하는 타겟 패키지 리소스의 오버레이 가능한 하위 집합 이름을 지정합니다. 타겟이 오버레이 가능한 리소스 집합을 정의하지 않는 경우 이 속성은 포함되지 않아야 합니다.

다음 코드는 오버레이 AndroidManifest.xml의 예를 보여 줍니다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:targetName="OverlayableResources"/>
</manifest>

오버레이는 코드를 오버레이할 수 없으므로 DEX 파일을 포함할 수 없습니다. 또한 매니페스트에서 <application> 태그의 android:hasCode 속성을 false로 설정해야 합니다.

리소스 맵 정의

Android 11 이상에서 오버레이 리소스 맵 정의에 권장되는 메커니즘은 오버레이 패키지의 res/xml 디렉터리에 파일을 만들고, 오버레이되어야 하는 타겟 리소스와 대체 값을 열거한 다음, <overlay> 매니페스트 태그의 android:resourcesMap 속성 값을 리소스 매핑 파일에 대한 참조로 설정합니다.

다음 코드는 res/xml/overlays.xml 파일의 예를 보여 줍니다.

<?xml version="1.0" encoding="utf-8"?>
<overlay xmlns:android="http://schemas.android.com/apk/res/android" >
    <!-- Overlays string/config1 and string/config2 with the same resource. -->
    <item target="string/config1" value="@string/overlay1" />
    <item target="string/config2" value="@string/overlay1" />

    <!-- Overlays string/config3 with the string "yes". -->
    <item target="string/config3" value="@android:string/yes" />

    <!-- Overlays string/config4 with the string "Hardcoded string". -->
    <item target="string/config4" value="Hardcoded string" />

    <!-- Overlays integer/config5 with the integer "42". -->
    <item target="integer/config5" value="42" />
</overlay>

다음 코드는 오버레이 매니페스트의 예를 보여 줍니다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:targetName="OverlayableResources"
                   android:resourcesMap="@xml/overlays"/>
</manifest>

패키지 빌드

Android 11 이상은 AAPT2(Android Asset Packaging Tool 2)가 동일한 값(--no-resource-deduping)이 있는 리소스의 중복 구성을 삭제하지 못하도록 하고 기본 구성(--no-resource-removal)이 없는 리소스를 제거하지 못하도록 하는 오버레이의 Soong 빌드 규칙을 지원합니다. 다음 코드는 Android.bp 파일의 예를 보여 줍니다.

runtime_resource_overlay {
    name: "ExampleOverlay",
    sdk_version: "current",
}

리소스 확인

타겟 리소스 또는 오버레이 리소스에 쿼리되는 리소스의 여러 구성이 정의되어 있으면 리소스 런타임은 기기 구성의 구성과 가장 일치하는 구성 값을 반환합니다. 가장 일치하는 구성이 무엇인지 확인하려면 오버레이 리소스 구성 집합을 타겟 리소스 구성 집합으로 병합한 다음, 일반 리소스 확인 흐름을 따릅니다(자세한 내용은 Android가 가장 일치하는 리소스를 찾는 방법 참조).

예를 들어 오버레이가 drawable-en 구성 값을 정의하고 타겟이 drawable-en-port 값을 정의하는 경우 drawable-en-port가 더 일치하므로 런타임에 타겟 구성 drawable-en-port가 선택됩니다. 모든 drawable-en 구성을 오버레이하려면 오버레이는 타겟에서 정의하는 각 drawable-en 구성의 값을 정의해야 합니다.

오버레이는 자체 리소스를 참조할 수 있으며 Android 출시 간에 동작이 다릅니다.

  • Android 11 이상에서 각 오버레이에는 타겟 리소스 ID 공간이나 다른 오버레이 리소스 ID 공간과 겹치지 않는 자체 예약된 리소스 ID 공간이 있으므로 자체 리소스를 참조하는 오버레이가 예상대로 작동합니다.

  • Android 10 이하에서 오버레이 및 타겟 패키지는 동일한 리소스 ID 공간을 공유하므로 @type/name 구문을 사용하여 자체 리소스를 참조하려고 할 때 충돌 및 예기치 않은 동작을 발생시킬 수 있습니다.

오버레이 사용 설정/중지

OverlayManager API를 사용하여 변경 가능한 오버레이를 사용 설정 및 중지합니다(Context#getSystemService(Context.OVERLAY_SERVICE)를 사용하여 API 인터페이스 검색). 오버레이는 타겟팅하는 패키지 또는 android.permission.CHANGE_OVERLAY_PACKAGES 권한이 있는 패키지에 의해서만 사용 설정될 수 있습니다. 오버레이가 사용 설정되거나 중지되면 구성 변경 이벤트는 타겟 패키지에 전파되고 타겟 활동이 다시 시작됩니다.

오버레이 가능한 리소스 제한

Android 10 이상에서 <overlayable> XML 태그는 RRO가 오버레이할 수 있는 리소스 집합을 노출합니다. 다음 예시 res/values/overlayable.xml 파일에서 string/foointeger/bar는 기기 모양의 테마를 설정하는 데 사용되는 리소스입니다. 이러한 리소스를 오버레이하기 위해서는 오버레이가 이름별로 오버레이 가능한 리소스의 컬렉션을 명시적으로 타겟팅해야 합니다.

<!-- The collection of resources for theming the appearance of the device -->
<overlayable name="ThemeResources">
       <policy type="public">
               <item type="string" name="foo/" />
               <item type="integer" name="bar/" />
       </policy>
       ...
</overlayable>

APK는 여러 <overlayable> 태그를 정의할 수 있지만 각 태그는 패키지 내에서 고유 이름을 사용해야 합니다. 예를 들면 다음과 같습니다.

  • 두 개의 다른 패키지에서 모두 <overlayable name="foo">를 정의해도 됩니다.

  • 단일 APK에 두 개의 <overlayable name="foo"> 블록이 있으면 안 됩니다.

다음 코드는 AndroidManifest.xml 파일에 있는 오버레이의 예를 보여 줍니다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.my.theme.overlay">
       <application android:hasCode="false" />
       <!-- This overlay will override the ThemeResources resources -->
       <overlay android:targetPackage="android" android:targetName="ThemeResources">
</manifest>

앱이 <overlayable> 태그를 정의하면 이 앱을 타겟팅하는 오버레이는 다음을 충족해야 합니다.

  • targetName을 지정해야 합니다.

  • <overlayable> 태그 내에 나열된 리소스만 오버레이할 수 있습니다.

  • <overlayable> 이름을 하나만 타겟팅할 수 있습니다.

오버레이 가능한 리소스를 노출하지만 특정 <overlayable> 태그를 타겟팅하는 데 android:targetName을 사용하지 않는 패키지를 타겟팅하는 오버레이는 사용 설정할 수 없습니다.

정책 제한

<policy> 태그를 사용하여 오버레이 가능한 리소스에 제한사항을 적용합니다. type 속성은 포함된 리소스를 재정의하기 위해 오버레이에서 처리해야 하는 정책을 지정합니다. 지원되는 유형에는 다음이 포함됩니다.

  • public. 모든 오버레이가 리소스를 재정의할 수 있습니다.
  • system. 시스템 파티션의 모든 오버레이가 리소스를 재정의할 수 있습니다.
  • vendor. 공급업체 파티션의 모든 오버레이가 리소스를 재정의할 수 있습니다.
  • product. 제품 파티션의 모든 오버레이가 리소스를 재정의할 수 있습니다.
  • signature. 타겟 APK와 동일하게 서명된 모든 오버레이가 리소스를 재정의할 수 있습니다.

다음 코드는 res/values/overlayable.xml 파일의 예시 <policy> 태그를 보여 줍니다.

<overlayable name="ThemeResources">
   <policy type="vendor" >
       <item type="string" name="foo" />
   </policy>
   <policy type="product|signature"  >
       <item type="string" name="bar" />
       <item type="string" name="baz" />
   </policy>
</overlayable>

여러 정책을 지정하려면 수직 막대(|)를 구분 문자로 사용하세요. 여러 정책을 지정하면 오버레이는 <policy> 태그 내에 나열된 리소스를 재정의하기 위해 하나의 정책만 처리하면 됩니다.

오버레이 구성

Android는 Android 출시 버전에 따라 오버레이의 변경 가능성, 기본 상태, 우선순위를 구성하는 다양한 메커니즘을 지원합니다.

  • Android 11 이상을 실행하는 기기는 매니페스트 속성 대신 OverlayConfig 파일(config.xml)을 사용할 수 있습니다. 오버레이 파일을 사용하는 것이 오버레이에 권장되는 방법입니다.

  • 모든 기기는 매니페스트 속성(android:isStaticandroid:priority)을 사용하여 정적 RRO를 구성할 수 있습니다.

OverlayConfig 사용

Android 11 이상에서는 OverlayConfig를 사용하여 오버레이의 변경 가능성, 기본 상태, 우선순위를 구성할 수 있습니다. 오버레이를 구성하려면 partition/overlay/config/config.xml에 있는 파일을 만들거나 수정합니다. 여기서 partition은 구성할 오버레이의 파티션입니다. 오버레이가 구성되려면 구성되는 파티션의 overlay/ 디렉터리에 있어야 합니다. 다음 코드는 예시 product/overlay/config/config.xml을 보여 줍니다.

<config>
    <merge path="OEM-common-rros-config.xml" />
    <overlay package="com.oem.overlay.device" mutable="false" enabled="true" />
    <overlay package="com.oem.green.theme" enabled="true" />
</config>"

<overlay> 태그에는 구성되는 오버레이 패키지를 나타내는 package 속성이 필요합니다. 선택적 enabled 속성은 오버레이가 기본적으로 사용 설정되는지 여부를 제어합니다(기본값은 false). 선택적 mutable 속성은 오버레이가 변경 가능하며 사용 설정된 상태가 런타임에 프로그래매틱 방식으로 변경되는지 여부를 제어합니다(기본값은 true). 구성 파일 내에 나열되지 않는 오버레이는 변경 가능하며 기본적으로 중지됩니다.

오버레이 우선순위

여러 오버레이가 동일한 리소스를 재정의할 경우 오버레이의 순서가 중요합니다. 오버레이는 자체 구성 앞에 구성이 있는 오버레이보다 우선순위가 높습니다. 서로 다른 파티션에서 오버레이의 우선순위 순서(최저에서 최고 우선순위 순서)는 다음과 같습니다.

  • system
  • vendor
  • oem
  • odm
  • product
  • system_ext

파일 병합

<merge> 태그를 사용하면 다른 구성 파일을 지정된 위치에서 구성 파일에 병합할 수 있습니다. 태그의 path 속성은 오버레이 구성 파일이 포함된 디렉터리를 기준으로 병합할 파일의 상대 경로를 나타냅니다.

매니페스트 속성(정적 RRO) 사용

Android 10 이하에서 오버레이 불변성 및 우선순위는 다음 매니페스트 속성을 사용하여 구성됩니다.

  • android:isStatic. 이 부울 속성의 값을 true로 설정하면 오버레이는 기본적으로 사용 설정되며 변경할 수 없으므로 오버레이가 중지되지 않습니다.

  • android:priority. 이 숫자 속성 값(정적 오버레이에만 영향을 줌)은 여러 정적 오버레이가 동일한 리소스 값을 타겟팅하는 경우 오버레이의 우선순위를 구성합니다. 숫자가 클수록 우선 순위가 높아집니다.

다음 코드는 예시 AndroidManifest.xml을 보여 줍니다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:isStatic="true"
                   android:priority="5"/>
</manifest>

Android 11 변경사항

Android 11 이상에서 구성 파일이 partition/overlay/config/config.xml에 있으면 이 파일을 사용하여 오버레이가 구성되며 android:isStaticandroid:priority는 파티션에 있는 오버레이에 영향을 주지 않습니다. 파티션에서 오버레이 구성 파일을 정의하면 오버레이 파티션 우선순위가 적용됩니다.

또한 Android 11 이상에서는 정적 오버레이를 사용하여 패키지 설치 중에 읽은 리소스 값에 영향을 주는 기능을 제거합니다. 정적 오버레이를 사용하여 구성요소 사용 설정 상태를 구성하는 부울 값을 변경하는 일반적인 사용 사례의 경우 <component-override> SystemConfig 태그를 사용합니다(Android 11의 새로운 기능).

오버레이 디버깅

오버레이를 수동으로 사용 설정, 중지, 덤프하려면 다음 오버레이 관리자 셸 명령어를 사용합니다.

adb shell cmd overlay

OverlayManagerServiceidmap2를 사용하여 타겟 패키지의 리소스 ID를 오버레이 패키지의 리소스 ID에 매핑합니다. 생성된 ID 매핑은 /data/resource-cache/에 저장됩니다. 오버레이가 올바르게 작동하지 않으면 /data/resource-cache/에서 오버레이에 상응하는 idmap 파일을 찾아서 다음 명령어를 실행합니다.

adb shell idmap2 dump --idmap-path [file]

이 명령어는 아래와 같이 리소스 매핑을 출력합니다.

[target res id] - > [overlay res id] [resource name]
0x01040151 -> 0x01050001 string/config_dozeComponent
0x01040152 -> 0x01050002 string/config_dozeDoubleTapSensorType
0x01040153 -> 0x01050003 string/config_dozeLongPressSensorType