Configuration du service ART

Avant de commencer, consultez la présentation générale du service ART.

À partir d'Android 14, la compilation AOT sur l'appareil pour les applications (également appelée dexopt) est gérée par le service ART. Le service ART fait partie du module ART. Vous pouvez le personnaliser à l'aide de propriétés système et d'API.

Propriétés système

Le service ART est compatible avec toutes les options dex2oat pertinentes.

De plus, le service ART est compatible avec les propriétés système suivantes :

pm.dexopt.<reason>

Il s'agit d'un ensemble de propriétés système qui déterminent les filtres de compilation par défaut pour toutes les raisons de compilation prédéfinies décrites dans Scénarios Dexopt.

Pour en savoir plus, consultez Filtres du compilateur.

Les valeurs par défaut standards sont les suivantes :

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 (par défaut : speed)

Il s'agit du filtre de compilation de secours pour les applications utilisées par d'autres applications.

En principe, le service ART effectue une compilation guidée par profil (speed-profile) pour toutes les applications lorsque cela est possible, généralement lors de l'optimisation dex en arrière-plan. Toutefois, certaines applications sont utilisées par d'autres applications (via <uses-library> ou chargées dynamiquement à l'aide de Context#createPackageContext avec CONTEXT_INCLUDE_CODE). Ces applications ne peuvent pas utiliser de profils locaux pour des raisons de confidentialité.

Pour une telle application, si la compilation guidée par profil est demandée, le service ART tente d'abord d'utiliser un profil cloud. Si aucun profil cloud n'existe, le service ART utilise le filtre de compilation spécifié par pm.dexopt.shared.

Si la compilation demandée n'est pas guidée par profil, cette propriété n'a aucun effet.

pm.dexopt.<reason>.concurrency (valeur par défaut : 1)

Il s'agit du nombre d'invocations dex2oat pour certaines raisons de compilation prédéfinies (first-boot, boot-after-ota, boot-after-mainline-update et bg-dexopt).

Notez que l'effet de cette option est combiné avec les options d'utilisation des ressources dex2oat (dalvik.vm.*dex2oat-threads, dalvik.vm.*dex2oat-cpu-set et les profils de tâches) :

  • dalvik.vm.*dex2oat-threads contrôle le nombre de threads pour chaque appel dex2oat, tandis que pm.dexopt.<reason>.concurrency contrôle le nombre d'appels dex2oat. Autrement dit, le nombre maximal de threads simultanés correspond au produit des deux propriétés système.
  • dalvik.vm.*dex2oat-cpu-set et les profils de tâches limitent toujours l'utilisation des cœurs de processeur, quel que soit le nombre maximal de threads simultanés (abordé ci-dessus).

Une seule invocation dex2oat peut ne pas utiliser pleinement tous les cœurs de processeur, quel que soit dalvik.vm.*dex2oat-threads. Par conséquent, l'augmentation du nombre d'invocations dex2oat (pm.dexopt.<reason>.concurrency) peut mieux utiliser les cœurs de processeur pour accélérer la progression globale de dexopt. Cela est particulièrement utile lors du démarrage.

Toutefois, un trop grand nombre d'invocations dex2oat peut entraîner un manque de mémoire sur l'appareil, même si cela peut être atténué en définissant dalvik.vm.dex2oat-swap sur true pour autoriser l'utilisation d'un fichier d'échange. Un nombre trop élevé d'invocations peut également entraîner des changements de contexte inutiles. Par conséquent, ce nombre doit être ajusté avec soin pour chaque produit.

pm.dexopt.downgrade_after_inactive_days (par défaut : non défini)

Si cette option est définie, le service ART n'optimise que les applications utilisées au cours du nombre de jours indiqué.

De plus, si l'espace de stockage est presque plein, lors de l'optimisation dex en arrière-plan, le service ART rétrograde le filtre du compilateur des applications qui n'ont pas été utilisées au cours du nombre de jours donné, afin de libérer de l'espace. La raison du compilateur est inactive et le filtre du compilateur est déterminé par pm.dexopt.inactive. Le seuil d'espace pour déclencher cette fonctionnalité est le seuil d'espace faible du Gestionnaire de stockage (configurable via les paramètres généraux sys_storage_threshold_percentage et sys_storage_threshold_max_bytes, par défaut : 500 Mo) plus 500 Mo.

Si vous personnalisez la liste des packages via ArtManagerLocal#setBatchDexoptStartCallback, les packages de la liste fournie par BatchDexoptStartCallback pour bg-dexopt ne sont jamais rétrogradés.

pm.dexopt.disable_bg_dexopt (valeur par défaut : false)

Ceci est un test. Cela empêche le service ART de planifier la tâche dexopt en arrière-plan.

Si le job dexopt en arrière-plan est déjà planifié, mais n'a pas encore été exécuté, cette option n'a aucun effet. Autrement dit, le job s'exécutera toujours.

Voici une séquence de commandes recommandée pour empêcher l'exécution de la tâche dexopt en arrière-plan :

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

La première ligne empêche la planification du job dexopt en arrière-plan, s'il n'est pas encore planifié. La deuxième ligne annule la planification du job dexopt en arrière-plan, s'il est déjà planifié, et l'annule immédiatement, s'il est en cours d'exécution.

API du service ART

Le service ART expose des API Java pour la personnalisation. Les API sont définies dans ArtManagerLocal. Consultez le Javadoc dans art/libartservice/service/java/com/android/server/art/ArtManagerLocal.java pour les utilisations (source Android 14, source de développement non publiée).

ArtManagerLocal est un singleton détenu par LocalManagerRegistry. Une fonction d'assistance com.android.server.pm.DexOptHelper#getArtManagerLocal vous aide à l'obtenir.

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

La plupart des API nécessitent une instance de PackageManagerLocal.FilteredSnapshot, qui contient les informations de toutes les applications. Vous pouvez l'obtenir en appelant PackageManagerLocal#withFilteredSnapshot, où PackageManagerLocal est également un singleton détenu par LocalManagerRegistry et peut être obtenu à partir de com.android.server.pm.PackageManagerServiceUtils#getPackageManagerLocal.

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

Voici quelques cas d'utilisation types des API.

Déclencher dexopt pour une application

Vous pouvez déclencher dexopt pour n'importe quelle application à tout moment en appelant ArtManagerLocal#dexoptPackage.

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

Vous pouvez également transmettre votre propre motif dexopt. Dans ce cas, la classe de priorité et le filtre du compilateur doivent être définis de manière explicite.

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());
}

