Конфигурация службы ART

Прежде чем начать, ознакомьтесь с общим обзором службы ART .

Начиная с Android 14, компиляция AOT на устройстве для приложений (также известная как dexopt) обрабатывается службой ART. Служба ART является частью модуля ART, и вы можете настраивать ее через системные свойства и API.

Свойства системы

Служба ART поддерживает все соответствующие опции dex2oat .

Кроме того, ART Service поддерживает следующие системные свойства:

pm.dexopt.<причина>

Это набор системных свойств, которые определяют фильтры компилятора по умолчанию для всех предопределенных причин компиляции, описанных в сценариях Dexopt .

Для получения дополнительной информации см. Фильтры компилятора .

Стандартные значения по умолчанию:

pm.dexopt.first-boot=verify
pm.dexopt.boot-after-ota=verify
pm.dexopt.boot-after-mainline-update=verify
pm.dexopt.bg-dexopt=speed-profile
pm.dexopt.inactive=verify
pm.dexopt.cmdline=verify

pm.dexopt.shared (по умолчанию: скорость)

Это резервный фильтр компилятора для приложений, используемых другими приложениями.

В принципе, ART Service выполняет компиляцию с профилированием ( speed-profile ) для всех приложений, когда это возможно, обычно во время фонового dexopt. Однако есть некоторые приложения, которые используются другими приложениями (либо через <uses-library> , либо загружаются динамически с помощью Context#createPackageContext с CONTEXT_INCLUDE_CODE ). Такие приложения не могут использовать локальные профили из-за соображений конфиденциальности.

Для такого приложения, если запрашивается компиляция с использованием профиля, ART Service сначала пытается использовать облачный профиль. Если облачный профиль не существует, ART Service возвращается к использованию фильтра компилятора, указанного в pm.dexopt.shared .

Если запрошенная компиляция не ориентирована на профиль, это свойство не имеет никакого эффекта.

pm.dexopt.<причина>.concurrency (по умолчанию: 1)

Это количество вызовов dex2oat для определенных предопределенных причин компиляции ( first-boot , boot-after-ota , boot-after-mainline-update и bg-dexopt ).

Обратите внимание, что эффект этой опции сочетается с параметрами использования ресурсов dex2oat ( dalvik.vm.*dex2oat-threads , dalvik.vm.*dex2oat-cpu-set и профилями задач):

  • dalvik.vm.*dex2oat-threads контролирует количество потоков для каждого вызова dex2oat, тогда как pm.dexopt.<reason>.concurrency контролирует количество вызовов dex2oat. То есть максимальное количество параллельных потоков является произведением двух системных свойств.
  • dalvik.vm.*dex2oat-cpu-set и профили задач всегда ограничивают использование ядра ЦП, независимо от максимального количества одновременных потоков (обсуждалось выше).

Один вызов dex2oat может не полностью использовать все ядра ЦП, независимо от dalvik.vm.*dex2oat-threads . Поэтому увеличение количества вызовов dex2oat ( pm.dexopt.<reason>.concurrency ) может лучше использовать ядра ЦП, чтобы ускорить общий ход выполнения dexopt. Это особенно полезно во время загрузки.

Однако слишком большое количество вызовов dex2oat может привести к исчерпанию памяти устройства, хотя это можно смягчить, установив dalvik.vm.dex2oat-swap в true , чтобы разрешить использование файла подкачки. Слишком большое количество вызовов также может привести к ненужному переключению контекста. Поэтому это число следует тщательно настраивать для каждого продукта отдельно.

pm.dexopt.downgrade_after_inactive_days (по умолчанию: не установлено)

Если эта опция установлена, служба ART дексоптирует только те приложения, которые использовались в течение последнего указанного количества дней.

Кроме того, если хранилище почти пусто, во время фонового dexopt служба ART понижает фильтр компилятора приложений, которые не использовались в течение последнего заданного количества дней, чтобы освободить место. Причина компилятора для этого — inactive , а фильтр компилятора определяется pm.dexopt.inactive . Пороговое значение пространства для запуска этой функции — это пороговое значение низкого пространства Storage Manager (настраивается через глобальные настройки sys_storage_threshold_percentage и sys_storage_threshold_max_bytes , по умолчанию: 500 МБ) плюс 500 МБ.

Если вы настраиваете список пакетов через ArtManagerLocal#setBatchDexoptStartCallback , пакеты в списке, предоставленном BatchDexoptStartCallback для bg-dexopt никогда не будут понижены до версии.

pm.dexopt.disable_bg_dexopt (по умолчанию: false)

Это только для тестирования. Это не позволяет ART Service планировать фоновое задание dexopt.

Если фоновое задание dexopt уже запланировано, но еще не запущено, эта опция не имеет никакого эффекта. То есть задание все равно будет запущено.

Рекомендуемая последовательность команд для предотвращения запуска фонового задания dexopt:

setprop pm.dexopt.disable_bg_dexopt true
pm bg-dexopt-job --disable

Первая строка предотвращает планирование фонового задания dexopt, если оно еще не запланировано. Вторая строка отменяет планирование фонового задания dexopt, если оно уже запланировано, и немедленно отменяет фоновое задание dexopt, если оно выполняется.

API-интерфейсы ART-сервисов

ART Service предоставляет API Java для настройки. API определены в ArtManagerLocal . См. Javadoc в art/libartservice/service/java/com/android/server/art/ArtManagerLocal.java для использования ( исходный код Android 14 , невыпущенный исходный код разработки ).

ArtManagerLocal — это синглтон, хранящийся в LocalManagerRegistry . Вспомогательная функция com.android.server.pm.DexOptHelper#getArtManagerLocal поможет вам получить его.

import static com.android.server.pm.DexOptHelper.getArtManagerLocal;

Большинству API требуется экземпляр PackageManagerLocal.FilteredSnapshot , который содержит информацию обо всех приложениях. Вы можете получить его, вызвав PackageManagerLocal#withFilteredSnapshot , где PackageManagerLocal также является синглтоном, удерживаемым LocalManagerRegistry и может быть получен из com.android.server.pm.PackageManagerServiceUtils#getPackageManagerLocal .

import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;

Ниже приведены некоторые типичные варианты использования API.

Запустить dexopt для приложения

Вы можете запустить dexopt для любого приложения в любое время, вызвав ArtManagerLocal#dexoptPackage .

try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
  getArtManagerLocal().dexoptPackage(
      snapshot,
      "com.google.android.calculator",
      new DexoptParams.Builder(ReasonMapping.REASON_INSTALL).build());
}

