Configurazione del servizio ART

Prima di iniziare, visualizza una panoramica generale di ART Service.

A partire da Android 14, la compilazione AOT on-device per le app (nota anche come dexopt) è gestita da ART Service. Il servizio ART fa parte del modulo ART e puoi personalizzarlo tramite proprietà di sistema e API.

Proprietà di sistema

Il servizio ART supporta tutte le opzioni dex2oat pertinenti.

Inoltre, il servizio ART supporta le seguenti proprietà di sistema:

pm.dexopt.<reason>

Si tratta di un insieme di proprietà di sistema che determinano i filtri del compilatore predefiniti per tutti i motivi di compilazione predefiniti descritti negli scenari Dexopt.

Per ulteriori informazioni, consulta Filtri del compilatore.

I valori predefiniti standard sono:

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 (valore predefinito: velocità)

Si tratta del filtro del compilatore di riserva per le app utilizzate da altre app.

In linea di principio, il servizio ART esegue la compilazione basata su profilo (speed-profile) per tutte le app, se possibile, in genere durante la dexopt in background. Tuttavia, esistono alcune app che vengono utilizzate da altre app (tramite <uses-library> o caricate dinamicamente utilizzando Context#createPackageContext con CONTEXT_INCLUDE_CODE). Queste app non possono utilizzare i profili locali per motivi di privacy.

Per un'app di questo tipo, se viene richiesta la compilazione basata su profilo, ART Service tenta innanzitutto di utilizzare un profilo cloud. Se non esiste un profilo cloud, il servizio ART ricorre all'utilizzo del filtro del compilatore specificato da pm.dexopt.shared.

Se la compilazione richiesta non è basata su profilo, questa proprietà non ha alcun effetto.

pm.dexopt.<reason>.concurrency (valore predefinito: 1)

Si tratta del numero di invocazioni di dex2oat per determinati motivi di compilazione predefiniti (first-boot, boot-after-ota, boot-after-mainline-update e bg-dexopt).

Tieni presente che l'effetto di questa opzione viene combinato con le opzioni di utilizzo delle risorse dex2oat (dalvik.vm.*dex2oat-threads,dalvik.vm.*dex2oat-cpu-set e i profili delle attività):

  • dalvik.vm.*dex2oat-threads controlla il numero di thread per ogni chiamata a dex2oat, mentre pm.dexopt.<reason>.concurrency controlla il numero di chiamate a dex2oat. In altre parole, il numero massimo di thread simultanei è il prodotto delle due proprietà di sistema.
  • dalvik.vm.*dex2oat-cpu-set e i profili delle attività vincolano sempre l'utilizzo del core della CPU, indipendentemente dal numero massimo di thread simultanei (discusso sopra).

Una singola chiamata a dex2oat potrebbe non utilizzare completamente tutti i core della CPU, indipendentemente da dalvik.vm.*dex2oat-threads. Pertanto, aumentare il numero di invocazioni di dex2oat (pm.dexopt.<reason>.concurrency) può utilizzare meglio i core della CPU per accelerare l'avanzamento complessivo di dexopt. Questo è particolarmente utile durante il boot.

Tuttavia, un numero eccessivo di chiamate dex2oat potrebbe causare un esaurimento della memoria del dispositivo, anche se questo problema può essere attenuato impostando dalvik.vm.dex2oat-swap su true per consentire l'utilizzo di un file di scambio. Troppe invocazioni potrebbero anche causare un cambio di contesto non necessario. Pertanto, questo numero deve essere calibrato attentamente su ciascun prodotto.

pm.dexopt.downgrade_after_inactive_days (valore predefinito: non impostato)

Se questa opzione è impostata, il servizio ART deseleziona le app utilizzate negli ultimi determinati giorni.

Inoltre, se lo spazio di archiviazione è quasi esaurito, durante il dexopt in background, il servizio ART esegue il downgrade del filtro del compilatore delle app che non vengono utilizzate nell'ultimo numero di giorni specificato per liberare spazio. Il motivo del compilatore è inactive e il filtro del compilatore è determinato da pm.dexopt.inactive. La soglia di spazio per attivare questa funzionalità è la soglia di spazio ridotto di Storage Manager (configurabile tramite le impostazioni globali sys_storage_threshold_percentage e sys_storage_threshold_max_bytes, valore predefinito: 500 MB) più 500 MB.

Se personalizzi l'elenco dei pacchetti tramite ArtManagerLocal#setBatchDexoptStartCallback, i pacchetti nell'elenco fornito da BatchDexoptStartCallback per bg-dexopt non vengono mai sottoposti a downgrade.

pm.dexopt.disable_bg_dexopt (valore predefinito: false)

Questo è solo per i test. Impedisce al servizio ART di pianificare il job di indicizzazione in background.

Se il job dexopt in background è già pianificato, ma non è ancora stato eseguito, questa opzione non ha alcun effetto. Ciò significa che il job continuerà a essere eseguito.

Una sequenza consigliata di comandi per impedire l'esecuzione del job dexopt in background è:

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

La prima riga impedisce la pianificazione del job dexopt in background, se non è ancora stato pianificato. La seconda riga annulla la pianificazione del job dexopt in background, se è già pianificato, e annulla immediatamente il job dexopt in background, se è in esecuzione.

API ART Service

Il servizio ART espone le API Java per la personalizzazione. Le API sono definite in ArtManagerLocal. Consulta la documentazione Javadoc in art/libartservice/service/java/com/android/server/art/ArtManagerLocal.java per gli utilizzi (codice sorgente di Android 14, codice sorgente di sviluppo non rilasciato).

ArtManagerLocal è un singleton detenuto da LocalManagerRegistry. Una funzione di supporto com.android.server.pm.DexOptHelper#getArtManagerLocal ti aiuta a ottenerla.

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

La maggior parte delle API richiede un'istanza di PackageManagerLocal.FilteredSnapshot, che contiene le informazioni di tutte le app. Puoi ottenerlo chiamando PackageManagerLocal#withFilteredSnapshot, dove PackageManagerLocal è anche un singleton tenuto da LocalManagerRegistry e può essere ottenuto da com.android.server.pm.PackageManagerServiceUtils#getPackageManagerLocal.

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

Di seguito sono riportati alcuni casi d'uso tipici delle API.

Attivare dexopt per un'app

Puoi attivare dexopt per qualsiasi app in qualsiasi momento chiamando ArtManagerLocal#dexoptPackage.

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

Puoi anche indicare un motivo per la decisione. In questo caso, la classe di priorità e il filtro del compilatore devono essere impostati esplicitamente.

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

Annulla dexopt

Se un'operazione viene avviata da una chiamata dexoptPackage, puoi passare un indicatore di annullamento, che ti consente di annullare l'operazione in un determinato momento. Questo può essere utile quando esegui dexopt in modo asincrono.

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

Puoi anche annullare la dexopt in background, che viene avviata da ART Service.

getArtManagerLocal().cancelBackgroundDexoptJob();

Ottenere i risultati di dexopt

Se un'operazione viene avviata da una chiamata dexoptPackage, puoi ottenere il risultato dal valore restituito.

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

// Process the result here.
...

Il servizio ART avvia anche le operazioni di dexopt in molti scenari, ad esempio la dexopt in background. Per ascoltare tutti i risultati di dexopt, indipendentemente dal fatto che l'operazione sia stata avviata da una chiamata dexoptPackage o dal servizio ART, utilizza ArtManagerLocal#addDexoptDoneCallback.

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

Il primo argomento determina se includere solo gli aggiornamenti nel risultato. Se vuoi ascoltare solo i pacchetti aggiornati da dexopt, impostalo su true.

Il secondo argomento è l'executor del callback. Per eseguire il callback sullo stesso thread che esegue dexopt, utilizza Runnable::run. Se non vuoi che il callback blocchi dexopt, utilizza un'esecuzione asincrona.

Puoi aggiungere più callback e il servizio ART li eseguirà tutti in sequenza. Tutti i rilanci rimarranno attivi per tutte le chiamate future, a meno che non li rimuovi.

Se vuoi rimuovere un richiamo, mantieni il riferimento del richiamo quando lo aggiunti e utilizza 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);

