Otimização do segundo plano

Os processos em segundo plano podem consumir muita memória e bateria. Por exemplo, uma transmissão implícita pode iniciar muitos processos em segundo plano que foram registrados para ouvi-la, mesmo que esses processos não tenham tanta utilidade. Isso pode ter um impacto significativo no desempenho do dispositivo e na experiência do usuário.

Para amenizar esse problema, o Android 7.0 (API de nível 24) usa as seguintes restrições:

Caso seu app use qualquer um desses intents, remova as dependências deles assim que possível para segmentar os dispositivos de destino com o Android 7.0 ou versões mais recentes corretamente. O framework do Android oferece várias soluções para reduzir a necessidade dessas transmissões implícitas. Por exemplo, JobScheduler e o novo WorkManager fornecem mecanismos robustos para programar operações de rede quando condições especificadas, como uma conexão com uma rede ilimitada, forem atendidas. Agora também é possível usar JobScheduler para reagir às mudanças nos provedores de conteúdo. Os objetos JobInfo encapsulam os parâmetros que JobScheduler usa para programar seu job. Quando as condições do job forem atendidas, o sistema vai executar o job no JobService do seu app.

Nesta página, vamos explicar o uso de métodos alternativos, como JobScheduler, para adaptar seu app a essas novas restrições.

Restrições iniciadas pelo usuário

Na página Uso da bateria nas configurações do sistema, o usuário pode escolher uma destas opções:

  • Sem restrições: permite executar todo o trabalho em segundo plano, podendo consumir mais bateria.
  • Otimizado (padrão): otimiza a capacidade do app de executar trabalhos em segundo plano de acordo com as interações do usuário.
  • Restrito: impede totalmente a execução de apps em segundo plano. Pode fazer com que os apps não funcionem como esperado.

Se um app apresenta alguns dos maus comportamentos descritos no Android vitals, o sistema solicita que o usuário restrinja o acesso desse app aos recursos do sistema.

Se o sistema perceber que um app está consumindo recursos em excesso, ele vai notificar o usuário e dará a opção de restringir as ações do app. Os comportamentos que podem acionar o aviso incluem:

  • Wake locks em excesso: um wake lock parcial mantido por uma hora quando a tela está desativada.
  • Serviços em segundo plano em excesso: o app segmenta níveis de API inferiores a 26 e há excesso de serviços em segundo plano

As restrições precisas impostas são determinadas pelo fabricante do dispositivo. Por exemplo, em builds do AOSP que executam o Android 9 (nível 28 da API) ou versões mais recentes, apps em execução em segundo plano no estado "restrito" têm as limitações abaixo:

  • Não é possível iniciar serviços em primeiro plano
  • Serviços em primeiro plano existentes são removidos
  • Alarmes não são acionados
  • Jobs não são executados

Além disso, se um app for direcionado ao Android 13 (nível 33 da API) ou versões mais recentes e estiver no estado "restrito", o sistema não envia as transmissões BOOT_COMPLETED e LOCKED_BOOT_COMPLETED até que o app seja iniciado por outros motivos.

Restrições específicas estão listadas em Restrições do gerenciamento de energia.

Restrições para o recebimento de transmissões de atividade de rede

Os apps para o Android 7.0 (API de nível 24) não recebem transmissões CONNECTIVITY_ACTION quando se registram para recebê-las no manifesto. Processos que dependem dessa transmissão não serão iniciados. Isso pode representar um problema para apps que querem ouvir mudanças de rede ou realizar atividades de rede em massa quando o dispositivo se conecta a uma rede ilimitada. Várias soluções para contornar essa restrição já existem no framework do Android, mas escolher o caminho certo depende do que você quer que o app faça.

Observação: um BroadcastReceiver registrado em Context.registerReceiver() continuará a receber essas transmissões enquanto o app estiver em execução.

Programar jobs de rede em conexões ilimitadas

Ao usar a classe JobInfo.Builder para criar seu objeto JobInfo, aplique o método setRequiredNetworkType() e transmita JobInfo.NETWORK_TYPE_UNMETERED como um parâmetro de trabalho. o mesmo exemplo de código a seguir programa um serviço para ser executado quando o dispositivo se conectar a uma rede ilimitada enquanto estiver sendo carregado:

Kotlin