Вы также можете передать свою собственную причину dexopt. Если вы это сделаете, класс приоритета и фильтр компилятора должны быть явно установлены.

try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
  getArtManagerLocal().dexoptPackage(
      snapshot,
      "com.google.android.calculator",
      new DexoptParams.Builder("my-reason")
          .setCompilerFilter("speed-profile")
          .setPriorityClass(ArtFlags.PRIORITY_BACKGROUND)
          .build());
}

Отменить дексопт

Если операция инициирована вызовом dexoptPackage , вы можете передать сигнал отмены, который позволяет отменить операцию в какой-то момент. Это может быть полезно, когда вы запускаете dexopt асинхронно.

Executor executor = ...;  // Your asynchronous executor here.
var cancellationSignal = new CancellationSignal();
executor.execute(() -> {
  try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
    getArtManagerLocal().dexoptPackage(
        snapshot,
        "com.google.android.calculator",
        new DexoptParams.Builder(ReasonMapping.REASON_INSTALL).build(),
        cancellationSignal);
  }
});

// When you want to cancel the operation.
cancellationSignal.cancel();

Вы также можете отменить фоновый дексопт, инициированный службой ART.

getArtManagerLocal().cancelBackgroundDexoptJob();

Получить результаты dexopt

Если операция инициирована вызовом dexoptPackage , вы можете получить результат из возвращаемого значения.

DexoptResult result;
try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
  result = getArtManagerLocal().dexoptPackage(...);
}

// Process the result here.
...

Служба ART также сама инициирует операции dexopt во многих сценариях, например, фоновый dexopt. Чтобы прослушать все результаты dexopt, независимо от того, инициирована ли операция вызовом dexoptPackage или службой ART, используйте ArtManagerLocal#addDexoptDoneCallback .

getArtManagerLocal().addDexoptDoneCallback(
    false /* onlyIncludeUpdates */,
    Runnable::run,
    (result) -> {
      // Process the result here.
      ...
    });

Первый аргумент определяет, следует ли включать в результат только обновления. Если вы хотите прослушивать только пакеты, обновленные dexopt, установите его в значение true.

Второй аргумент — исполнитель обратного вызова. Чтобы выполнить обратный вызов в том же потоке, который выполняет dexopt, используйте Runnable::run . Если вы не хотите, чтобы обратный вызов блокировал dexopt, используйте асинхронный исполнитель.

Вы можете добавить несколько обратных вызовов, и ART Service выполнит их все последовательно. Все обратные вызовы останутся активными для всех будущих вызовов, если вы их не удалите.

