Introdução

A maioria dos aplicativos do mundo real precisa executar tarefas em segundo plano de longa duração. Por exemplo, um aplicativo pode carregar arquivos para um servidor, sincronizar dados de um servidor e salvá-los em um banco de dados Room, enviar registros para um servidor ou executar operações caras em dados. Essas operações devem ser realizadas em segundo plano, fora da thread de IU (thread principal). As tarefas em segundo plano consomem os recursos limitados de um dispositivo, como RAM e bateria. Isso pode resultar em uma experiência ruim para o usuário se não for manuseado corretamente.

Neste tutorial, você aprende como usar o WorkManager para agendar uma tarefa em segundo plano de forma otimizada e eficiente. Para saber mais sobre outras soluções disponíveis para processamento em segundo plano no Android, consulte o Guia para processamento em segundo plano.

O que você já deveria saber

O que aprenderá

O que fará

Neste tutorial, você trabalha no aplicativo DevBytes que desenvolveu em um tutorial anterior. (Se você não tem este aplicativo, pode baixar o código inicial para esta lição).

O aplicativo DevBytes exibe uma lista de vídeos DevByte, que são tutoriais curtos feitos pela equipe de relações de desenvolvedor do Google Android. Os vídeos apresentam os recursos do desenvolvedor e as práticas recomendadas para o desenvolvimento Android.

Você aprimora a experiência do usuário no aplicativo obtendo previamente os vídeos uma vez por dia. Isso garante que o usuário obtenha novos conteúdos assim que abrir o aplicativo.

Nesta tarefa, você baixa e inspeciona o código inicial.

Etapa 1: Baixe e execute o aplicativo inicial

Você pode continuar trabalhando por meio do aplicativo DevBytes que você criou no tutorial anterior (se tiver). Alternativamente, você pode baixar o aplicativo inicial.

Nesta tarefa, você baixa e executa o aplicativo inicial e examina o código inicial.

  1. Se você ainda não tem o aplicativo DevBytes, baixe o código inicial DevBytes para este tutorial do projeto DevBytesRepository do GitHub.
  2. Descompacte o código e abra o projeto no Android Studio.
  3. Conecte seu dispositivo de teste ou emulador à Internet, se ainda não estiver conectado. Construa e execute o aplicativo. O aplicativo busca a lista de vídeos DevByte da rede e os exibe.
  4. No aplicativo, toque em qualquer vídeo para abri-lo no aplicativo do YouTube.

Etapa 2: Explore o código

O aplicativo inicial vem com muitos códigos que foram introduzidos no tutorial anterior. O código inicial para este tutorial tem rede, interface do usuário, cache offline e módulos de repositório. Você pode se concentrar no agendamento da tarefa em segundo plano usando o WorkManager.

  1. No Android Studio, expanda todos os pacotes.
  2. Explore o pacote database. O pacote contém as entidades do banco de dados e o banco de dados local, que é implementado usando Room.
  3. Explore o pacote do repository. O pacote contém a classe VideosRepository que abstrai a camada de dados do resto do aplicativo.
  4. Explore o resto do código inicial por conta própria e com a ajuda do tutorial anterior.

WorkManager é um dos Componentes de arquitetura do Android e parte do Android Jetpack. WorkManager é para trabalho em segundo plano que pode ser adiado e requer execução garantida:

Enquanto o WorkManager executa o trabalho em segundo plano, ele cuida dos problemas de compatibilidade e das práticas recomendadas para a integridade da bateria e do sistema. WorkManager oferece compatibilidade de volta ao nível de API 14. WorkManager escolhe uma maneira apropriada de agendar uma tarefa em segundo plano, dependendo do nível de API do dispositivo. Ele pode usar JobScheduler (na API 23 e superior) ou uma combinação de AlarmManager e BroadcastReceiver.

O WorkManager também permite definir critérios de execução da tarefa em segundo plano. Por exemplo, você pode desejar que a tarefa seja executada apenas quando o status da bateria, o status da rede ou o estado de carga atenderem a certos critérios. Você aprenderá a definir restrições posteriormente neste tutorial.

Neste tutorial, você programa uma tarefa para obter previamente a lista de reprodução de vídeo DevBytes da rede uma vez por dia. Para agendar esta tarefa, você usa a biblioteca WorkManager.

  1. Abra o arquivo build.gradle (Module:app) e adicione a dependência WorkManager ao projeto.

    Se você usar a versão mais recente da biblioteca, o aplicativo de solução deve compilar conforme o esperado. Caso contrário, tente resolver o problema ou reverta para a versão da biblioteca mostrada abaixo.
