Configuración del servicio de ART

Antes de comenzar, consulta una descripción general de alto nivel del servicio de ART.

A partir de Android 14, el servicio de ART controla la compilación AOT en el dispositivo para las apps (también conocida como dexopt). El servicio de ART forma parte del servicio de ART y puedes personalizarlo con las propiedades del sistema y las APIs.

Propiedades del sistema

El servicio de ART admite todas las opciones de dex2oat relevantes.

Además, el servicio de ART admite las siguientes propiedades del sistema:

pm.dexopt.<reason>

Este es un conjunto de propiedades del sistema que determina los filtros predeterminados del compilador. para todos los motivos de compilación predefinidos que se describen en situaciones de Dexopt.

Para obtener más información, consulta Filtros del compilador.

Los valores predeterminados estándar son los siguientes:

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 (predeterminado: velocidad)

Este es el filtro de compilador de resguardo para las apps que usan otras apps.

En principio, el servicio de ART realiza la compilación guiada por perfil (speed-profile) para todas las apps cuando es posible, por lo general, durante el dexopt en segundo plano. Sin embargo, hay algunas apps que usan otras apps (ya sea a través de <uses-library> o cargadas de forma dinámica con Context#createPackageContext con CONTEXT_INCLUDE_CODE). Estas apps no pueden usar perfiles locales por motivos de privacidad.

Para una app de este tipo, si se solicita una compilación guiada por perfil, primero el servicio de ART intenta usar un perfil en la nube. Si no existe un perfil de nube, el servicio de ART recurre al uso del filtro de compilador especificado por pm.dexopt.shared.

Si la compilación solicitada no está guiada por perfiles, esta propiedad no tiene efecto.

pm.dexopt.<reason>.concurrency (predeterminado: 1)

Este es el número de invocaciones de dex2oat para cierta compilación predefinida motivos (first-boot, boot-after-ota, boot-after-mainline-update y bg-dexopt).

Ten en cuenta que el efecto de esta opción se combina con opciones de uso de recursos de dex2oat (dalvik.vm.*dex2oat-threads, dalvik.vm.*dex2oat-cpu-set y los perfiles de tareas):

  • dalvik.vm.*dex2oat-threads controla la cantidad de subprocesos para cada invocación de dex2oat, mientras que pm.dexopt.<reason>.concurrency controla la cantidad de invocaciones de dex2oat. Es decir, si la cantidad máxima de subprocesos simultáneos es el producto de las dos propiedades del sistema.
  • dalvik.vm.*dex2oat-cpu-set y los perfiles de tareas siempre vinculan el núcleo de la CPU independientemente de la cantidad máxima de subprocesos simultáneos (análisis arriba).

Es posible que una sola invocación de dex2oat no use por completo todos los núcleos de la CPU, independientemente de dalvik.vm.*dex2oat-threads. Por lo tanto, aumentar el número de dex2oat las invocaciones (pm.dexopt.<reason>.concurrency) pueden usar mejor los núcleos de CPU para acelerar el progreso general del dexopt. Esto es particularmente útil durante inicio.

Sin embargo, tener demasiadas invocaciones de dex2oat podría provocar que el dispositivo se quede sin memoria, aunque esto se puede mitigar si configuras dalvik.vm.dex2oat-swap como true para permitir el uso de un archivo de intercambio. Demasiadas invocaciones también pueden provocar cambios de contexto innecesarios. Por lo tanto, este número debe ajustarse cuidadosamente producto por producto.

pm.dexopt.downgrade_after_inactive_days (valor predeterminado: sin establecer)

Si se establece esta opción, el servicio de ART solo dexopta las apps que se usaron en la última cantidad de días determinada.

Además, si el almacenamiento es casi bajo, durante el dexopt en segundo plano, el servicio de ART cambia a una versión inferior el filtro del compilador de aplicaciones que no se utilizan en las últimas aplicaciones cantidad de días, para liberar espacio. El motivo del compilador es inactive, y el filtro del compilador está determinado por pm.dexopt.inactive. El umbral de espacio para activar esta función es el umbral de espacio bajo del Administrador de almacenamiento (se puede configurar a través de la configuración global sys_storage_threshold_percentage y sys_storage_threshold_max_bytes, predeterminado: 500 MB) más 500 MB.

Si personalizas la lista de paquetes a través de ArtManagerLocal#setBatchDexoptStartCallback, los paquetes de la lista que proporciona BatchDexoptStartCallback para bg-dexopt nunca se cambian a una versión anterior.

pm.dexopt.disable_bg_dexopt (predeterminado: "false")

Esto es solo para pruebas. Impide que el servicio de ART programe la ejecución en segundo plano dexopt.

Si la tarea de dexopt en segundo plano ya está programada, pero aún no se ejecuta, esta opción no tiene efecto. Es decir, la tarea se ejecutará de todos modos.

Una secuencia de comandos recomendada para evitar que se ejecute el trabajo de dexopt en segundo plano es la siguiente:

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

La primera línea impide que se programe el trabajo de dexopt en segundo plano si está aún no está programado. La segunda línea anula la programación de la tarea de dexopt en segundo plano, si ya está programada, y la cancela de inmediato, si está en ejecución.

APIs de servicio de ART

El servicio de ART expone APIs de Java para la personalización. Las APIs se definen en ArtManagerLocal Consulta Javadoc en art/libartservice/service/java/com/android/server/art/ArtManagerLocal.java para (fuente de Android 14, fuente de desarrollo no publicada).

ArtManagerLocal es un singleton que contiene LocalManagerRegistry. Una función auxiliar com.android.server.pm.DexOptHelper#getArtManagerLocal te ayuda a obtenerla.

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

La mayoría de las APIs requieren una instancia de PackageManagerLocal.FilteredSnapshot, que contiene la información de todas las apps. Para obtenerlo, llama a PackageManagerLocal#withFilteredSnapshot, donde PackageManagerLocal también es un singleton de LocalManagerRegistry y que se puede obtener de com.android.server.pm.PackageManagerServiceUtils#getPackageManagerLocal

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

Los siguientes son algunos casos de uso típicos de las APIs.

Cómo activar dexopt para una app

Puedes activar el dexopt para cualquier app en cualquier momento llamando a ArtManagerLocal#dexoptPackage

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

También puede pasar su propio motivo de dexopt. Si haces eso, la clase de prioridad y el filtro del compilador debe establecerse de manera explícita.

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

Cancelar dexopt

Si una llamada a dexoptPackage inicia una operación, puedes pasar un como indicador de cancelación, que te permite cancelar la operación en algún momento. Esto puede puede ser útil cuando se ejecuta dexopt de manera asíncrona.

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

También puedes cancelar el dexopt en segundo plano, que inicia el servicio de ART.

getArtManagerLocal().cancelBackgroundDexoptJob();

Cómo obtener resultados de dexopt

Si una operación se inicia con una llamada a dexoptPackage, puedes obtener el resultado del valor que se muestra.

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

// Process the result here.
...

El servicio de ART también inicia operaciones de dexopt por su cuenta en muchas situaciones, como dexopt en segundo plano. Para escuchar todos los resultados de dexopt, ya sea que la operación se inicie con una llamada a dexoptPackage o con el servicio de ART, usa ArtManagerLocal#addDexoptDoneCallback.

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

El primer argumento determina si solo se deben incluir actualizaciones en el resultado. Si solo quieres escuchar los paquetes que se actualizan con dexopt, configúralo como verdadero.

El segundo argumento es el ejecutor de la devolución de llamada. Para ejecutar la devolución de llamada en el mismo subproceso que realiza dexopt, usa Runnable::run. Si no quieres que la devolución de llamada bloquee dexopt, usa un ejecutor asíncrono.

Puedes agregar varias devoluciones de llamadas, y el servicio de ART las ejecutará de forma secuencial. Todas las devoluciones de llamada permanecerán activas para todas las llamadas futuras, a menos que y quitarlos.

Si deseas quitar una devolución de llamada, conserva la referencia cuando agregarlo y usar 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);