Personalizzare l'elenco dei pacchetti e i parametri dexopt

ART Service avvia le operazioni dexopt autonomamente durante l'avvio e in background dexopt. Per personalizzare l'elenco dei pacchetti o i parametri dexopt per queste operazioni, utilizza 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.
      }
    });

Puoi aggiungere elementi all'elenco dei pacchetti, rimuoverli, ordinarli o persino utilizzare un elenco completamente diverso.

La chiamata di ritorno deve ignorare i motivi sconosciuti perché in futuro potrebbero essere aggiunti altri motivi.

Puoi impostare al massimo un BatchDexoptStartCallback. Il numero di richiamata rimarrà attivo per tutte le chiamate future, a meno che non lo cancelli.

Se vuoi cancellare il callback, utilizza ArtManagerLocal#clearBatchDexoptStartCallback.

getArtManagerLocal().clearBatchDexoptStartCallback();

Personalizzare i parametri del job dexopt in background

Per impostazione predefinita, il job dexopt in background viene eseguito una volta al giorno quando il dispositivo è inattivo e in carica. Questa impostazione può essere modificata utilizzando ArtManagerLocal#setScheduleBackgroundDexoptJobCallback.

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

Puoi impostare al massimo un ScheduleBackgroundDexoptJobCallback. Il numero di richiamata rimane attivo per tutte le chiamate future, a meno che tu non lo cancelli.

