Настройка сервиса ART

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

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

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

ART Service поддерживает все необходимые параметры dex2oat.

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

pm.dexopt.<reason>

Это набор системных свойств, которые определяют фильтры компилятора по умолчанию для всех предопределенных причин компиляции, описанных в разделе Сценарии 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 (по умолчанию: speed)

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

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

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

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

pm.dexopt.<reason>.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 Service оптимизирует только приложения, которые использовались в течение указанного количества дней.

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

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

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

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

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

Чтобы фоновое задание dexopt не выполнялось, рекомендуем использовать следующую последовательность команд:

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

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

API сервиса ART

Сервис ART предоставляет Java API для настройки. 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());
}

Отменить dexopt

Если операция инициирована вызовом 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();

Вы также можете отменить фоновую оптимизацию dexopt, запущенную сервисом ART.

getArtManagerLocal().cancelBackgroundDexoptJob();

Как получить результаты dexopt

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

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

// Process the result here.
...

Сервис ART также сам инициирует операции dexopt во многих сценариях, например при фоновой оптимизации dex. Чтобы прослушать все результаты 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 Service, запускает BatchDexoptStartCallback. Вы можете продолжать отменять операции, чтобы отключить dexopt.

Если вы отмените фоновую операцию 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. Обратный вызов выполняется каждый раз, когда пакет должен быть оптимизирован с помощью dexopt, независимо от того, инициирована ли оптимизация сервисом 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 также поддерживает некоторые другие настройки.

Установите пороговое значение температуры для фоновой оптимизации dexopt

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

Как определить, выполняется ли фоновая оптимизация dexopt

Фоновое задание dexopt управляется планировщиком заданий, и его идентификатор задания – 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. Во второй строке выполняется команда 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.)