Personaliza la lista de paquetes y los parámetros dexopt

El servicio de ART inicia las operaciones de dexopt por sí mismo durante el inicio y el dexopt en segundo plano. Para personalizar la lista de paquetes o los parámetros de dexopt para esas operaciones, usa 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.
      }
    });

Puedes agregar elementos a la lista del paquete, quitarlos, ordenarla o incluso usar una lista completamente diferente.

Tu devolución de llamada debe ignorar motivos desconocidos, ya que es posible que se agreguen más motivos en el futuro.

Puedes establecer un máximo de un BatchDexoptStartCallback. La devolución de llamada permanecerá activo para todas las llamadas futuras, a menos que lo borres.

Si quieres borrar la devolución de llamada, usa ArtManagerLocal#clearBatchDexoptStartCallback.

getArtManagerLocal().clearBatchDexoptStartCallback();

Personalizar los parámetros del trabajo de dexopt en segundo plano

De forma predeterminada, el trabajo de dexopt en segundo plano se ejecuta una vez al día cuando el dispositivo está inactivo. y cargando. Esto se puede cambiar con ArtManagerLocal#setScheduleBackgroundDexoptJobCallback.

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

Puedes establecer como máximo un ScheduleBackgroundDexoptJobCallback. La devolución de llamada permanecerá activa para todas las llamadas futuras, a menos que la borres.

