Configuração do serviço ART

Antes de começar, consulte a visão geral de alto nível 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 do ART. O serviço do ART faz parte do módulo do ART e pode ser personalizado por propriedades do sistema e APIs.

Propriedades do sistema

O serviço ART oferece suporte a todos os opções dodex2oat.

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

pm.dexopt.<reason>

É um conjunto de propriedades do sistema que determina os filtros padrão do compilador para todos os motivos de compilação predefinidos descritos em Cenários do 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 do ART faz a compilação guiada por perfil (speed-profile) para todos os apps quando possível, normalmente durante a dexopt em segundo plano. No entanto, existem alguns apps usados por outros apps (por meio do <uses-library> ou já carregados) dinamicamente usando Context#createPackageContext com CONTEXT_INCLUDE_CODE). Esses apps não podem usar o do YouTube por motivos de privacidade.

Para esse tipo de app, se a compilação guiada por perfil for solicitada, o serviço do ART vai tentar usar um perfil de nuvem primeiro. Se um perfil de nuvem não existir, o serviço do ART vai usar o filtro do compilador especificado por pm.dexopt.shared.

Se a compilação solicitada não for guiada por perfil, essa propriedade não terá efeito.

pm.dexopt.<reason>.concurrency (padrão: 1)

Este é o número de invocações de dex2oat para determinada compilação predefinida (first-boot, boot-after-ota, boot-after-mainline-update e bg-dexopt).

Observe que o efeito dessa opção é combinado opções de uso de recursos do 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 de dex2oat, enquanto pm.dexopt.<reason>.concurrency controla o número de invocações de dex2oat. Ou seja, o número máximo de threads simultâneos é de o produto das duas propriedades do sistema.
  • dalvik.vm.*dex2oat-cpu-set e os perfis de tarefa sempre limitam 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 de dex2oat pode não utilizar todos os núcleos da CPU, independentemente de dalvik.vm.*dex2oat-threads. Portanto, aumentar o número de dex2oat (pm.dexopt.<reason>.concurrency) podem utilizar melhor os núcleos da CPU para acelerar o progresso geral do dexopt. Isso é particularmente útil durante inicialização.

No entanto, ter muitas invocações de dex2oat pode fazer com que o dispositivo fique sem memória, mesmo que isso possa ser mitigado 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. Por isso, é preciso ajustar cuidadosamente esse número produto por produto.

pm.dexopt.downgrade_after_inactive_days (padrão: não definido)

Se essa opção estiver definida, o serviço do ART só vai desfazer a escolha de apps usados nos últimos dias.

Além disso, se o armazenamento estiver quase vazio, durante a dexopt em segundo plano, o serviço do ART vai fazer o downgrade do filtro do compilador de apps que não foram 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 por meio ArtManagerLocal#setBatchDexoptStartCallback, os pacotes na lista fornecido por BatchDexoptStartCallback para bg-dexopt nunca sofrem downgrade.

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

Isso é apenas para testes. Impede que o serviço ART programe o segundo plano dexopt.

Se o job de dexopt em segundo plano já estiver programado, mas ainda não tiver sido executado, esse não tem efeito. Ou seja, o job ainda será executado.

Uma sequência recomendada de comandos para evitar que o job dexopt em segundo plano seja em execução é:

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 for ainda não agendado. A segunda linha cancela a programação do job de dexopt em segundo plano, se ele já está agendado e cancela imediatamente o job dexopt em segundo plano, se quando ele está em execução.

APIs do serviço do 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 a obter 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. Você pode obtê-lo chamando 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;

Veja a seguir alguns casos de uso típicos das APIs.

Acionar 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 informar seu próprio motivo para o dexopt. Se você fizer isso, a classe de prioridade e o filtro do compilador precisa ser definido 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 sinal 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();

Você também pode cancelar o dexopt em segundo plano, que é iniciado pelo serviço ART.

getArtManagerLocal().cancelBackgroundDexoptJob();

Conferir os resultados do 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 do ART também inicia operações dexopt em muitos cenários, como dexopt em segundo plano. Para detectar todos os resultados de dexopt, seja a operação iniciada por uma chamada dexoptPackage ou pelo serviço do 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ê só quiser ouvir pacotes que são atualizados pelo dexopt, defina-o como verdadeiro.

O segundo argumento é o executor do callback. Para executar o callback na mesma linha de execução que executa 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 do ART vai executá-los sequencialmente. Todos os retornos de chamada permanecerão ativos para todas as chamadas futuras, a menos que removê-los.

Se você quiser remover um callback, mantenha a referência dele ao adicionar 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 do ART inicia as operações dexopt durante a inicialização e a dexopt em segundo plano. Para personalizar a lista de pacotes ou os parâmetros dexopt para essas 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.
      }
    });

É possível adicionar ou remover itens da lista de pacotes, classificá-los ou até mesmo usar uma lista completamente diferente.

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

É possível definir no máximo um BatchDexoptStartCallback. O callback permanecerá ativo para todas as chamadas futuras, a menos que você permita isso.

Se 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á ocioso e carregando. Isso pode ser alterado usando ArtManagerLocal#setScheduleBackgroundDexoptJobCallback:

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

É possível definir no máximo uma ScheduleBackgroundDexoptJobCallback. A chamada de retorno permanecerá ativo para todas as chamadas futuras, a menos que você o autorize.

Se quiser limpar o callback, use ArtManagerLocal#clearScheduleBackgroundDexoptJobCallback:

getArtManagerLocal().clearScheduleBackgroundDexoptJobCallback();

Desativar temporariamente o dexopt

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

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

Você pode ter no máximo uma 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 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 do ART. Em vez disso, ele é iniciado pelo gerenciador de pacotes por 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 versões mais recentes).

É possível substituir o filtro do compilador para determinados pacotes registrando um callback com setAdjustCompilerFilterCallback. O callback é chamado sempre que um pacote vai ser dexoptado, não importa se a dexopt é iniciada pelo serviço do 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 ajustes, o callback precisará retornar originalCompilerFilter.

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

Só é possível definir uma AdjustCompilerFilterCallback. Se você quiser usar AdjustCompilerFilterCallback para substituir o filtro do compilador para vários pacotes, combine o código em um callback. O callback permanece ativo para todas as chamadas futuras, a menos que você permita isso.

Se quiser limpar o callback, use ArtManagerLocal#clearAdjustCompilerFilterCallback:

getArtManagerLocal().clearAdjustCompilerFilterCallback();

Outras personalizações

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

Definir o limite térmico do dexopt em segundo plano

O Job Scheduler executa o controle térmico do job dexopt em segundo plano. 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 dexopt em segundo plano é gerenciado pelo JobScheduler, e o ID dele é 27873780. Para determinar se o job está em execução, use as APIs 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 o 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 de arquivo do APK + .prof. Por exemplo:

base.apk.prof

O nome do arquivo .dm precisa ser o mesmo do APK com o 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 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, os que estão em /data/misc/profiles), se houver, para garantir que o perfil ao lado do APK seja o único que o serviço do ART pode usar. A segunda linha executa dexopt com speed-profile e transmite -v para mostrar o resultado detalhado.

Se o perfil estiver sendo usado, você verá actualCompilerFilter=speed-profile no o 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 do 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. As somas de verificação no perfil não correspondem às somas de verificação dos arquivos .dex no APK.