Se vuoi cancellare il richiamata, usa ArtManagerLocal#clearScheduleBackgroundDexoptJobCallback.

getArtManagerLocal().clearScheduleBackgroundDexoptJobCallback();

Disattivare temporaneamente dexopt

Qualsiasi operazione dexopt avviata dal servizio ART attiva un BatchDexoptStartCallback. Puoi continuare ad annullare le operazioni per disattivare efficacemente dexopt.

Se l'operazione annullata è dexopt in background, segue il criterio di ripetizione predefinito (30 secondi, esponenziale, limitato a 5 ore).

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

Puoi avere al massimo un BatchDexoptStartCallback. Se vuoi utilizzare anche BatchDexoptStartCallback per personalizzare l'elenco di pacchetti o i parametri dexopt, devi combinare il codice in un solo callback.

// Bad example.

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

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

L'operazione dexopt eseguita durante l'installazione dell'app non viene avviata da ART Service. Viene invece avviato dal gestore dei pacchetti tramite una chiamata dexoptPackage. Pertanto, non attivaBatchDexoptStartCallback. Per disattivare dexopt all'installazione dell'app, impedisci al gestore dei pacchetti di chiamare dexoptPackage.

Eseguire l'override del filtro del compilatore per determinati pacchetti (Android 15 e versioni successive)

Puoi eseguire l'override del filtro del compilatore per determinati pacchetti registrando un callback tramite setAdjustCompilerFilterCallback. Il callback viene chiamato ogni volta che un pacchetto deve essere sottoposto a dexopt, indipendentemente dal fatto che la dexopt sia stata avviata dal servizio ART durante l'avvio e la dexopt in background o da una chiamata all'API dexoptPackage.

Se un pacchetto non richiede modifiche, il callback deve restituire originalCompilerFilter.

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

Puoi impostare un solo AdjustCompilerFilterCallback. Se vuoi utilizzare AdjustCompilerFilterCallback per eseguire l'override del filtro del compilatore per più pacchetti, devi combinare il codice in un solo callback. Il numero di richiamata rimane attivo per tutte le chiamate future, a meno che tu non lo cancelli.

Se vuoi cancellare il callback, utilizza ArtManagerLocal#clearAdjustCompilerFilterCallback.

getArtManagerLocal().clearAdjustCompilerFilterCallback();

Altre personalizzazioni

Il servizio ART supporta anche alcune altre personalizzazioni.

Impostare la soglia termica per la dexopt in background

Il controllo termico del job dexopt in background viene eseguito da Job Scheduler. Il job viene annullato immediatamente quando la temperatura raggiunge THERMAL_STATUS_MODERATE. La soglia di THERMAL_STATUS_MODERATE è regolabile.

Determinare se dexopt in background è in esecuzione

Il job dexopt in background è gestito da Job Scheduler e il relativo ID job è 27873780. Per determinare se il job è in esecuzione, utilizza le 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.
  ...
}

Fornisci un profilo per dexopt

Per utilizzare un profilo per guidare dexopt, inserisci un file .prof o un file .dm accanto all'APK.

Il file .prof deve essere un file di profilo in formato binario e il nome del file deve essere il nome del file APK + .prof. Ad esempio,

base.apk.prof

Il nome del file .dm deve corrispondere al nome del file APK con l'estensione sostituita da .dm. Ad esempio,

base.dm

Per verificare che il profilo venga utilizzato per dexopt, esegui dexopt con speed-profile e controlla il risultato.

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

La prima riga cancella tutti i profili prodotti dal runtime (ovvero quelli in /data/misc/profiles), se presenti, per assicurarsi che il profilo accanto all'APK sia l'unico che ART Service può eventualmente utilizzare. La seconda riga esegue dexopt con speed-profile e passa -v per stampare il risultato dettagliato.

Se il profilo è in uso, nel risultato viene visualizzato actualCompilerFilter=speed-profile. Altrimenti, vedrai actualCompilerFilter=verify. Ad esempio,

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}

Ecco alcuni motivi tipici per cui ART Service non utilizza il profilo:

  • Il profilo ha un nome file errato o non si trova accanto all'APK.
  • Il profilo non è nel formato corretto.
  • Il profilo non corrisponde all'APK. I checksum nel profilo non corrisponde ai checksum dei file .dex nell'APK.