def work_version = "1.0.1"
implementation "android.arch.work:work-runtime-ktx:$work_version"
  1. Sincronize seu projeto e certifique-se de que não haja erros de compilação.

Antes de adicionar código ao projeto, familiarize-se com as seguintes classes na biblioteca WorkManager:

Etapa 1: Crie um trabalhador

Nesta tarefa, você adiciona um Worker para pré-buscar a lista de reprodução de vídeo DevBytes em segundo plano.

  1. Dentro do pacote devbyteviewer, crie um pacote chamado work.
  2. Dentro do pacote work, crie uma classe Kotlin chamada RefreshDataWorker.
  3. Estenda a classe RefreshDataWorker da classe CoroutineWorker. Passe no context e WorkerParameters como parâmetros do construtor.
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
       CoroutineWorker(appContext, params) {
}
  1. Para resolver o erro da classe abstrata, substitua o método doWork() dentro da classe RefreshDataWorker.
override suspend fun doWork(): Result {
  return Result.success()
}

Uma função de suspensão é uma função que pode ser pausada e retomada posteriormente. Uma função de suspensão pode executar uma operação de longa duração e esperar que ela seja concluída sem bloquear a thread principal.

Etapa 2: Implemente doWork()

O método doWork() dentro da classe Worker é chamado em uma thread de segundo plano. O método executa o trabalho de forma síncrona e deve retornar um objeto ListenableWorker.Result. O sistema Android fornece a um Worker no máximo 10 minutos para terminar sua execução e retornar um objeto ListenableWorker.Result. Após esse tempo expirar, o sistema interrompe à força o Worker.

Para criar um objeto ListenableWorker.Result, chame um dos seguintes métodos estáticos para indicar o status de conclusão do trabalho em segundo plano:

Nesta tarefa, você implementa o método doWork() para buscar a lista de reprodução de vídeo DevBytes da rede. Você pode reutilizar os métodos existentes na classe VideosRepository para recuperar os dados da rede.

  1. Na classe RefreshDataWorker, dentro de doWork(), crie e instancie um objeto VideosDatabase e um objeto VideosRepository.
override suspend fun doWork(): Result {
   val database = getDatabase(applicationContext)
   val repository = VideosRepository(database)

   return Result.success()
}
  1. Na classe RefreshDataWorker, dentro de doWork(), acima da instrução return, chame o método refreshVideos() dentro um bloco try. Adicione um registro para rastrear quando o trabalhador é executado.
try {
   repository.refreshVideos( )
   Timber.d("Work request for sync is run")
   } catch (e: HttpException) {
   return Result.retry()
}

Para resolver o erro de "Referência não resolvida", importe retrofit2.HttpException.

  1. Aqui está a classe RefreshDataWorker completa para sua referência:
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
       CoroutineWorker(appContext, params) {

   override suspend fun doWork(): Result {
       val database = getDatabase(applicationContext)
       val repository = VideosRepository(database)
       try {
           repository.refreshVideos()
       } catch (e: HttpException) {
           return Result.retry()
       }
       return Result.success()
   }
}

Um Worker define uma unidade de trabalho e o WorkRequest define como e quando o trabalho deve ser executado. Existem duas implementações concretas da classe WorkRequest:

As tarefas podem ser únicas ou periódicas, então escolha a classe de acordo. Para obter mais informações sobre como agendar trabalho recorrente, consulte a documentação de trabalho recorrente.

Nesta tarefa, você define e programa um WorkRequest para executar o trabalhador que você criou na tarefa anterior.

Etapa 1: Configure o trabalho recorrente

Em um aplicativo Android, a classe Application é a classe base que contém todos os outros componentes, como atividades e serviços. Quando o processo para seu aplicativo ou pacote é criado, a classe Application (ou qualquer subclasse de Application) é instanciada antes de qualquer outra classe.

Neste aplicativo de amostra, a classe DevByteApplication é uma subclasse da classe Application. A classe DevByteApplication é um bom lugar para agendar o WorkManager.

  1. Na classe DevByteApplication, crie um método chamado setupRecurringWork() para configurar o trabalho recorrente em segundo plano.
private fun setupRecurringWork() {
}
  1. Dentro do método setupRecurringWork(), crie e inicialize uma solicitação de trabalho periódica para ser executada uma vez por dia, usando o método PeriodicWorkRequestBuilder(). Passe na classe RefreshDataWorker que você criou na tarefa anterior. Passe em um intervalo de repetição de 1 com uma unidade de tempo de TimeUnit.DAYS.
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .build()

