Configuração do serviço de ART

Antes de começar, veja uma visão geral resumida do Serviço ART .

A partir do Android 14, a compilação AOT para aplicativos no dispositivo (também conhecida como dexopt) é feita pelo ART Service. O serviço ART faz parte do módulo ART e você pode personalizá-lo por meio de propriedades do sistema e APIs.

Propriedades do sistema

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

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

pm.dexopt.<reason>

Este é um conjunto de propriedades do sistema que determinam os filtros padrão do compilador para todos os motivos de compilação predefinidos descritos em cenários Dexopt .

Para obter mais informações, consulte Filtros do compilador .

Os valores padrão 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 )

Este é o filtro do compilador substituto para aplicativos usados ​​por outros aplicativos.

Em princípio, o ART Service faz compilação guiada por perfil ( speed-profile ) para todos os aplicativos quando possível, normalmente durante o dexopt em segundo plano. No entanto, existem alguns aplicativos que são usados ​​por outros aplicativos (por meio de <uses-library> ou carregados dinamicamente usando Context#createPackageContext com CONTEXT_INCLUDE_CODE ). Esses aplicativos não podem usar perfis locais por motivos de privacidade.

Para tal aplicativo, se a compilação guiada por perfil for solicitada, o ART Service primeiro tenta usar um perfil de nuvem. Se um perfil de nuvem não existir, o ART Service voltará a usar o filtro do compilador especificado por pm.dexopt.shared .

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

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

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

Observe que o efeito desta 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 threads 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 threads concorrentes é 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 threads simultâneos (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. Isto é particularmente útil durante a inicialização.

No entanto, ter muitas invocações 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 trocas desnecessárias de contexto. Portanto, esse número deve ser cuidadosamente ajustado produto por produto.

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

Se esta opção estiver definida, o ART Service apenas dexopts aplicativos usados ​​nos últimos dias.

Além disso, se o armazenamento estiver quase baixo, durante a dexopt em segundo plano, o ART Service faz downgrade do filtro do compilador de aplicativos 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 Storage Manager (configurável por meio das 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 fornecida por BatchDexoptStartCallback para bg-dexopt nunca serão rebaixados.

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

Isto é apenas para teste. Impede que o ART Service agende a tarefa dexopt em segundo plano.

Se o trabalho dexopt em segundo plano já estiver agendado, mas ainda não tiver sido executado, esta opção não terá efeito. Ou seja, o trabalho ainda será executado.

Uma sequência recomendada de comandos para evitar a execução do trabalho dexopt em segundo plano é:

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

A primeira linha impede que o trabalho dexopt em segundo plano seja agendado, se ainda não estiver agendado. A segunda linha desprograma o trabalho dexopt em segundo plano, se já estiver agendado, e cancela o trabalho dexopt em segundo plano imediatamente, se estiver em execução.

APIs de serviço ART

O ART Service 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 requer uma instância de PackageManagerLocal.FilteredSnapshot , que contém as informações de todos os aplicativos. Você pode obtê-lo chamando PackageManagerLocal#withFilteredSnapshot , onde 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;

Abaixo estão alguns casos de uso típicos das APIs.

Dexoptando um aplicativo

Você pode acionar o dexopt para qualquer aplicativo 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 passar seu próprio motivo dexopt. Se você fizer isso, a classe de prioridade e o filtro do compilador deverã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());
}

Cancelando dexopt

Se uma operação for iniciada por uma chamada dexoptPackage , você poderá passar um sinal de cancelamento, que permitirá 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 ART Service.

getArtManagerLocal().cancelBackgroundDexoptJob();

Obtendo resultados dexopt

Se uma operação for iniciada por uma chamada dexoptPackage , você poderá obter o resultado do valor de retorno.

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

// Process the result here.
...

O ART Service também inicia operações de dexopt em muitos cenários, como dexopt em segundo plano. Para ouvir 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 devem ser incluídas apenas atualizações no resultado. Se você deseja apenas ouvir pacotes atualizados pelo dexopt, defina-o como verdadeiro.

O segundo argumento é o executor do retorno de chamada. Para executar o retorno de chamada no mesmo thread que executa o dexopt, use Runnable::run . Se você não quiser que o retorno de chamada bloqueie o dexopt, use um executor assíncrono.

Você pode adicionar vários retornos de chamada e o ART Service executará todos eles sequencialmente. Todos os retornos de chamada permanecerão ativos para todas as chamadas futuras, a menos que você os remova.

Se você deseja remover um retorno de chamada, mantenha a referência do retorno de chamada 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);

Personalizando a lista de pacotes e/ou parâmetros dexopt

O ART Service inicia as próprias operações de dexopt durante a inicialização e o dexopt em segundo plano. Para personalizar a lista de pacotes e/ou 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.
      }
    });

Você pode adicionar itens à lista de pacotes, remover itens dela, classificá-la ou até mesmo usar uma lista completamente diferente.

Seu retorno de chamada deve ignorar motivos desconhecidos porque mais motivos poderão ser adicionados no futuro.

Você pode definir no máximo um BatchDexoptStartCallback . O retorno de chamada permanecerá ativo para todas as chamadas futuras, a menos que você o apague.

Se você quiser limpar o retorno de chamada, use ArtManagerLocal#clearBatchDexoptStartCallback .

getArtManagerLocal().clearBatchDexoptStartCallback();

Personalizando os parâmetros do trabalho dexopt em segundo plano

Por padrão, o trabalho 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));
    });

Você pode definir no máximo um ScheduleBackgroundDexoptJobCallback . O retorno de chamada permanecerá ativo para todas as chamadas futuras, a menos que você o apague.

Se você quiser limpar o retorno de chamada, use ArtManagerLocal#clearScheduleBackgroundDexoptJobCallback .

getArtManagerLocal().clearScheduleBackgroundDexoptJobCallback();

Desativando temporariamente o dexopt

Qualquer operação dexopt iniciada pelo ART Service aciona um BatchDexoptStartCallback . Você pode continuar cancelando as operações para desabilitar efetivamente o dexopt.

Se a operação cancelada for 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 dexopt, deverá combinar o código em um retorno de chamada.

// Bad example.

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

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

A operação dexopt realizada na instalação do aplicativo não é iniciada pelo ART Service. Em vez disso, ele é iniciado pelo gerenciador de pacotes por meio de uma chamada dexoptPackage . Portanto, não aciona BatchDexoptStartCallback . Para desabilitar o dexopt na instalação do aplicativo, evite que o gerenciador de pacotes chame dexoptPackage .

Outras personalizações

Configurando o limite térmico para dexopt de fundo

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

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

O trabalho dexopt em segundo plano é gerenciado pelo Job Scheduler e seu ID de trabalho é 27873780 . Para determinar se o trabalho está em execução, use 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.
  ...
}

Fornecendo um perfil para dexopt

Para usar um perfil para guiar o dexopt, coloque um arquivo .prof ou .dm próximo ao APK.

O arquivo .prof deve ser um arquivo de perfil em formato binário e o nome do arquivo deve ser o nome do APK + .prof . Por exemplo,

base.apk.prof

O nome do arquivo .dm deve 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 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 tempo de execução (ou seja, aqueles em /data/misc/profiles ), se houver, para garantir que o perfil próximo ao APK seja o único perfil que o ART Service pode usar. A segunda linha executa dexopt com speed-profile e passa -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 pelos quais o ART Service não usa o perfil incluem os seguintes:

  • O perfil tem um nome de arquivo errado ou não está próximo ao 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.)