Configuração do serviço ART

Antes de começar, confira uma visão geral do serviço 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 você pode personalizá-lo usando propriedades e APIs do sistema.

Propriedades do sistema

O serviço ART oferece suporte a todas as opções relevantes do dex2oat.

Além disso, o serviço ART oferece suporte às seguintes propriedades do sistema:

pm.dexopt.<reason>

Esse é 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: speed)

Esse é o filtro de compilador de fallback 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, normalmente durante o dexopt em segundo plano. No entanto, há alguns apps que são usados por outros (usando <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 na nuvem. Se um perfil na 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)

Esse é o número de invocações 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 tarefa):

  • dalvik.vm.*dex2oat-threads controla o número de linhas de execução para cada invocação dex2oat, enquanto pm.dexopt.<reason>.concurrency controla o número de invocações dex2oat. Ou seja, o número máximo de linhas de execução simultâneas é o produto das duas propriedades do sistema.
  • dalvik.vm.*dex2oat-cpu-set e os perfis de tarefa sempre vinculam o uso do núcleo da CPU, independentemente do número máximo de linhas de execução simultâneas (discutido acima).

Uma única invocação dex2oat pode não utilizar totalmente todos os núcleos da CPU, independentemente de dalvik.vm.*dex2oat-threads. Portanto, aumentar o número de invocações dex2oat (pm.dexopt.<reason>.concurrency) pode utilizar melhor os núcleos da CPU para acelerar o progresso geral do dexopt. Isso é particularmente ú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 fazer o dexopt de apps usados nos últimos dias.

Além disso, se o armazenamento estiver quase cheio, durante o dexopt em segundo plano, o serviço ART vai fazer o downgrade do filtro de compilador de apps que não são usados nos últimos dias para liberar espaço. O motivo do compilador para isso é inactive, e o filtro do compilador é determinado por pm.dexopt.inactive. O limite de espaço para acionar esse recurso é o limite de espaço baixo 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 por BatchDexoptStartCallback para bg-dexopt nunca serão rebaixados.

pm.dexopt.disable_bg_dexopt (padrão: false)

Isso é apenas para testes. Ele impede que o serviço ART programe o job de 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 de 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 de dexopt em segundo plano seja programado, se ainda não estiver programado. A segunda linha desprograma o job de dexopt em segundo plano, se ele já estiver programado, e o cancela imediatamente, se estiver em execução.

APIs do serviço ART

O serviço ART expõe APIs Java para personalização. As APIs sã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 obtê-lo.

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. Você pode acessá-la chamando PackageManagerLocal#withFilteredSnapshot, em que PackageManagerLocal também é um singleton mantido por LocalManagerRegistry e pode ser obtido em com.android.server.pm.PackageManagerServiceUtils#getPackageManagerLocal.

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

Confira a seguir 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 o dexopt

Se uma operação for iniciada por uma chamada dexoptPackage, você poderá transmitir um sinal de cancelamento, que permite cancelar a operação em algum momento. Isso pode ser útil ao executar 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();

Você também pode cancelar o dexopt em segundo plano, que é iniciado 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 de dexopt em muitos cenários, como o dexopt em segundo plano. Para detectar todos os resultados de 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 as atualizações serão incluídas no resultado. Se você quiser detectar apenas pacotes atualizados pelo dexopt, defina como verdadeiro.

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.

Você pode adicionar vários callbacks, e o serviço ART vai executar todos eles sequencialmente. 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 de dexopt

O serviço ART inicia operações de dexopt durante a inicialização e o dexopt em segundo plano. Para personalizar a lista de pacotes ou os parâmetros de 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 itens à lista de pacotes, remover itens dela, classificar ou até mesmo usar uma lista completamente diferente.

Seu callback precisa ignorar motivos desconhecidos, porque mais motivos podem ser adicionados no futuro.

Você pode definir no máximo um BatchDexoptStartCallback. O callback vai permanecer ativo para todas as chamadas futuras, a menos que você o limpe.

Se você quiser limpar o callback, use ArtManagerLocal#clearBatchDexoptStartCallback.

getArtManagerLocal().clearBatchDexoptStartCallback();

Personalizar os parâmetros do job de dexopt em segundo plano

Por padrão, o job de dexopt em segundo plano é executado uma vez por dia quando o dispositivo está inativo e carregando. Isso pode ser alterado usando ArtManagerLocal#setScheduleBackgroundDexoptJobCallback.

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

Você pode definir no máximo um ScheduleBackgroundDexoptJobCallback. O callback vai permanecer ativo para todas as chamadas futuras, a menos que você o limpe.

Se você quiser limpar o callback, use ArtManagerLocal#clearScheduleBackgroundDexoptJobCallback.

getArtManagerLocal().clearScheduleBackgroundDexoptJobCallback();

Desativar temporariamente o dexopt

Qualquer operação de dexopt iniciada pelo serviço ART aciona um BatchDexoptStartCallback. Você pode continuar cancelando as operações para desativar o dexopt.

Se a operação cancelada for o dexopt em segundo plano, ela seguirá a política de repetição padrão (30 segundos, exponencial, limitado 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);

Você pode ter no máximo um BatchDexoptStartCallback. Se você também quiser usar BatchDexoptStartCallback para personalizar a lista de pacotes ou os parâmetros de dexopt, combine o código em um callback.

// Bad example.

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

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

A operação de dexopt realizada na instalação do app não é iniciada pelo serviço ART. Em vez disso, ela é iniciada pelo gerenciador de pacotes por meio de uma chamada dexoptPackage. Portanto, ela 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 e mais recentes)

Você pode substituir o filtro do compilador para determinados pacotes registrando um callback usando setAdjustCompilerFilterCallback. O callback é chamado sempre que um pacote é dexoptado, não importa se o dexopt é iniciado pelo serviço ART durante a inicialização e o dexopt em segundo plano ou por uma chamada de API dexoptPackage.

Se um pacote não precisar de ajuste, o callback precisará retornar originalCompilerFilter.

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

Você pode definir apenas um AdjustCompilerFilterCallback. Se você quiser usar AdjustCompilerFilterCallback para substituir o filtro do compilador de vários pacotes, combine o código em um callback. O callback vai permanecer ativo para todas as chamadas futuras, a menos que você o limpe.

Se você quiser limpar o callback, use ArtManagerLocal#clearAdjustCompilerFilterCallback.

getArtManagerLocal().clearAdjustCompilerFilterCallback();

Outras personalizações

O serviço ART também oferece suporte a algumas outras personalizações.

Definir o limite térmico para o dexopt em segundo plano

O controle térmico do job de dexopt em segundo plano é realizado pelo programador de jobs. O job é cancelado imediatamente quando a temperatura atinge THERMAL_STATUS_MODERATE. O limite de THERMAL_STATUS_MODERATE é ajustável.

Determinar se o dexopt em segundo plano está em execução

O job de dexopt em segundo plano é gerenciado pelo programador de jobs, e o ID do job é 27873780. Para determinar se o job está em execução, use as APIs do programador de jobs.

// 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 de 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 nome do arquivo 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 o dexopt com speed-profile e verifique 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 ambiente 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 possa usar. A segunda linha executa o dexopt com speed-profile e transmite -v para imprimir o resultado detalhado.

Se o perfil estiver sendo usado, você verá actualCompilerFilter=speed-profile no resultado. Caso contrário, você 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 seguinte:

  • 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.)