Para resolver o erro, importe java.util.concurrent.TimeUnit.

Etapa 2: Agende um WorkRequest com WorkManager

Depois de definir seu WorkRequest, você pode agendá-lo com o WorkManager, usando o método enqueueUniquePeriodicWork(). Este método permite que você adicione um nome exclusivo PeriodicWorkRequest à fila, onde apenas um PeriodicWorkRequest de um determinado nome pode estar ativo por vez.

Por exemplo, você pode querer que apenas uma operação de sincronização esteja ativa. Se uma operação de sincronização estiver pendente, você pode optar por deixá-la ser executada ou substituí-la por seu novo trabalho, usando uma ExistingPeriodicWorkPolicy.

Para aprender mais sobre as maneiras de agendar um WorkRequest, consulte a documentação WorkManager.

  1. Na classe RefreshDataWorker, no início da classe, adicione um objeto complementar. Defina um nome de trabalho para identificar exclusivamente este trabalhador.
companion object {
   const val WORK_NAME = "com.example.android.devbyteviewer.work.RefreshDataWorker"
}
  1. Na classe DevByteApplication, no final do método setupRecurringWork(), programe o trabalho usando o método enqueueUniquePeriodicWork(). Passe o KEEP enum para a ExistingPeriodicWorkPolicy. Passe repeatingRequest como o parâmetro PeriodicWorkRequest.
WorkManager.getInstance().enqueueUniquePeriodicWork(
       RefreshDataWorker.WORK_NAME,
       ExistingPeriodicWorkPolicy.KEEP,
       repeatingRequest)

Se houver trabalho pendente (não concluído) com o mesmo nome, o parâmetro ExistingPeriodicWorkPolicy.KEEP faz com que o WorkManager mantenha o trabalho periódico anterior e descartar a nova solicitação de trabalho.

  1. No início da classe DevByteApplication, crie um objeto CoroutineScope. Passe em Dispatchers.Default como o parâmetro do construtor.
private val applicationScope = CoroutineScope(Dispatchers.Default)
  1. Na classe DevByteApplication, adicione um novo método chamado delayedInit() para iniciar uma corrotina.
private fun delayedInit() {
   applicationScope.launch {
   }
}
  1. Dentro do método delayedInit(), chame setupRecurringWork().
  2. Mova a inicialização do Timber do método onCreate() para o método delayedInit().
private fun delayedInit() {
   applicationScope.launch {
       Timber.plant(Timber.DebugTree())
       setupRecurringWork()
   }
}
  1. Na classe DevByteApplication, no final do método onCreate(), adicione uma chamada ao método delayedInit().
override fun onCreate() {
   super.onCreate()
   delayedInit()
}
  1. Abra o painel Logcat na parte inferior da janela do Android Studio. Filtre em RefreshDataWorker.
  2. Execute o aplicativo. O WorkManager agenda seu trabalho recorrente imediatamente.

    No painel Logcat, observe as instruções de registro que mostram que a solicitação de trabalho está agendada e, em seguida, é executada com êxito.
D/RefreshDataWorker: Work request for sync is run
I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

O registro WM-WorkerWrapper é exibido na biblioteca WorkManager, portanto, você não pode alterar esta mensagem de log.

Etapa 3: (Opcional) programe o WorkRequest para um intervalo mínimo

Nesta etapa, você diminui o intervalo de tempo de 1 dia para 15 minutos. Faça isso para ver os registros de uma solicitação de trabalho periódico em ação.

  1. Na classe DevByteApplication, dentro do método setupRecurringWork(), comente a definição repeatingRequest atual. Adicione uma nova solicitação de trabalho com um intervalo de repetição periódica de 15 minutos.
// val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
//        .build()
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
       .build()
  1. Abra o painel Logcat no Android Studio e filtre RefreshDataWorker. Para limpar os registros anteriores, clique no ícone Clear logcat.
  2. Execute o aplicativo e o WorkManager agenda seu trabalho recorrente imediatamente. No painel Logcat, observe os registros - a solicitação de trabalho é executada uma vez a cada 15 minutos. Aguarde 15 minutos para ver outro conjunto de registros de solicitação de trabalho. Você pode deixar o aplicativo em execução ou fechá-lo; o gerenciador de trabalho ainda deve ser executado.

    Observe que o intervalo às vezes é inferior a 15 minutos e às vezes mais de 15 minutos. (O tempo exato está sujeito às otimizações da bateria do sistema operacional).
