Antes de começar, confira uma visão geral do Serviço de ART.
A partir do Android 14, a compilação AOT no dispositivo para apps (também conhecida como dexopt) é processada pelo serviço ART. O serviço ART faz parte do módulo ART e pode ser personalizado usando propriedades do sistema e APIs.
Propriedades do sistema
O serviço ART é compatível com todas as opções dex2oat relevantes.
Além disso, o serviço ART é compatível com as seguintes propriedades do sistema:
pm.dexopt.<reason>
É um conjunto de propriedades do sistema que determinam os filtros de compilador padrão para todos os motivos de compilação predefinidos descritos em Cenários de dexopt.
Para mais informações, consulte Filtros do compilador.
Os valores padrão são:
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 (padrão: velocidade)
Esse é o filtro de compilador substituto para apps usados por outros apps.
Em princípio, o serviço ART faz a compilação orientada por perfil (speed-profile
) para
todos os apps quando possível, geralmente durante a dexopt em segundo plano. No entanto, alguns apps são usados por outros apps (por <uses-library>
ou carregados dinamicamente usando Context#createPackageContext
com CONTEXT_INCLUDE_CODE
). Esses apps não podem usar perfis locais por motivos de privacidade.
Para um app desse tipo, se a compilação orientada por perfil for solicitada, o serviço ART primeiro
tentará usar um perfil de nuvem. Se um perfil de nuvem não existir, o serviço ART vai usar o filtro de compilador especificado por pm.dexopt.shared
.
Se a compilação solicitada não for orientada por perfil, essa propriedade não terá efeito.
pm.dexopt.<reason>.concurrency (padrão: 1)
É o número de invocações do dex2oat para determinados motivos de compilação predefinidos (first-boot
, boot-after-ota
, boot-after-mainline-update
e bg-dexopt
).
O efeito dessa opção é combinado com as opções de uso de recursos dex2oat (dalvik.vm.*dex2oat-threads
, dalvik.vm.*dex2oat-cpu-set
e os perfis de tarefas):
dalvik.vm.*dex2oat-threads
controla o número de linhas de execução para cada invocação do dex2oat, enquantopm.dexopt.<reason>.concurrency
controla o número de invocações do dex2oat. Ou seja, o número máximo de threads simultâneas é o produto das duas propriedades do sistema.dalvik.vm.*dex2oat-cpu-set
e os perfis de tarefas sempre limitam o uso do núcleo da CPU, independente do número máximo de linhas de execução simultâneas (discutido acima).
Uma única invocação do dex2oat pode não usar totalmente todos os núcleos da CPU, independente
de dalvik.vm.*dex2oat-threads
. Portanto, aumentar o número de invocações do dex2oat (pm.dexopt.<reason>.concurrency
) pode utilizar melhor os núcleos da CPU para acelerar o progresso geral do dexopt. Isso é especialmente útil durante a inicialização.
No entanto, ter muitas invocações dex2oat pode fazer com que o dispositivo fique sem
memória, embora isso possa ser atenuado definindo dalvik.vm.dex2oat-swap
como
true
para permitir o uso de um arquivo de troca. Muitas invocações também podem causar
mudanças de contexto desnecessárias. Portanto, esse número precisa ser ajustado com cuidado para cada produto.
pm.dexopt.downgrade_after_inactive_days (padrão: não definido)
Se essa opção estiver definida, o serviço ART só vai dexoptar apps usados no período especificado.
Além disso, se o armazenamento estiver quase cheio, durante a dexopt em segundo plano, o serviço ART
fará downgrade do filtro do compilador de apps que não foram usados no período
especificado para liberar espaço. O motivo do compilador é inactive
, e o filtro do compilador é determinado por pm.dexopt.inactive
. O limite de espaço para acionar esse recurso é o limite de pouco espaço do Gerenciador de armazenamento (configurável nas configurações globais sys_storage_threshold_percentage
e sys_storage_threshold_max_bytes
, padrão: 500 MB) mais 500 MB.
Se você personalizar a lista de pacotes usando
ArtManagerLocal#setBatchDexoptStartCallback
, os pacotes na lista fornecida
pelo BatchDexoptStartCallback
para bg-dexopt
nunca vão passar por downgrade.
pm.dexopt.disable_bg_dexopt (padrão: false)
Isso é apenas para testes. Isso impede que o serviço ART programe o job dexopt em segundo plano.
Se o job de dexopt em segundo plano já estiver programado, mas ainda não tiver sido executado, essa opção não terá efeito. Ou seja, o job ainda será executado.
Uma sequência recomendada de comandos para impedir que o job dexopt em segundo plano seja executado é:
setprop pm.dexopt.disable_bg_dexopt true
pm bg-dexopt-job --disable
A primeira linha impede que o job dexopt em segundo plano seja programado, se ainda não tiver sido. A segunda linha cancela o agendamento do job dexopt em segundo plano, se ele já estiver agendado, e cancela o job dexopt em segundo plano imediatamente, se ele estiver em execução.
APIs do serviço ART
O serviço ART expõe APIs Java para personalização. As APIs estão definidas em
ArtManagerLocal
. Consulte o Javadoc em
art/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
para
usos (fonte do Android 14, fonte de desenvolvimento não lançada).
ArtManagerLocal
é um singleton mantido por LocalManagerRegistry
. Uma função auxiliar com.android.server.pm.DexOptHelper#getArtManagerLocal
ajuda você a fazer isso.
import static com.android.server.pm.DexOptHelper.getArtManagerLocal;
A maioria das APIs exige uma instância de PackageManagerLocal.FilteredSnapshot
, que contém as informações de todos os apps. Para isso, chame
PackageManagerLocal#withFilteredSnapshot
, em que PackageManagerLocal
também é
um singleton mantido por LocalManagerRegistry
e pode ser obtido de
com.android.server.pm.PackageManagerServiceUtils#getPackageManagerLocal
.
import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;
Confira alguns casos de uso típicos das APIs.
Acionar o dexopt para um app
Você pode acionar o dexopt para qualquer app a qualquer momento chamando
ArtManagerLocal#dexoptPackage
.
try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
getArtManagerLocal().dexoptPackage(
snapshot,
"com.google.android.calculator",
new DexoptParams.Builder(ReasonMapping.REASON_INSTALL).build());
}
Você também pode transmitir seu próprio motivo de dexopt. Se você fizer isso, a classe de prioridade e o filtro do compilador precisarão ser definidos explicitamente.
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
Se uma operação for iniciada por uma chamada dexoptPackage
, será possível transmitir um
indicador de cancelamento, que permite cancelar a operação em algum momento. Isso pode
ser útil quando você executa o dexopt de forma assí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();
Também é possível cancelar a dexopt em segundo plano, que é iniciada pelo serviço ART.
getArtManagerLocal().cancelBackgroundDexoptJob();
Receber resultados de dexopt
Se uma operação for iniciada por uma chamada dexoptPackage
, você poderá receber o resultado
do valor de retorno.
DexoptResult result;
try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
result = getArtManagerLocal().dexoptPackage(...);
}
// Process the result here.
...
O serviço ART também inicia operações dexopt por conta própria em muitos cenários, como
dexopt em segundo plano. Para detectar todos os resultados do dexopt, seja a operação iniciada por uma chamada dexoptPackage
ou pelo serviço ART, use ArtManagerLocal#addDexoptDoneCallback
.
getArtManagerLocal().addDexoptDoneCallback(
false /* onlyIncludeUpdates */,
Runnable::run,
(result) -> {
// Process the result here.
...
});
O primeiro argumento determina se apenas as atualizações serão incluídas no resultado. Se você quiser ouvir apenas pacotes atualizados pelo dexopt, defina como "true".
O segundo argumento é o executor do callback. Para executar o callback na mesma linha de execução que realiza o dexopt, use Runnable::run
. Se você não quiser que o
callback bloqueie o dexopt, use um executor assíncrono.
É possível adicionar vários callbacks, e o serviço ART vai executar todos eles em sequência. Todos os callbacks vão permanecer ativos para todas as chamadas futuras, a menos que você os remova.
Se você quiser remover um callback, mantenha a referência dele ao
adicioná-lo e use 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);
Personalizar a lista de pacotes e os parâmetros dexopt
O serviço ART inicia as operações de dexopt durante a inicialização e a
dexopt em segundo plano. Para personalizar a lista de pacotes ou os parâmetros dexopt dessas operações,
use 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.
}
});
Você pode adicionar, remover ou classificar itens na lista de pacotes ou até mesmo usar uma lista completamente diferente.
Seu callback precisa ignorar motivos desconhecidos, porque mais motivos podem ser adicionados no futuro.
É possível definir no máximo um BatchDexoptStartCallback
. O callback vai permanecer
ativo para todas as chamadas futuras, a menos que você o limpe.
Se quiser limpar o callback, use
ArtManagerLocal#clearBatchDexoptStartCallback
.
getArtManagerLocal().clearBatchDexoptStartCallback();
Personalizar os parâmetros do job dexopt em segundo plano
Por padrão, o job dexopt em segundo plano é executado uma vez por dia quando o dispositivo está inativo
e carregando. Isso pode ser mudado usando
ArtManagerLocal#setScheduleBackgroundDexoptJobCallback
.
getArtManagerLocal().setScheduleBackgroundDexoptJobCallback(
Runnable::run,
builder -> {
builder.setPeriodic(TimeUnit.DAYS.toMillis(2));
});
É possível definir no máximo um ScheduleBackgroundDexoptJobCallback
. O callback vai
permanecer ativo para todas as chamadas futuras, a menos que você o limpe.
Se quiser limpar o callback, use
ArtManagerLocal#clearScheduleBackgroundDexoptJobCallback
.
getArtManagerLocal().clearScheduleBackgroundDexoptJobCallback();
Desativar temporariamente o dexopt
Qualquer operação dexopt iniciada pelo serviço ART aciona um
BatchDexoptStartCallback
. Continue cancelando as operações para desativar o dexopt.
Se a operação cancelada for dexopt em segundo plano, ela seguirá a política de nova tentativa padrão (30 segundos, exponencial, limitada a 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);
É possível ter no máximo um BatchDexoptStartCallback
. Se você também quiser usar
BatchDexoptStartCallback
para personalizar a lista de pacotes ou os parâmetros dexopt,
combine o código em um único callback.
// Bad example.
// Disable dexopt.
getArtManagerLocal().unscheduleBackgroundDexoptJob();
// Re-enable dexopt.
getArtManagerLocal().scheduleBackgroundDexoptJob();
A operação dexopt realizada na instalação do app não é iniciada pelo serviço
ART. Em vez disso, ele é iniciado pelo gerenciador de pacotes com uma chamada
dexoptPackage
. Portanto, ele não aciona BatchDexoptStartCallback
. Para desativar o dexopt na instalação do app, impeça que o
gerenciador de pacotes chame dexoptPackage
.
Substituir o filtro do compilador para determinados pacotes (Android 15 ou mais recente)
É possível substituir o filtro do compilador para determinados pacotes registrando um
callback usando setAdjustCompilerFilterCallback
. O callback é chamado
sempre que um pacote vai ser dexoptado, não importa se a dexopt é iniciada pelo
serviço ART durante a inicialização e a dexopt em segundo plano ou por uma chamada de API dexoptPackage
.
Se um pacote não precisar de ajuste, o callback vai retornar
originalCompilerFilter
.
getArtManagerLocal().setAdjustCompilerFilterCallback(
Runnable::run,
(packageName, originalCompilerFilter, reason) -> {
if (isVeryImportantPackage(packageName)) {
return "speed-profile";
}
return originalCompilerFilter;
});
Só é possível definir um AdjustCompilerFilterCallback
. Se você quiser usar
AdjustCompilerFilterCallback
para substituir o filtro do compilador em vários
pacotes, combine o código em um único callback. O callback permanece
ativo para todas as chamadas futuras, a menos que você o limpe.
Se quiser limpar o callback, use
ArtManagerLocal#clearAdjustCompilerFilterCallback
.
getArtManagerLocal().clearAdjustCompilerFilterCallback();
Outras personalizações
O serviço ART também aceita outras personalizações.
Definir o limite térmico para dexopt em segundo plano
O controle térmico do job dexopt em segundo plano é realizado pelo Job Scheduler.
O job é cancelado imediatamente quando a temperatura atinge THERMAL_STATUS_MODERATE
. O limite de THERMAL_STATUS_MODERATE
pode ser ajustado.
Determinar se o dexopt em segundo plano está em execução
O job dexopt em segundo plano é gerenciado pelo Job Scheduler, e o ID dele é
27873780
. Para determinar se o job está em execução, use as APIs do 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.
...
}
Fornecer um perfil para dexopt
Para usar um perfil para orientar o dexopt, coloque um arquivo .prof
ou .dm
ao lado do
APK.
O arquivo .prof
precisa ser um arquivo de perfil em formato binário, e o nome do arquivo precisa ser
o nome do arquivo do APK + .prof
. Por exemplo,
base.apk.prof
O nome do arquivo .dm
precisa ser o mesmo do APK, com a extensão substituída por .dm
. Por exemplo,
base.dm
Para verificar se o perfil está sendo usado para dexopt, execute dexopt com
speed-profile
e confira o resultado.
pm art clear-app-profiles <package-name>
pm compile -m speed-profile -f -v <package-name>
A primeira linha limpa todos os perfis produzidos pelo tempo de execução (ou seja, aqueles em
/data/misc/profiles
), se houver, para garantir que o perfil ao lado do APK seja
o único que o serviço ART pode usar. A segunda linha executa o dexopt
com speed-profile
e transmite -v
para imprimir o resultado detalhado.
Se o perfil estiver em uso, você vai ver actualCompilerFilter=speed-profile
no
resultado. Caso contrário, você vai ver actualCompilerFilter=verify
. Por exemplo,
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}
Os motivos típicos para o serviço ART não usar o perfil incluem:
- O perfil tem um nome de arquivo incorreto ou não está ao lado do APK.
- O perfil está no formato errado.
- O perfil não corresponde ao APK. Os checksums no perfil não correspondem aos checksums dos arquivos
.dex
no APK.