Если вы хотите удалить обратный вызов, сохраните ссылку на обратный вызов при его добавлении и используйте ArtManagerLocal#removeDexoptDoneCallback .

DexoptDoneCallback callback = (result) -> {
  // Process the result here.
  ...
};

getArtManagerLocal().addDexoptDoneCallback(
    false /* onlyIncludeUpdates */, Runnable::run, callback);

// When you want to remove it.
getArtManagerLocal().removeDexoptDoneCallback(callback);

Настройте список пакетов и параметры dexopt

Служба ART сама инициирует операции dexopt во время загрузки и фонового dexopt. Чтобы настроить список пакетов или параметры dexopt для этих операций, используйте ArtManagerLocal#setBatchDexoptStartCallback .

getArtManagerLocal().setBatchDexoptStartCallback(
    Runnable::run,
    (snapshot, reason, defaultPackages, builder, cancellationSignal) -> {
      switch (reason) {
        case ReasonMapping.REASON_BG_DEXOPT:
          var myPackages = new ArrayList<String>(defaultPackages);
          myPackages.add(...);
          myPackages.remove(...);
          myPackages.sort(...);
          builder.setPackages(myPackages);
          break;
        default:
          // Ignore unknown reasons.
      }
    });

Вы можете добавлять элементы в список пакетов, удалять из него элементы, сортировать его или даже использовать совершенно другой список.

Ваш обратный вызов должен игнорировать неизвестные причины, поскольку в будущем могут быть добавлены новые причины.

Вы можете установить максимум один BatchDexoptStartCallback . Обратный вызов останется активным для всех будущих вызовов, пока вы его не очистите.

Если вы хотите очистить обратный вызов, используйте ArtManagerLocal#clearBatchDexoptStartCallback .

getArtManagerLocal().clearBatchDexoptStartCallback();

Настройте параметры фонового задания dexopt

По умолчанию фоновое задание dexopt запускается один раз в день, когда устройство простаивает и заряжается. Это можно изменить с помощью ArtManagerLocal#setScheduleBackgroundDexoptJobCallback .

getArtManagerLocal().setScheduleBackgroundDexoptJobCallback(
    Runnable::run,
    builder -> {
      builder.setPeriodic(TimeUnit.DAYS.toMillis(2));
    });

Вы можете установить максимум один ScheduleBackgroundDexoptJobCallback . Обратный вызов останется активным для всех будущих вызовов, пока вы его не очистите.

Если вы хотите очистить обратный вызов, используйте ArtManagerLocal#clearScheduleBackgroundDexoptJobCallback .

getArtManagerLocal().clearScheduleBackgroundDexoptJobCallback();

Временно отключить dexopt

Любая операция dexopt, инициированная службой ART, запускает BatchDexoptStartCallback . Вы можете продолжать отменять операции, чтобы эффективно отключить dexopt.

Если отменяемая операция выполняется в фоновом режиме, она следует политике повтора по умолчанию (30 секунд, экспоненциальный рост, ограничение — 5 часов).

// Good example.

var shouldDisableDexopt = new AtomicBoolean(false);

getArtManagerLocal().setBatchDexoptStartCallback(
    Runnable::run,
    (snapshot, reason, defaultPackages, builder, cancellationSignal) -> {
      if (shouldDisableDexopt.get()) {
        cancellationSignal.cancel();
      }
    });

// Disable dexopt.
shouldDisableDexopt.set(true);
getArtManagerLocal().cancelBackgroundDexoptJob();

// Re-enable dexopt.
shouldDisableDexopt.set(false);

Вы можете иметь максимум один BatchDexoptStartCallback . Если вы также хотите использовать BatchDexoptStartCallback для настройки списка пакетов или параметров dexopt, вы должны объединить код в один обратный вызов.

// Bad example.

// Disable dexopt.
getArtManagerLocal().unscheduleBackgroundDexoptJob();

// Re-enable dexopt.
getArtManagerLocal().scheduleBackgroundDexoptJob();

Операция dexopt, выполняемая при установке приложения, не инициируется службой ART. Вместо этого она инициируется менеджером пакетов через вызов dexoptPackage . Поэтому она не запускает BatchDexoptStartCallback . Чтобы отключить dexopt при установке приложения, запретите менеджеру пакетов вызывать dexoptPackage .

Переопределить фильтр компилятора для определенных пакетов (Android 15+)