12:44:40 D/RefreshDataWorker: Work request for sync is run
12:44:40 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
12:59:24 D/RefreshDataWorker: Work request for sync is run
12:59:24 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:15:03 D/RefreshDataWorker: Work request for sync is run
13:15:03 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:29:22 D/RefreshDataWorker: Work request for sync is run
13:29:22 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:44:26 D/RefreshDataWorker: Work request for sync is run
13:44:26 I/WM-WorkerWrapper: Worker result SUCCESS for Work
 

Parabéns! Você criou um trabalhador e agendou a solicitação de trabalho com WorkManager. Mas há um problema: Você não especificou nenhuma restrição. O WorkManager agendará o trabalho uma vez por dia, mesmo se o dispositivo estiver com pouca bateria, hibernando ou não tiver conexão de rede. Isso afetará a bateria e o desempenho do dispositivo e pode resultar em uma experiência do usuário insatisfatória.

Em sua próxima tarefa, você resolverá esse problema adicionando restrições.

Na tarefa anterior, você usou o WorkManager para agendar uma solicitação de trabalho. Nesta tarefa, você adiciona critérios para quando executar o trabalho.

Ao definir o WorkRequest, você pode especificar restrições para quando o Worker deve ser executado. Por exemplo, você pode especificar que o trabalho deve ser executado apenas quando o dispositivo estiver ocioso ou apenas quando o dispositivo estiver conectado e conectado ao Wi-Fi. Você também pode especificar uma política de retirada para repetir o trabalho. As restrições suportadas são os métodos definidos em Constraints.Builder. Para saber mais, consulte Definindo suas solicitações de trabalho.

Etapa 1: Adicione um objeto Constraints e definir uma restrição

Nesta etapa, você cria um objeto Constraints e define uma restrição no objeto, uma restrição do tipo rede. (É mais fácil notar os registros com apenas uma restrição. Em uma etapa posterior, você adiciona outras restrições).

  1. Na classe DevByteApplication, no início de setupRecurringWork(), defina um val do tipo Constraints. Use o método Constraints.Builder().
val constraints = Constraints.Builder()

Para resolver o erro, importe androidx.work.Constraints.

  1. Use o método setRequiredNetworkType() para adicionar uma restrição de tipo de rede ao objeto constraints. Use o enum UNMETERED para que a solicitação de trabalho somente seja executada quando o dispositivo estiver em uma rede ilimitada.
.setRequiredNetworkType(NetworkType.UNMETERED)
  1. Use o método build() para gerar as restrições do construtor.
val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .build()

Agora você precisa definir o objeto Constraints recém-criado para a solicitação de trabalho.

  1. Na classe DevByteApplication, dentro do método setupRecurringWork(), defina o objeto Constraints para a solicitação de trabalho periódico, repeatingRequest. Para definir as restrições, adicione o método setConstraints() acima da chamada do método build().
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
               .setConstraints(constraints)
               .build()

Etapa 2: Execute o aplicativo e observe os registros

Nesta etapa, você executa o aplicativo e observa a solicitação de trabalho restrita sendo executada em segundo plano em intervalos.

  1. Desinstale o aplicativo do dispositivo ou emulador para cancelar quaisquer tarefas agendadas anteriormente.
  2. Abra o painel Logcat no Android Studio. No painel Logcat, limpe os registros anteriores clicando no ícone Clear logcat à esquerda. Filtre em work.
  3. Desligue o Wi-Fi no dispositivo ou emulador, para que você possa ver como as restrições funcionam. O código atual define apenas uma restrição, indicando que a solicitação deve ser executada apenas em uma rede sem medição. Como o Wi-Fi está desligado, o dispositivo não está conectado à rede, medido ou não medido. Portanto, essa restrição não será atendida.
  4. Execute o aplicativo e observe o painel Logcat. O WorkManager agenda a tarefa em segundo plano imediatamente. Como a restrição da rede não é atendida, a tarefa não é executada.
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
  1. Ligue o Wi-Fi no dispositivo ou emulador e observe o painel Logcat. Agora, a tarefa em segundo plano agendada é executada aproximadamente a cada 15 minutos, desde que a restrição de rede seja atendida.
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
11:31:47 D/RefreshDataWorker: Work request for sync is run
11:31:47 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]
11:46:45 D/RefreshDataWorker: Work request for sync is run
11:46:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:03:05 D/RefreshDataWorker: Work request for sync is run
12:03:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:16:45 D/RefreshDataWorker: Work request for sync is run
12:16:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:31:45 D/RefreshDataWorker: Work request for sync is run
12:31:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:47:05 D/RefreshDataWorker: Work request for sync is run
12:47:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
13:01:45 D/RefreshDataWorker: Work request for sync is run
13:01:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