Si quieres borrar la devolución de llamada, usa ArtManagerLocal#clearScheduleBackgroundDexoptJobCallback.

getArtManagerLocal().clearScheduleBackgroundDexoptJobCallback();

Inhabilitar dexopt temporalmente

Cualquier operación de dexopt que inicie el servicio de ART activará una BatchDexoptStartCallback Puedes seguir cancelando las operaciones para inhabilitar dexopt de manera eficaz.

Si la operación que cancelas es dexopt en segundo plano, sigue la política de reintento predeterminada (30 segundos, exponencial, con un límite de 5 horas).

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

Puedes tener un BatchDexoptStartCallback como máximo. Si también quieres usar BatchDexoptStartCallback para personalizar la lista de paquetes o los parámetros de dexopt. debes combinar el código en una devolución de llamada.

// Bad example.

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

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

ART no inicia la operación dexopt que se realiza en la instalación de la app. Servicio. En su lugar, el administrador de paquetes lo inicia a través de una llamada a dexoptPackage. Por lo tanto, no activa BatchDexoptStartCallback. Para inhabilitar dexopt en la instalación de la app, evita que el administrador de paquetes llame a dexoptPackage.

Anula el filtro del compilador para ciertos paquetes (Android 15 y versiones posteriores)

Puedes anular el filtro del compilador para ciertos paquetes registrando un a través de setAdjustCompilerFilterCallback. Se llama a la devolución de llamada cada vez que se va a dexoptar un paquete, sin importar que el servicio de ART inicie la dexoptación durante el inicio y la dexoptación en segundo plano, o mediante una llamada a la API de dexoptPackage.

Si un paquete no necesita ajustes, la devolución de llamada debe mostrar originalCompilerFilter.

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

Puedes establecer solo un AdjustCompilerFilterCallback. Si deseas usar AdjustCompilerFilterCallback para anular el filtro del compilador para varios paquetes, debes combinar el código en una devolución de llamada. La devolución de llamada permanece activo para todas las llamadas futuras, a menos que lo borres.

Si deseas borrar la devolución de llamada, usa ArtManagerLocal#clearAdjustCompilerFilterCallback

getArtManagerLocal().clearAdjustCompilerFilterCallback();

Otras personalizaciones

El servicio de ART también admite algunas otras personalizaciones.

Establece el umbral térmico para dexopt en segundo plano

Job Scheduler realiza el control térmico del trabajo de dexopt en segundo plano. El trabajo se cancela de inmediato cuando la temperatura alcanza THERMAL_STATUS_MODERATE El umbral de THERMAL_STATUS_MODERATE se puede ajustar.

Determina si se está ejecutando el dexopt en segundo plano

Job Scheduler administra la tarea de dexopt en segundo plano, y su ID de trabajo es 27873780. Para determinar si la tarea se está ejecutando, usa las APIs de 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.
  ...
}

Proporciona un perfil para dexopt

Para usar un perfil como guía para dexopt, coloca un archivo .prof o .dm junto al archivo APK

El archivo .prof debe ser un archivo de perfil en formato binario, y el nombre debe ser el nombre del archivo del APK + .prof. Por ejemplo:

base.apk.prof

El nombre del archivo .dm debe ser el nombre del APK con la extensión reemplazada por .dm. Por ejemplo:

base.dm

Para verificar que se esté usando el perfil para dexopt, ejecuta dexopt con speed-profile y verifica el resultado.

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

La primera línea borra todos los perfiles que produce el entorno de ejecución (es decir, los que se encuentran en /data/misc/profiles), si los hay, para asegurarse de que el perfil junto al APK sea el único que el servicio de ART pueda usar. La segunda línea ejecuta dexopt con speed-profile y pasa -v para imprimir el resultado detallado.

Si se está usando el perfil, verás actualCompilerFilter=speed-profile en el resultado. De lo contrario, verás actualCompilerFilter=verify. Por ejemplo:

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}

Entre los motivos típicos por los que el servicio de ART no usa el perfil, se incluyen los siguientes:

  • El perfil tiene un nombre de archivo incorrecto o no está junto al APK.
  • El perfil tiene un formato incorrecto.
  • El perfil no coincide con el APK. (Las sumas de comprobación del perfil no coinciden con las sumas de comprobación de los archivos .dex del APK)