Otimizações em 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 nível 24) usa as seguintes restrições:

Se seu app usa qualquer um desses intents, remova as dependências deles assim que possível para segmentar os dispositivos com o Android 7.0 ou versões posteriores 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 oferecem mecanismos robustos para programar operações de rede quando condições especificadas, como uma conexão com uma rede ilimitada, são atendidas. Agora você também pode usar JobScheduler para reagir a mudanças nos provedores de conteúdo. Os objetos JobInfo encapsulam os parâmetros que JobScheduler usa para programar sua tarefa. Quando as condições da tarefa forem atendidas, o sistema a executará no JobService do app.

Neste documento, aprenderemos a usar métodos alternativos, como JobScheduler, para adaptar seu app a essas novas restrições.

Restrições iniciadas pelo usuário

A partir do Android 9 (API nível 28), se um app exibe 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 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 versões do AOSP, os apps restritos não podem executar tarefas, acionar alarmes nem usar a rede, exceto quando o app está no primeiro plano. Para saber os critérios usados para determinar se um app está no primeiro plano, consulte Restrições de serviços em segundo plano. Restrições específicas estão listadas em Restrições de gerenciamento de energia.

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

Os apps para o Android 7.0 (API nível 24) não recebem transmissões CONNECTIVITY_ACTION quando se registram para recebê-las no manifesto. Os 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 com Context.registerReceiver() continua recebendo essas transmissões enquanto o app está em execução.

Programar tarefas 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 do job. A amostra 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 a tarefa forem atendidas, o app receberá um callback para executar o método onStartJob() na JobService.class especificada. Para ver mais exemplos da implementação de JobScheduler, consulte o app de amostra JobScheduler.

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 podem ouvir 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.

Objetos NetworkRequest definem os parâmetros do callback de rede com relação a NetworkCapabilities. Crie objetos NetworkRequest com a classe NetworkRequest.Builder. registerNetworkCallback() Então, transmita 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 respectiva 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 nível 24), os apps não conseguem 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 nível 24) estende JobInfo e JobParameters para oferecer uma solução alternativa.

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

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

JobInfo.TriggerContentUri()
Encapsula os parâmetros necessários para acionar uma tarefa 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 uma tarefa, o sistema oferecerá um callback mesmo que ele relate mudança em apenas um dos URIs de conteúdo.
Adicione a sinalização TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS para acionar a tarefa se qualquer um dos descendentes do URI especificado mudar. 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 a existência de mudanças 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 uma tarefa a ser acionada 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 informa uma mudança nos URIs de conteúdo especificados, seu app recebe um callback, e um objeto JobParameters é transmitido ao método onStartJob() em MediaContentJob.class.

Determinar quais autoridades de conteúdo acionaram uma tarefa

O Android 7.0 (API 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, se ela foi acionada devido a um prazo ou 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 a tarefa. Se a matriz retornada não for null, use getTriggeredContentUris() para recuperar os detalhes de quais URIs mudaram.

A amostra de código a seguir modifica o método JobService.onStartJob() e registra as autoridades de conteúdo e os URIs que acionaram a tarefa.

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 (API nível 24) tome medidas para reduzir alguns desses problemas, recomendamos que você otimize seu app para ser executado totalmente sem esses processos em segundo plano.

O Android 7.0 (API nível 24) introduz alguns outros comandos do Android Debug Bridge (ADB) que podem ser usados para testar o comportamento do app quando esses 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 seguinte comando:
  •     $ 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 seguinte comando:
  •     $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND allow