Annuler l'optimisation dex

Si une opération est lancée par un appel dexoptPackage, vous pouvez transmettre un signal d'annulation, qui vous permet d'annuler l'opération à un moment donné. Cela peut être utile lorsque vous exécutez dexopt de manière asynchrone.

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();

Vous pouvez également annuler l'optimisation dex en arrière-plan, qui est lancée par le service ART.

getArtManagerLocal().cancelBackgroundDexoptJob();

Obtenir les résultats dexopt

Si une opération est lancée par un appel dexoptPackage, vous pouvez obtenir le résultat à partir de la valeur renvoyée.

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

// Process the result here.
...

Le service ART lance également lui-même des opérations dexopt dans de nombreux scénarios, comme dexopt en arrière-plan. Pour écouter tous les résultats dexopt, que l'opération soit initiée par un appel dexoptPackage ou par le service ART, utilisez ArtManagerLocal#addDexoptDoneCallback.

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

Le premier argument détermine s'il faut inclure uniquement les mises à jour dans le résultat. Si vous ne souhaitez écouter que les packages mis à jour par dexopt, définissez-le sur "true".

Le deuxième argument est l'exécuteur du rappel. Pour exécuter le rappel sur le même thread que celui qui effectue l'optimisation dex, utilisez Runnable::run. Si vous ne souhaitez pas que le rappel bloque dexopt, utilisez un exécuteur asynchrone.

Vous pouvez ajouter plusieurs rappels. Le service ART les exécutera tous de manière séquentielle. Tous les rappels resteront actifs pour tous les futurs appels, sauf si vous les supprimez.

Si vous souhaitez supprimer un rappel, conservez la référence du rappel lorsque vous l'ajoutez et utilisez 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);

Personnaliser la liste des packages et les paramètres dexopt

Le service ART lance lui-même les opérations dexopt lors du démarrage et de l'optimisation dexopt en arrière-plan. Pour personnaliser la liste des packages ou les paramètres dexopt pour ces opérations, utilisez 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.
      }
    });

Vous pouvez ajouter des éléments à la liste des packages, en supprimer, la trier ou même utiliser une liste complètement différente.

Votre rappel doit ignorer les raisons inconnues, car d'autres raisons pourront être ajoutées à l'avenir.

Vous ne pouvez définir qu'un seul BatchDexoptStartCallback. Le rappel restera actif pour tous les futurs appels, sauf si vous le supprimez.

Si vous souhaitez effacer le rappel, utilisez ArtManagerLocal#clearBatchDexoptStartCallback.

getArtManagerLocal().clearBatchDexoptStartCallback();

Personnaliser les paramètres du job dexopt en arrière-plan

Par défaut, la tâche dexopt en arrière-plan s'exécute une fois par jour lorsque l'appareil est inactif et en charge. Vous pouvez modifier ce paramètre à l'aide de ArtManagerLocal#setScheduleBackgroundDexoptJobCallback.

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