Вы можете переопределить фильтр компилятора для определенных пакетов, зарегистрировав обратный вызов через setAdjustCompilerFilterCallback . Обратный вызов вызывается всякий раз, когда пакет будет дексоптирован, независимо от того, инициирован ли дексопт службой ART во время загрузки и фонового дексопта или вызовом API dexoptPackage .

Если пакет не требует настройки, обратный вызов должен возвращать originalCompilerFilter .

getArtManagerLocal().setAdjustCompilerFilterCallback(
    Runnable::run,
    (packageName, originalCompilerFilter, reason) -> {
      if (isVeryImportantPackage(packageName)) {
        return "speed-profile";
      }
      return originalCompilerFilter;
    });

Вы можете задать только один AdjustCompilerFilterCallback . Если вы хотите использовать AdjustCompilerFilterCallback для переопределения фильтра компилятора для нескольких пакетов, вы должны объединить код в один обратный вызов. Обратный вызов остается активным для всех будущих вызовов, пока вы его не очистите.

Если вы хотите очистить обратный вызов, используйте ArtManagerLocal#clearAdjustCompilerFilterCallback .

getArtManagerLocal().clearAdjustCompilerFilterCallback();

Другие настройки

ART Service также поддерживает некоторые другие настройки.

Установите тепловой порог для фонового дексопта

Температурный контроль фонового задания dexopt выполняется Планировщиком заданий. Задание немедленно отменяется, когда температура достигает THERMAL_STATUS_MODERATE . Пороговое значение THERMAL_STATUS_MODERATE настраивается.

Определите, запущен ли фоновый процесс dexopt

Фоновое задание dexopt управляется Job Scheduler, а его идентификатор задания — 27873780 Чтобы определить, выполняется ли задание, используйте API Job Scheduler.

// Good example.

var jobScheduler =
    Objects.requireNonNull(mContext.getSystemService(JobScheduler.class));
int reason = jobScheduler.getPendingJobReason(27873780);

if (reason == PENDING_JOB_REASON_EXECUTING) {
  // Do something when the job is running.
  ...
}
// Bad example.

var backgroundDexoptRunning = new AtomicBoolean(false);

getArtManagerLocal().setBatchDexoptStartCallback(
    Runnable::run,
    (snapshot, reason, defaultPackages, builder, cancellationSignal) -> {
      if (reason.equals(ReasonMapping.REASON_BG_DEXOPT)) {
        backgroundDexoptRunning.set(true);
      }
    });

getArtManagerLocal().addDexoptDoneCallback(
    false /* onlyIncludeUpdates */,
    Runnable::run,
    (result) -> {
      if (result.getReason().equals(ReasonMapping.REASON_BG_DEXOPT)) {
        backgroundDexoptRunning.set(false);
      }
    });

if (backgroundDexoptRunning.get()) {
  // Do something when the job is running.
  ...
}

Предоставьте профиль для dexopt

Чтобы использовать профиль для управления dexopt, поместите файл .prof или .dm рядом с APK.

Файл .prof должен быть файлом профиля в двоичном формате, а имя файла должно быть именем файла APK + .prof . Например,

base.apk.prof

Имя файла .dm должно быть именем файла APK с расширением, замененным на .dm . Например,

base.dm

Чтобы убедиться, что профиль используется для dexopt, запустите dexopt с speed-profile и проверьте результат.

pm art clear-app-profiles <package-name>
pm compile -m speed-profile -f -v <package-name>

Первая строка очищает все профили, созданные средой выполнения (т. е. те, что в /data/misc/profiles ), если таковые имеются, чтобы убедиться, что профиль рядом с APK является единственным профилем, который может использовать ART Service. Вторая строка запускает dexopt с speed-profile , и он передает -v для вывода подробного результата.

Если профиль используется, вы увидите actualCompilerFilter=speed-profile в результате. В противном случае вы увидите actualCompilerFilter=verify . Например,

DexContainerFileDexoptResult{dexContainerFile=/data/app/~~QR0fTV0UbDbIP1Su7XzyPg==/com.google.android.gms-LvusF2uARKOtBbcaPHdUtQ==/base.apk, primaryAbi=true, abi=x86_64, actualCompilerFilter=speed-profile, status=PERFORMED, dex2oatWallTimeMillis=4549, dex2oatCpuTimeMillis=14550, sizeBytes=3715344, sizeBeforeBytes=3715344}

Типичные причины, по которым ART Service не использует профиль, включают следующее:

  • Профиль имеет неверное имя файла или его нет рядом с APK.
  • Профиль имеет неправильный формат.
  • Профиль не соответствует APK. (Контрольные суммы в профиле не соответствуют контрольным суммам файлов .dex в APK.)