const val MY_BACKGROUND_JOB = 0
...
fun scheduleJob(context: Context) {
    val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    val job = JobInfo.Builder(
            MY_BACKGROUND_JOB,
            ComponentName(context, MyJobService::class.java)
    )
            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
            .setRequiresCharging(true)
            .build()
    jobScheduler.schedule(job)
}

Java

public static final int MY_BACKGROUND_JOB = 0;
...
public static void scheduleJob(Context context) {
  JobScheduler js =
      (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
  JobInfo job = new JobInfo.Builder(
    MY_BACKGROUND_JOB,
    new ComponentName(context, MyJobService.class))
      .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
      .setRequiresCharging(true)
      .build();
  js.schedule(job);
}

Quando as condições para o job forem atendidas, o app vai receber um callback para executar o método onStartJob() na JobService.class especificada. Para mais exemplos de implementação do JobScheduler, consulte o app de exemplo JobScheduler (link em inglês).

Uma nova alternativa para o JobScheduler é o WorkManager, uma API que permite programar tarefas em segundo plano que precisam de uma conclusão garantida, independentemente de o processo estar ativo ou não. O WorkManager escolhe a maneira apropriada de executar o trabalho (diretamente em uma linha de execução no processo do app ou usando JobScheduler, FirebaseJobDispatcher ou AlarmManager) com base em fatores como o nível da API do dispositivo. Além disso, o WorkManager não requer o Google Play Services e oferece vários recursos avançados, como encadeamento de tarefas ou verificação do status de uma tarefa. Para saber mais, consulte WorkManager.

Monitorar a conectividade de rede enquanto o app está em execução

Os apps em execução ainda poderão escutar CONNECTIVITY_CHANGE com um BroadcastReceiver registrado. No entanto, a API ConnectivityManager fornece um método mais robusto para solicitar um callback somente quando as condições de rede especificadas são atendidas.

Os objetos NetworkRequest definem os parâmetros do callback de rede em termos de NetworkCapabilities. Você cria objetos NetworkRequest com a classe NetworkRequest.Builder. Em seguida, registerNetworkCallback() transmite o objeto NetworkRequest para o sistema. Quando as condições da rede são atendidas, o app recebe um callback para executar o método onAvailable() definido na classe ConnectivityManager.NetworkCallback.

O app continua recebendo callbacks até o app sair ou chamar unregisterNetworkCallback().

Restrições para o recebimento de transmissões de imagens e vídeos

No Android 7.0 (API de nível 24), os apps não podem enviar ou receber transmissões ACTION_NEW_PICTURE ou ACTION_NEW_VIDEO. Essa restrição ajuda a reduzir os impactos no desempenho e na experiência do usuário quando vários apps precisam ser ativados para processar uma nova imagem ou vídeo. O Android 7.0 (API de nível 24) estende JobInfo e JobParameters para fornecer uma solução alternativa.

Acionar jobs em mudanças de URI de conteúdo

Para acionar jobs em mudanças de URI de conteúdo, o Android 7.0 (API de nível 24) estende a API JobInfo com os seguintes métodos:

JobInfo.TriggerContentUri()
Encapsula os parâmetros necessários para acionar um job em mudanças de URI de conteúdo.
JobInfo.Builder.addTriggerContentUri()
Transmite um objeto TriggerContentUri para JobInfo. Um ContentObserver monitora o URI de conteúdo encapsulado. Se houver vários objetos TriggerContentUri associados a um job, o sistema fornecerá um callback mesmo que ele relate uma mudança em apenas um dos URIs de conteúdo.
Adicione a sinalização TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS para acionar o job em caso de mudança em qualquer um dos descendentes do URI especificado. Essa sinalização corresponde ao parâmetro notifyForDescendants transmitido para registerContentObserver().

Observação: TriggerContentUri() não pode ser usado em combinação com setPeriodic() ou setPersisted(). Para monitorar continuamente as alterações de conteúdo, programe um novo JobInfo antes que o JobService do app termine de processar o callback mais recente.

A amostra de código a seguir programa um job a ser acionado quando o sistema informa uma mudança no URI de conteúdo, MEDIA_URI.

Kotlin

const val MY_BACKGROUND_JOB = 0
...
fun scheduleJob(context: Context) {
    val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    val job = JobInfo.Builder(
            MY_BACKGROUND_JOB,
            ComponentName(context, MediaContentJob::class.java)
    )
            .addTriggerContentUri(
                    JobInfo.TriggerContentUri(
                            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                            JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS
                    )
            )
            .build()
    jobScheduler.schedule(job)
}

Java

public static final int MY_BACKGROUND_JOB = 0;
...
public static void scheduleJob(Context context) {
  JobScheduler js =
          (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
  JobInfo.Builder builder = new JobInfo.Builder(
          MY_BACKGROUND_JOB,
          new ComponentName(context, MediaContentJob.class));
  builder.addTriggerContentUri(
          new JobInfo.TriggerContentUri(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
          JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
  js.schedule(builder.build());
}

Quando o sistema indicar uma mudança nos URIs de conteúdo especificados, o app receberá um callback e um objeto JobParameters será transmitido para o método onStartJob() na MediaContentJob.class.

Determinar quais autoridades de conteúdo acionaram um job

O Android 7.0 (API de nível 24) também estende JobParameters para permitir que o app receba informações úteis sobre quais autoridades de conteúdo e URIs acionaram o job:

Uri[] getTriggeredContentUris()
Retorna uma matriz de URIs que acionaram o job. Isso será null se nenhum URI tiver acionado o job (por exemplo, o job foi acionado devido a um prazo ou algum outro motivo) ou se o número de URIs alterados for maior que 50.
String[] getTriggeredContentAuthorities()
Retorna uma matriz de strings de autoridades de conteúdo que acionaram o job. Se a matriz retornada não for null, use getTriggeredContentUris() para recuperar os detalhes sobre quais URIs foram modificados.

O exemplo de código abaixo modifica o método JobService.onStartJob() e registra as autoridades de conteúdo e os URIs que acionaram o job.

Kotlin

override fun onStartJob(params: JobParameters): Boolean {
    StringBuilder().apply {
        append("Media content has changed:\n")
        params.triggeredContentAuthorities?.also { authorities ->
            append("Authorities: ${authorities.joinToString(", ")}\n")
            append(params.triggeredContentUris?.joinToString("\n"))
        } ?: append("(No content)")
        Log.i(TAG, toString())
    }
    return true
}

Java

@Override
public boolean onStartJob(JobParameters params) {
  StringBuilder sb = new StringBuilder();
  sb.append("Media content has changed:\n");
  if (params.getTriggeredContentAuthorities() != null) {
      sb.append("Authorities: ");
      boolean first = true;
      for (String auth :
          params.getTriggeredContentAuthorities()) {
          if (first) {
              first = false;
          } else {
             sb.append(", ");
          }
           sb.append(auth);
      }
      if (params.getTriggeredContentUris() != null) {
          for (Uri uri : params.getTriggeredContentUris()) {
              sb.append("\n");
              sb.append(uri);
          }
      }
  } else {
      sb.append("(No content)");
  }
  Log.i(TAG, sb.toString());
  return true;
}

Otimizar ainda mais seu app

A otimização dos seus apps para serem executados em dispositivos com pouca memória ou em condições de pouca memória pode melhorar o desempenho e a experiência do usuário. A remoção de dependências em serviços em segundo plano e de broadcast receivers implícitos registrados pelo manifesto pode ajudar o app a funcionar melhor nesses dispositivos. Embora o Android 7.0 (nível 24 da API) tome medidas para reduzir alguns desses problemas, recomendamos que você otimize seu app para ser executado totalmente sem esses processos em segundo plano.

Os comandos do Android Debug Bridge (adb) abaixo podem ajudar a testar o comportamento do app quando os processos em segundo plano estão desativados:

  • Para simular condições em que transmissões implícitas e serviços em segundo plano estão indisponíveis, digite o comando abaixo:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND ignore
    
  • Para reativar transmissões implícitas e serviços em segundo plano, digite o comando abaixo:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND allow
    
  • É possível simular o comportamento do usuário colocando seu app no estado "restrito" para uso da bateria em segundo plano. Essa configuração impede que o app seja executado em segundo plano. Para fazer isso, execute os comandos abaixo em uma janela de terminal:
  • $ adb shell cmd appops set <PACKAGE_NAME> RUN_ANY_IN_BACKGROUND deny