Vous ne pouvez définir qu'un seul ScheduleBackgroundDexoptJobCallback. Le rappel restera actif pour tous les futurs appels, sauf si vous le supprimez.

Si vous souhaitez effacer le rappel, utilisez ArtManagerLocal#clearScheduleBackgroundDexoptJobCallback.

getArtManagerLocal().clearScheduleBackgroundDexoptJobCallback();

Désactiver temporairement dexopt

Toute opération dexopt initiée par le service ART déclenche un BatchDexoptStartCallback. Vous pouvez continuer à annuler les opérations pour désactiver efficacement dexopt.

Si l'opération que vous annulez est une dexopt en arrière-plan, elle suit la règle de nouvelle tentative par défaut (30 secondes, exponentielle, plafonnée à 5 heures).

// 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);

Vous ne pouvez avoir qu'un seul BatchDexoptStartCallback. Si vous souhaitez également utiliser BatchDexoptStartCallback pour personnaliser la liste des packages ou les paramètres dexopt, vous devez combiner le code en un seul rappel.

// Bad example.

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

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

L'opération dexopt effectuée lors de l'installation de l'application n'est pas initiée par le service ART. mais par le gestionnaire de packages via un appel dexoptPackage. Par conséquent, il ne déclenche BatchDexoptStartCallback. Pour désactiver dexopt lors de l'installation de l'application, empêchez le gestionnaire de packages d'appeler dexoptPackage.

Remplacer le filtre du compilateur pour certains packages (Android 15+)

Vous pouvez remplacer le filtre du compilateur pour certains packages en enregistrant un rappel via setAdjustCompilerFilterCallback. Le rappel est appelé chaque fois qu'un package va être dexopté, que la dexopt soit initiée par le service ART lors du démarrage et de la dexopt en arrière-plan ou par un appel d'API dexoptPackage.

Si un package n'a pas besoin d'être ajusté, le rappel doit renvoyer originalCompilerFilter.

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

Vous ne pouvez définir qu'un seul AdjustCompilerFilterCallback. Si vous souhaitez utiliser AdjustCompilerFilterCallback pour remplacer le filtre du compilateur pour plusieurs packages, vous devez combiner le code en un seul rappel. Le rappel reste actif pour tous les futurs appels, sauf si vous l'effacez.

Si vous souhaitez effacer le rappel, utilisez ArtManagerLocal#clearAdjustCompilerFilterCallback.

getArtManagerLocal().clearAdjustCompilerFilterCallback();

Autres personnalisations

Le service ART est également compatible avec d'autres personnalisations.

Définir le seuil thermique pour dexopt en arrière-plan

Le contrôle thermique de la tâche dexopt en arrière-plan est effectué par JobScheduler. La tâche est annulée immédiatement lorsque la température atteint THERMAL_STATUS_MODERATE. Le seuil de THERMAL_STATUS_MODERATE est ajustable.

Déterminer si dexopt en arrière-plan est en cours d'exécution

La tâche dexopt en arrière-plan est gérée par Job Scheduler et son ID de tâche est 27873780. Pour déterminer si le job est en cours d'exécution, utilisez les 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.
  ...
}

Fournir un profil pour dexopt

Pour utiliser un profil pour guider dexopt, placez un fichier .prof ou un fichier .dm à côté de l'APK.

Le fichier .prof doit être un fichier de profil au format binaire. Son nom doit correspondre à celui de l'APK suivi de .prof. Par exemple,

base.apk.prof

Le nom du fichier .dm doit être celui de l'APK, avec l'extension remplacée par .dm. Par exemple,

base.dm

Pour vérifier que le profil est utilisé pour dexopt, exécutez dexopt avec speed-profile et vérifiez le résultat.

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

La première ligne efface tous les profils produits par le runtime (c'est-à-dire ceux de /data/misc/profiles), le cas échéant, pour s'assurer que le profil à côté de l'APK est le seul que le service ART puisse utiliser. La deuxième ligne exécute dexopt avec speed-profile et transmet -v pour imprimer le résultat détaillé.

Si le profil est utilisé, actualCompilerFilter=speed-profile s'affiche dans le résultat. Sinon, actualCompilerFilter=verify s'affiche. Par exemple,

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}

Voici quelques raisons courantes pour lesquelles le service ART n'utilise pas le profil :

  • Le nom de fichier du profil est incorrect ou il ne se trouve pas à côté de l'APK.
  • Le profil n'est pas au bon format.
  • Le profil ne correspond pas à l'APK. (Les sommes de contrôle du profil ne correspondent pas à celles des fichiers .dex de l'APK.)