Configuration du service ART

Avant de commencer, consultez une 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 via des propriétés système et des API.

Propriétés système

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

De plus, le service ART prend en charge 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 les scénarios Dexopt.

Pour en savoir plus, consultez la section 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 compilateur de remplacement 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 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 le profil, cette propriété n'a aucun effet.

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

Il s'agit du nombre d'appels de 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é aux options d'utilisation des ressources dex2oat (dalvik.vm.*dex2oat-threads, dalvik.vm.*dex2oat-cpu-set et les profils de tâche):

  • 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âche limitent toujours l'utilisation des cœurs de processeur, quel que soit le nombre maximal de threads simultanés (voir ci-dessus).

Une seule invocation de dex2oat peut ne pas utiliser pleinement tous les cœurs de processeur, quel que soit dalvik.vm.*dex2oat-threads. Par conséquent, augmenter le nombre d'appels 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 au démarrage.

Toutefois, un trop grand nombre d'appels de dex2oat peut entraîner une pénurie de mémoire sur l'appareil, même si cela peut être atténué en définissant dalvik.vm.dex2oat-swap sur true pour permettre l'utilisation d'un fichier d'échange. Un trop grand nombre d'appels peut également entraîner un changement de contexte inutile. Vous devez donc ajuster cette valeur pour chaque produit.

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

Si cette option est définie, le service ART ne déxopte que les applications utilisées au cours des derniers jours indiqués.

De plus, si l'espace de stockage est presque saturé, lors du déxopt en arrière-plan, le service ART rétrograde le filtre du compilateur des applications qui n'ont pas été utilisées au cours des derniers jours donnés, afin de libérer de l'espace. Le compilateur explique cela pour inactive, et le filtre de compilateur est déterminé par pm.dexopt.inactive. Le seuil d'espace pour déclencher cette fonctionnalité correspond au seuil d'espace insuffisant du Gestionnaire de stockage (configurable via les paramètres globaux 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 à des fins de test uniquement. Il empêche le service ART de planifier la tâche de décompilation en arrière-plan.

Si la tâche dexopt en arrière-plan est déjà planifiée, mais qu'elle n'a pas encore été exécutée, cette option n'a aucun effet. Autrement dit, la tâche 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 de la tâche dexopt en arrière-plan si elle n'est pas encore planifiée. La deuxième ligne annule la planification de la tâche dexopt en arrière-plan, si elle est déjà planifiée, et annule immédiatement la tâche dexopt en arrière-plan, si elle est en cours d'exécution.

API de service ART

Le service ART expose des API Java à des fins de personnalisation. Les API sont définies dans ArtManagerLocal. Consultez la documentation JavaDoc dans art/libartservice/service/java/com/android/server/art/ArtManagerLocal.java pour connaître 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 courants 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 raison de décompilation. Dans ce cas, la classe de priorité et le filtre du compilateur doivent être définis explicitement.

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 dexopt

Si une opération est lancée par un appel dexoptPackage, vous pouvez transmettre un signal d'annulation, ce 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 la décompilation en arrière-plan, qui est lancée par le service ART.

getArtManagerLocal().cancelBackgroundDexoptJob();

Obtenir des 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, tels que le dexopt en arrière-plan. Pour écouter tous les résultats de dexopt, que l'opération soit lancé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 si seules les mises à jour doivent être incluses 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 qui effectue dexopt, 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 au démarrage et en arrière-plan. Pour personnaliser la liste de 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 peuvent ê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 de la tâche 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 prochains appels, sauf si vous l'effacez.

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

getArtManagerLocal().clearScheduleBackgroundDexoptJobCallback();

Désactiver temporairement dexopt

Toute opération dexopt lancé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 déxopt en arrière-plan, elle suit la stratégie de nouvelle tentative par défaut (30 secondes, exponentielle, limitée à cinq 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 de packages ou les paramètres dexopt, vous devez combiner le code dans 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 lancée par ART Service. Il est plutôt lancé par le gestionnaire de paquets via un appel dexoptPackage. Par conséquent, il ne déclenche pas BatchDexoptStartCallback. Pour désactiver dexopt lors de l'installation de l'application, empêchez le gestionnaire de paquets d'appeler dexoptPackage.

Remplacer le filtre du compilateur pour certains packages (Android 15 et versions ultérieures)

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 déxopté, que le déxopt soit lancé par le service ART lors du démarrage et du déxopt en arrière-plan, ou par un appel d'API dexoptPackage.

Si un package ne nécessite pas d'ajustement, 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 dans un seul rappel. Le rappel reste actif pour tous les futurs appels, sauf si vous le videz.

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

getArtManagerLocal().clearAdjustCompilerFilterCallback();

Autres personnalisations

Le service ART prend également en charge d'autres personnalisations.

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

La régulation thermique de la tâche dexopt en arrière-plan est effectuée par Job Scheduler. 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 le 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, et le nom de fichier doit être le nom de fichier de l'APK + .prof. Par exemple :

base.apk.prof

Le nom de fichier 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 l'environnement d'exécution (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 profil que le service ART peut 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 les raisons courantes pour lesquelles le service ART n'utilise pas le profil:

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