Как изменить значение ресурсов приложения во время выполнения

Переопределение ресурсов во время выполнения (RRO) – это пакет, который изменяет значения ресурсов целевого пакета во время выполнения. Например, приложение, установленное в системном образе, может изменить свое поведение в зависимости от значения ресурса. Вместо того чтобы жестко задавать значение ресурса во время сборки, RRO, установленный в другом разделе, может изменять значения ресурсов приложения во время выполнения.

RRO можно включать и отключать. Вы можете программно задать состояние включения/отключения, чтобы переключать возможность RRO изменять значения ресурсов. По умолчанию RRO отключены, но статические RRO включены.

Переопределение ресурсов

Оверлеи работают, сопоставляя ресурсы, определенные в пакете оверлея, с ресурсами, определенными в целевом пакете. Когда приложение пытается разрешить значение ресурса в целевом пакете, вместо этого возвращается значение ресурса наложения, с которым сопоставлен целевой ресурс.

Как настроить манифест

Пакет считается пакетом RRO, если он содержит тег <overlay> как дочерний элемент тега <manifest>.

  • Значение обязательного атрибута 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-файлы. Кроме того, атрибут android:hasCode тега <application> в манифесте должен иметь значение false.

Определите карту ресурсов

В Android 11 и более поздних версиях рекомендуется создавать файл в каталоге res/xml пакета оверлея, перечислять целевые ресурсы, которые должны быть перекрыты, и их значения замены, а затем задавать значение атрибута android:resourcesMap тега манифеста <overlay> в виде ссылки на файл сопоставления ресурсов.

Ниже приведен пример файла 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 и более поздних версиях поддерживается правило сборки Soong для оверлеев, которое не позволяет Android Asset Packaging Tool 2 (AAPT2) пытаться дедуплицировать конфигурации ресурсов с одинаковым значением (--no-resource-deduping) и удалять ресурсы без конфигураций по умолчанию (--no-resource-removal). Ниже приведен пример файла 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 и более поздних версий у каждого оверлея есть собственное зарезервированное пространство идентификаторов ресурсов, которое не пересекается с пространством идентификаторов целевых ресурсов или других оверлеев. Поэтому оверлеи, ссылающиеся на собственные ресурсы, работают как ожидается.

  • В Android 10 и более ранних версиях оверлеи и целевые пакеты используют одно и то же пространство идентификаторов ресурсов, что может привести к конфликтам и непредвиденному поведению при попытке сослаться на собственные ресурсы с помощью синтаксиса @type/name.

Как включить или отключить оверлеи

Оверлеи можно включать и отключать вручную или программно.

Как вручную включить или отключить оверлеи

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

adb shell cmd overlay enable --user current com.example.carrro
adb shell cmd overlay list --user current | grep -i com.example com.example.carrro

Это позволяет использовать RRO для системного пользователя (userId = 0), которому принадлежит SystemUI. Эта инструкция не влияет на приложения, запущенные пользователем, находящимся на переднем плане (userId = 10). Чтобы включить RRO для пользователя, находящегося на переднем плане, используйте параметр -–user 10:

adb shell cmd overlay enable --user 10 com.example.carrro

Как программно включить или отключить оверлеи

Используйте API OverlayManager, чтобы включать и отключать изменяемые оверлеи (получите интерфейс API с помощью Context#getSystemService(Context.OVERLAY_SERVICE)). Оверлей может быть включен только пакетом, на который он нацелен, или пакетом с разрешением android.permission.CHANGE_OVERLAY_PACKAGES. Когда наложение включается или отключается, события изменения конфигурации распространяются на целевой пакет, и целевые объекты activity перезапускаются.

Ограничить ресурсы, которые можно переопределять

В Android 10 и более поздних версий XML-тег <overlayable> предоставляет набор ресурсов, которые могут перекрывать RRO. В следующем примере файла res/values/overlayable.xml ресурсы string/foo и integer/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>.

Нельзя включить оверлей, предназначенный для пакета, который предоставляет ресурсы для оверлеев, но не использует android:targetName для таргетинга на определенный тег <overlayable>.

Ограничить правила

Используйте тег <policy>, чтобы применить ограничения к ресурсам, которые можно переопределять. Атрибут type указывает, каким правилам должно соответствовать наложение, чтобы переопределить включенные ресурсы. Поддерживаются следующие типы:

  • public. Любой оверлей может переопределить ресурс.
  • system. Любое наложение на системный раздел может переопределить ресурсы.
  • vendor. Любое наложение на раздел поставщика может переопределить ресурсы.
  • product. Любое наложение на раздел товара может переопределить ресурсы.
  • oem. Любой оверлей в разделе OEM может переопределять ресурсы.
  • odm. Любой оверлей в разделе odm может переопределять ресурсы.
  • signature. Любой оверлей, подписанный той же подписью, что и целевой APK, может переопределять ресурсы.
  • actor. Любое наложение, подписанное той же подписью, что и APK-файл actor, может переопределять ресурсы. Исполнитель объявлен в теге named-actor в системной конфигурации.
  • config_signature. Любое наложение, подписанное той же подписью, что и APK-файл overlay-config, может переопределять ресурсы. Конфигурация оверлея объявляется в теге overlay-config-signature в системной конфигурации.

В приведенном ниже примере показан тег <policy> в файле res/values/overlayable.xml.

<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 11 и более поздних версий вместо атрибутов манифеста можно использовать файл OverlayConfig (config.xml). Рекомендуется использовать файл оверлея.

  • Все устройства могут использовать атрибуты манифеста (android:isStatic и android: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
  • odm
  • oem
  • 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:isStatic и android:priority не влияют на наложения, расположенные в разделе. Если в каком-либо разделе задан файл конфигурации оверлея, приоритет будет отдан этому разделу.

Кроме того, в Android 11 и более поздних версиях больше нельзя использовать статические оверлеи, чтобы влиять на значения ресурсов, считываемых во время установки пакета. Если вы хотите использовать статические оверлеи, чтобы изменить значение логического параметра, определяющего состояние компонента, используйте тег <component-override> SystemConfig (новый в Android 11).

Оверлеи отладки

Чтобы вручную включить, отключить или сбросить наложения, используйте следующую команду оболочки overlaymanager.

adb shell cmd overlay

Если использовать enable, не указывая пользователя, это повлияет на текущего пользователя, то есть системного пользователя (userId = 0), которому принадлежит интерфейс системы. Это не повлияет на пользователя, который работает с устройством в данный момент (userId = 10), поскольку он является владельцем приложений. Чтобы включить RRO для активного пользователя, используйте параметр –-user 10:

adb shell cmd overlay enable --user 10 com.example.carrro

OverlayManagerService использует idmap2, чтобы сопоставить идентификаторы ресурсов в целевом пакете с идентификаторами ресурсов в пакете оверлея. Сгенерированные сопоставления идентификаторов хранятся в /data/resource-cache/. Если оверлей работает неправильно, найдите соответствующий файл idmap в /data/resource-cache/ и выполните следующую команду:

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