Etapa 3: Adicione mais restrições

Nesta etapa, você adiciona as seguintes restrições ao PeriodicWorkRequest:

Implemente o seguinte na classe DevByteApplication.

  1. Na classe DevByteApplication, dentro do método setupRecurringWork(), indique que a solicitação de trabalho deve ser executada apenas se a bateria não estiver fraca. Adicione a restrição antes da chamada do método build() e use o método setRequiresBatteryNotLow().
.setRequiresBatteryNotLow(true)
  1. Atualize a solicitação de trabalho para que seja executada apenas quando o dispositivo estiver carregando. Adicione a restrição antes da chamada do método build() e use o método setRequiresCharging().
.setRequiresCharging(true)
  1. Atualize a solicitação de trabalho para que seja executada apenas quando o dispositivo estiver ocioso. Adicione a restrição antes da chamada do método build() e use o método setRequiresDeviceIdle(). Essa restrição executa a solicitação de trabalho apenas quando o usuário não está usando ativamente o dispositivo. Este recurso está disponível apenas no Android 6.0 (Marshmallow) e superior, portanto, adicione uma condição para a versão do SDK M e superior.
.apply {
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
       setRequiresDeviceIdle(true)
   }
}

Aqui está a definição completa do objeto constraints.

val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .setRequiresBatteryNotLow(true)
       .setRequiresCharging(true)
       .apply {
           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
               setRequiresDeviceIdle(true)
           }
       }
       .build()
  1. Dentro do método setupRecurringWork(), altere o intervalo de solicitação de volta para uma vez por dia.
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .setConstraints(constraints)
       .build()

Aqui está a implementação completa do método setupRecurringWork(), com um registro para que você possa rastrear quando a solicitação de trabalho periódico é agendada.

private fun setupRecurringWork() {

       val constraints = Constraints.Builder()
               .setRequiredNetworkType(NetworkType.UNMETERED)
               .setRequiresBatteryNotLow(true)
               .setRequiresCharging(true)
               .apply {
                   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                       setRequiresDeviceIdle(true)
                   }
               }
               .build()
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
               .setConstraints(constraints)
               .build()
       
       Timber.d("Periodic Work request for sync is scheduled")
       WorkManager.getInstance().enqueueUniquePeriodicWork(
               RefreshDataWorker.WORK_NAME,
               ExistingPeriodicWorkPolicy.KEEP,
               repeatingRequest)
   }
  1. Para remover a solicitação de trabalho agendada anteriormente, desinstale o aplicativo DevBytes do seu dispositivo ou emulador.
  2. Execute o aplicativo e o WorkManager agenda imediatamente a solicitação de trabalho. A solicitação de trabalho é executada uma vez por dia, quando todas as restrições são atendidas.
  3. Esta solicitação de trabalho será executada em segundo plano enquanto o aplicativo estiver instalado, mesmo se o aplicativo não estiver em execução. Por esse motivo, você deve desinstalar o aplicativo do telefone.

Bom trabalho! Você implementou e programou uma solicitação de trabalho compatível com a bateria para a pré-busca diária de vídeos no aplicativo DevBytes. O WorkManager agendará e executará o trabalho, otimizando os recursos do sistema. Seus usuários e suas baterias ficarão muito felizes.

Projeto Android Studio: DevBytesWorkManager.

Documentação para desenvolvimento em Android:

Esta seção lista as possíveis tarefas de casa para os alunos que estão trabalhando neste tutorial como parte de um curso ministrado por um instrutor.

Pergunta 1

Quais são as implementações concretas da classe WorkRequest?

OneTimeWorkPeriodicRequest

OneTimeWorkRequest e PeriodicWorkRequest

OneTimeWorkRequest e RecurringWorkRequest

OneTimeOffWorkRequest e RecurringWorkRequest

Pergunta 2

Qual das seguintes classes o WorkManager usa para agendar a tarefa em segundo plano na API 23 e superior?

▢ Apenas JobScheduler

BroadcastReceiver e AlarmManager

AlarmManager e JobScheduler

Scheduler e BroadcastReceiver

Pergunta 3

Qual API você usa para adicionar restrições a um WorkRequest?

setConstraints()

addConstraints()

setConstraint()

addConstraintsToWorkRequest()

Para obter enlaces para outros tutoriais neste curso, consulte a página de destino dos tutoriais Fundamentos de Android em Kotlin.