Antes de comenzar, consulta una descripción general 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 módulo de ART y puedes personalizarlo a través de las propiedades y las APIs del sistema.
Propiedades del sistema
El servicio de ART admite todas las opciones de dex2oat pertinentes.
Además, el servicio de ART admite las siguientes propiedades del sistema:
pm.dexopt.<reason>
Este es un conjunto de propiedades del sistema que determinan los filtros del compilador predeterminados 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: speed)
Es el filtro de compilador de resguardo para las apps que usan otras apps.
En principio, ART Service realiza la compilación guiada por perfiles (speed-profile
) para todas las apps cuando es posible, por lo general, durante la 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.
En el caso de una app de este tipo, si se solicita la compilación guiada por perfiles, el servicio de ART primero intenta usar un perfil de nube. Si no existe un perfil de nube, el servicio de ART recurre al filtro del compilador especificado por pm.dexopt.shared
.
Si la compilación solicitada no se basa en el perfil, esta propiedad no tiene efecto.
pm.dexopt.<reason>.concurrency (valor predeterminado: 1)
Es la cantidad de invocaciones de dex2oat para ciertos motivos de compilación predefinidos (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 las 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 quepm.dexopt.<reason>.concurrency
controla la cantidad de invocaciones de dex2oat. Es decir, 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 limitan el uso del núcleo de la CPU, independientemente de la cantidad máxima de subprocesos simultáneos (que se analizó anteriormente).
Es posible que una sola invocación de dex2oat no utilice por completo todos los núcleos de CPU, independientemente de dalvik.vm.*dex2oat-threads
. Por lo tanto, aumentar la cantidad de invocaciones de dex2oat (pm.dexopt.<reason>.concurrency
) puede utilizar mejor los núcleos de CPU para acelerar el progreso general de dexopt. Esto es particularmente útil durante el arranque.
Sin embargo, tener demasiadas invocaciones de dex2oat puede hacer que el dispositivo se quede sin memoria, aunque esto se puede mitigar configurando dalvik.vm.dex2oat-swap
en true
para permitir el uso de un archivo de intercambio. Demasiadas invocaciones también podrían causar cambios de contexto innecesarios. Por lo tanto, este número debe ajustarse cuidadosamente para cada producto.
pm.dexopt.downgrade_after_inactive_days (valor predeterminado: no establecido)
Si se configura esta opción, el servicio de ART solo dexoptimizará las apps que se usaron en la cantidad de días especificada.
Además, si el almacenamiento está casi lleno, durante la dexopt en segundo plano, el servicio de ART degrada el filtro del compilador de las apps que no se usaron en la cantidad de días especificada más reciente para liberar espacio. El motivo del compilador para esto es inactive
, y el filtro del compilador se determina según pm.dexopt.inactive
. El umbral de espacio para activar esta función es el umbral de espacio bajo del Administrador de almacenamiento (configurable a través de los parámetros de configuración globales sys_storage_threshold_percentage
y sys_storage_threshold_max_bytes
, valor 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 degradan.
pm.dexopt.disable_bg_dexopt (valor predeterminado: false)
Esto es solo para pruebas. Evita que ART Service programe el trabajo de dexopt en segundo plano.
Si el trabajo de dexopt en segundo plano ya está programado, pero aún no se ejecutó, esta opción no tiene efecto. Es decir, el trabajo se seguirá ejecutando.
Una secuencia recomendada de comandos 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 evita que se programe el trabajo de dexopt en segundo plano, si aún no se programó. La segunda línea anula la programación del trabajo de dexopt en segundo plano, si ya está programado, y lo cancela de inmediato, si se está ejecutando.
APIs de ART Service
El servicio de ART expone APIs de Java para la personalización. Las APIs se definen en ArtManagerLocal
. Consulta el Javadoc en art/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
para ver los usos (fuente de Android 14, fuente de desarrollo sin lanzar).
ArtManagerLocal
es un singleton que contiene LocalManagerRegistry
. Una función auxiliar com.android.server.pm.DexOptHelper#getArtManagerLocal
te ayuda a obtenerlo.
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. Puedes obtenerlo llamando a PackageManagerLocal#withFilteredSnapshot
, donde PackageManagerLocal
también es un singleton que mantiene LocalManagerRegistry
y se puede obtener de com.android.server.pm.PackageManagerServiceUtils#getPackageManagerLocal
.
import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;
A continuación, se muestran algunos casos de uso típicos de las APIs.
Cómo activar dexopt para una app
Puedes activar 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 puedes pasar tu propio motivo de dexopt. Si lo haces, la clase de prioridad y el filtro del compilador se deben establecer de forma 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());
}
Cancela dexopt
Si una operación se inicia con una llamada a dexoptPackage
, puedes pasar un indicador de cancelación, que te permite cancelar la operación en algún momento. Esto puede ser útil cuando ejecutas dexopt de forma 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 la dexopt en segundo plano, que inicia ART Service.
getArtManagerLocal().cancelBackgroundDexoptJob();
Cómo obtener resultados de dexopt
Si una llamada a dexoptPackage
inicia una operación, puedes obtener el resultado del valor de devolución.
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 sí mismo 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 ART Service, 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 deseas escuchar los paquetes que actualiza 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á todas de forma secuencial. Todas las devoluciones de llamada permanecerán activas para todas las llamadas futuras, a menos que las quites.
Si quieres quitar una devolución de llamada, conserva la referencia de la devolución de llamada cuando la agregues y usa 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 de dexopt
El servicio de ART inicia las operaciones de dexopt por sí mismo durante el arranque y la 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 de paquetes, quitarlos, ordenarlos o incluso usar una lista completamente diferente.
Tu devolución de llamada debe ignorar los motivos desconocidos, ya que es posible que se agreguen más motivos en el futuro.
Puedes establecer como máximo un BatchDexoptStartCallback
. 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#clearBatchDexoptStartCallback
.
getArtManagerLocal().clearBatchDexoptStartCallback();
Personaliza 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 se está 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();
Inhabilita dexopt de forma temporal
Cualquier operación de dexopt que inicie ART Service activa un BatchDexoptStartCallback
. Puedes seguir cancelando las operaciones para inhabilitar dexopt de manera efectiva.
Si la operación que cancelas es dexopt en segundo plano, se sigue la política de reintentos 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 como máximo un BatchDexoptStartCallback
. Si también deseas 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();
La operación de dexopt que se realiza durante la instalación de la app no la inicia el servicio de ART. En cambio, 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 una devolución de llamada a través de setAdjustCompilerFilterCallback
. Se llama a la devolución de llamada cada vez que se va a realizar la optimización de dex de un paquete, sin importar si la inicia el servicio de ART durante el inicio y la optimización de dex en segundo plano, o bien una llamada a la API de dexoptPackage
.
Si no es necesario ajustar un paquete, la devolución de llamada debe devolver originalCompilerFilter
.
getArtManagerLocal().setAdjustCompilerFilterCallback(
Runnable::run,
(packageName, originalCompilerFilter, reason) -> {
if (isVeryImportantPackage(packageName)) {
return "speed-profile";
}
return originalCompilerFilter;
});
Solo puedes establecer un AdjustCompilerFilterCallback
. Si quieres 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 activa para todas las llamadas futuras, a menos que la borres.
Si quieres borrar la devolución de llamada, usa ArtManagerLocal#clearAdjustCompilerFilterCallback
.
getArtManagerLocal().clearAdjustCompilerFilterCallback();
Otras personalizaciones
El servicio de ART también admite otras personalizaciones.
Establece el umbral térmico para la optimización dex en segundo plano
El control térmico del trabajo de dexopt en segundo plano lo realiza Job Scheduler.
El trabajo se cancela de inmediato cuando la temperatura alcanza THERMAL_STATUS_MODERATE
. El umbral de THERMAL_STATUS_MODERATE
se puede ajustar.
Cómo determinar si se está ejecutando dexopt en segundo plano
El trabajo de dexopt en segundo plano se administra con Job Scheduler, y su ID de trabajo es 27873780
. Para determinar si el trabajo 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 de dexopt, coloca un archivo .prof
o un archivo .dm
junto al APK.
El archivo .prof
debe ser un archivo de perfil en formato binario, y el nombre del archivo debe ser el nombre del archivo APK más .prof
. Por ejemplo:
base.apk.prof
El nombre del archivo .dm
debe ser el mismo que el del APK, pero con la extensión reemplazada por .dm
. Por ejemplo:
base.dm
Para verificar que el perfil se esté usando 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 produjo el tiempo de ejecución (es decir, los que se encuentran en /data/misc/profiles
), si hay alguno, para asegurarse de que el perfil junto al APK sea el único que puede usar ART Service. La segunda línea ejecuta dexopt con speed-profile
y pasa -v
para imprimir el resultado detallado.
Si se usa 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}
Estos son algunos de los motivos típicos por los que el servicio de ART no usa el perfil:
- El perfil tiene un nombre de archivo incorrecto o no está junto al APK.
- El perfil tiene el formato incorrecto.
- El perfil no coincide con el APK. (Las sumas de verificación del perfil no coinciden con las de los archivos
.dex
del APK).