1. Antes de começar
Este codelab abrange o WorkManager, uma biblioteca compatível com versões anteriores, flexível e simples para trabalhos em segundo plano adiáveis. O WorkManager
é o programador de tarefas recomendado para Android para trabalhos adiáveis, com garantia de execução.
Pré-requisitos
- Conhecimento sobre StateFlow e ViewModel. Se você ainda não conhece essas classes, confira o codelab ViewModel e estado no Compose (especificamente para ViewModel e estados) ou Ler e atualizar dados com o Room (especificamente para fluxos e StateFlow).
- Conhecimento sobre repositórios e injeção de dependências. Para relembrar, confira Adicionar repositório e DI manual.
- Saber implementar corrotinas no app.
O que você vai aprender
- Como adicionar o WorkManager ao projeto.
- Como agendar uma tarefa simples.
- Como configurar parâmetros de entrada e saída para workers.
- Como encadear workers.
O que você vai fazer
- Modificar um app inicial para usar o WorkManager.
- Implementar uma solicitação de trabalho para desfocar uma imagem.
- Implementar um grupo em série de trabalhos encadeados.
- Transmitir dados para dentro e para fora do trabalho que está sendo agendado.
O que é necessário
- A versão estável mais recente do Android Studio.
- Uma conexão com a Internet.
2. Visão geral do app
Atualmente, os smartphones são muito bons para tirar fotos. Tirar fotos desfocadas de algo misterioso é coisa do passado.
Neste codelab, você vai trabalhar com o Blur-O-Matic, um app que desfoca fotos e salva os resultados em um arquivo. Aquilo era o monstro do Lago Ness ou um submarino de brinquedo (link em inglês)? Com o Blur-O-Matic, ninguém vai saber!
A tela tem botões de opção em que você pode selecionar o nível de desfoque da imagem. Clicar no botão Start (começar) desfoca e salva a imagem.
No momento, o app não aplica desfoque nem salva a imagem final.
O foco deste codelab é adicionar o WorkManager ao app, criar workers para limpar arquivos temporários criados para desfocar uma imagem, desfocar a imagem e salvar uma cópia final dela, que pode ser visualizada ao clicar no botão See File (mostrar arquivo). Você também vai aprender a monitorar o status do trabalho em segundo plano e a atualizar a interface do app.
3. Conhecer o app inicial Blur-O-Matic
Acessar o código inicial
Para começar, faça o download do código inicial:
Outra opção é clonar o repositório do GitHub:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git $ cd basic-android-kotlin-compose-training-workmanager $ git checkout starter
Procure o código do app Blur-o-matic neste repositório do GitHub (em inglês).
Executar o código inicial
Para se familiarizar com o código inicial, siga estas etapas:
- Abra o projeto com o código inicial no Android Studio.
- Execute o app em um dispositivo Android ou em um emulador.
A tela tem botões de opção que permitem selecionar o nível de desfoque da imagem. Quando você clica no botão Start, o app desfoca e salva a imagem.
No momento, o app não aplica desfoque quando você clica no botão Start.
Tutorial do código inicial
Nesta tarefa, você vai conhecer a estrutura do projeto. Confira abaixo explicações sobre os arquivos e as pastas mais importantes desse projeto.
WorkerUtils
: métodos práticos que vão ser usados mais tarde para mostrarNotifications
e o código para salvar um bitmap no arquivo.BlurViewModel
: esse modelo de visualização armazena o estado do app e interage com o repositório.WorkManagerBluromaticRepository
: a classe em que você inicia o trabalho em segundo plano com o WorkManager.Constants
: uma classe estática com algumas constantes usadas durante o codelab.BluromaticScreen
: contém funções combináveis para a interface e interage com oBlurViewModel
. As funções combináveis mostram a imagem e incluem botões de opção para selecionar o nível de desfoque desejado.
4. O que é o WorkManager?
O WorkManager faz parte do Android Jetpack e de um componente de arquitetura para trabalho em segundo plano que requer uma combinação de execução oportunista e garantida. Execução oportunista significa que o WorkManager faz o trabalho em segundo plano o quanto antes. Já a execução garantida se refere à lógica para iniciar o trabalho em diversas situações, mesmo se você sair do app.
O WorkManager é uma biblioteca incrivelmente flexível, que oferece diversos outros benefícios. Alguns desses benefícios incluem:
- Suporte a tarefas únicas e periódicas assíncronas.
- Suporte a restrições, por exemplo, condições de rede, espaço de armazenamento e status de carregamento.
- Encadeamento de solicitações de trabalho complexas, por exemplo, a execução de trabalhos em paralelo.
- Usar a saída de uma solicitação de trabalho como entrada para a próxima.
- Suporte a níveis anteriores da API até o 14 (confira a observação).
- Trabalhar com ou sem o Google Play Services.
- Segue as práticas recomendadas de integridade do sistema.
- Oferece suporte à exibição facilitada do estado das solicitações de trabalho na interface do app.
5. Quando usar o WorkManager?
O WorkManager é uma boa opção para tarefas que precisam ser concluídas. A execução dessas tarefas não depende que o app continue em execução depois que o trabalho é colocado na fila. As tarefas são executadas mesmo se o app for fechado ou se o usuário retornar à tela inicial.
Alguns exemplos de tarefas que fazem um bom uso do WorkManager:
- Consultar periodicamente as últimas notícias.
- Aplicar filtros a uma imagem e salvá-la.
- Sincronização periódica de dados locais com a rede.
O WorkManager é uma opção para executar uma tarefa fora da linha de execução principal, mas não é abrangente o suficiente para executar todos os tipos de tarefa fora da linha de execução principal. Corrotinas são uma outra opção discutida nos codelabs anteriores.
Para saber mais sobre quando usar o WorkManager, confira o Guia para o processamento em segundo plano.
6. Adicionar o WorkManager ao app
O WorkManager
requer a dependência do Gradle mostrada abaixo. Ela já está incluída no arquivo de build.
app/build.gradle.kts
dependencies {
// WorkManager dependency
implementation("androidx.work:work-runtime-ktx:2.8.1")
}
É necessário usar a versão mais recente da versão estável do work-runtime-ktx
no app.
Se você mudar a versão, clique em Sync Now para sincronizar o projeto com os arquivos atualizados do Gradle.
7. Noções básicas do WorkManager
Há algumas classes do WorkManager que você precisa conhecer:
Worker
/CoroutineWorker
: Worker é uma classe que executa o trabalho de forma síncrona em uma linha de execução em segundo plano. Como temos interesse no trabalho assíncrono, podemos usar a classe CoroutineWorker, que tem interoperabilidade com as corrotinas do Kotlin. Neste app, você estende a classe CoroutineWorker e substitui o métododoWork()
, em que você coloca o código do trabalho real que quer realizar em segundo plano.WorkRequest
: representa uma solicitação para realizar algum trabalho. UmaWorkRequest
é o local onde você define se o worker precisa ser executado uma vez ou periodicamente. Também é possível impor restrições naWorkRequest
que exigem determinadas condições antes da execução do trabalho. Por exemplo, que o dispositivo esteja carregando antes de iniciar o trabalho solicitado. Você transmite oCoroutineWorker
como parte da criação daWorkRequest
.WorkManager
: essa classe agenda e executa aWorkRequest
. Ela programa umaWorkRequest
de modo a distribuir a carga nos recursos do sistema, respeitando as restrições especificadas.
No seu caso, você define uma nova classe BlurWorker
, que contém o código para desfocar uma imagem. Quando você clica no botão Start, o WorkManager cria e depois enfileira um objeto WorkRequest
.
8. Criar o BlurWorker
Nesta etapa, você vai usar uma imagem na pasta res/drawable
chamada android_cupcake.png
e executar algumas funções nela em segundo plano. Essas funções desfocam a imagem.
- Clique com o botão direito do mouse no pacote
com.example.bluromatic.workers
no painel do projeto Android e selecione New -> Kotlin Class/File. - Dê o nome
BlurWorker
à nova classe Kotlin. Estenda-a doCoroutineWorker
com os parâmetros obrigatórios do construtor.
workers/BlurWorker.kt
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import android.content.Context
class BlurWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
}
A classe do BlurWorker
estende a classe do CoroutineWorker
, em vez da classe mais geral Worker
. A implementação da classe CoroutineWorker
do doWork()
é uma função de suspensão, que permite executar um código assíncrono, o que não pode ser realizado por um Worker
. Como detalhado no guia Linhas de execução no WorkManager, "CoroutineWorker é a implementação recomendada para usuários do Kotlin".
Nesse ponto, o Android Studio mostra uma linha ondulada vermelha em class BlurWorker
, que indica um erro.
Se você colocar o cursor sobre o texto class BlurWorker
, o ambiente de desenvolvimento integrado vai mostrar um pop-up com mais informações sobre o erro.
A mensagem de erro indica que você não substituiu o método doWork()
conforme necessário.
No método doWork()
, crie o código para desfocar a imagem mostrada do cupcake.
Siga estas etapas para corrigir o erro e implementar o método doWork()
:
- Posicione o cursor dentro do código da classe, clicando no texto "BlurWorker".
- No menu do Android Studio, selecione Code > Override Methods...
- No pop-up Override Members, selecione
doWork()
. - Clique em OK.
- Imediatamente antes da declaração de classe, crie uma variável com o nome
TAG
e atribua a ela o valorBlurWorker
. Essa variável não está relacionada especificamente ao métododoWork()
, mas é usada mais tarde em chamadas paraLog()
.
workers/BlurWorker.kt
private const val TAG = "BlurWorker"
class BlurWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
...
- Para ver melhor quando o trabalho é executado, você precisa usar a função
makeStatusNotification()
doWorkerUtil
. Essa função permite que você mostre facilmente um banner de notificação na parte de cima da tela.
No método doWork()
, use a função makeStatusNotification()
para exibir uma notificação de status e informar ao usuário que o worker de desfoque foi iniciado e está desfocando a imagem.
workers/BlurWorker.kt
import com.example.bluromatic.R
...
override suspend fun doWork(): Result {
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
...
- Adicione um bloco de código
return try...catch
, em que o trabalho real da imagem de desfoque é realizado.
workers/BlurWorker.kt
...
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
return try {
} catch (throwable: Throwable) {
}
...
- No bloco
try
, adicione uma chamada paraResult.success()
. - No bloco
catch
, adicione uma chamada paraResult.failure()
.
workers/BlurWorker.kt
...
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
return try {
Result.success()
} catch (throwable: Throwable) {
Result.failure()
}
...
- No bloco
try
, crie uma nova variável chamadapicture
e a preencha com o bitmap retornado da chamada do métodoBitmapFactory.decodeResource
()
e da transmissão do pacote de recursos do aplicativo e o ID do recurso da imagem do cupcake.
workers/BlurWorker.kt
...
return try {
val picture = BitmapFactory.decodeResource(
applicationContext.resources,
R.drawable.android_cupcake
)
Result.success()
...
- Desfoque o bitmap chamando a função
blurBitmap()
e transmita a variávelpicture
e um valor de1
(um) para o parâmetroblurLevel
. - Salve o resultado em uma nova variável com o nome
output
.
workers/BlurWorker.kt
...
val picture = BitmapFactory.decodeResource(
applicationContext.resources,
R.drawable.android_cupcake
)
val output = blurBitmap(picture, 1)
Result.success()
...
- Crie uma nova variável
outputUri
e a preencha com uma chamada para a funçãowriteBitmapToFile()
. - Na chamada para
writeBitmapToFile()
, transmita o contexto do aplicativo e a variáveloutput
como argumentos.
workers/BlurWorker.kt
...
val output = blurBitmap(picture, 1)
// Write bitmap to a temp file
val outputUri = writeBitmapToFile(applicationContext, output)
Result.success()
...
- Adicione o código para mostrar uma mensagem de notificação ao usuário com a variável
outputUri
.
workers/BlurWorker.kt
...
val outputUri = writeBitmapToFile(applicationContext, output)
makeStatusNotification(
"Output is $outputUri",
applicationContext
)
Result.success()
...
- No bloco
catch
, registre uma mensagem indicando que ocorreu um erro ao tentar desfocar a imagem. A chamada paraLog.e()
transmite a variávelTAG
definida anteriormente, uma mensagem adequada e a exceção que está sendo gerada.
workers/BlurWorker.kt
...
} catch (throwable: Throwable) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_applying_blur),
throwable
)
Result.failure()
}
...
Por padrão, um CoroutineWorker,
é executado como Dispatchers.Default
, mas pode ser mudado chamando withContext()
e transmitindo o agente desejado.
- Crie um bloco
withContext()
. - Na chamada para
withContext()
, transmitaDispatchers.IO
para que a função lambda seja executada em um pool de linhas de execução especial para possivelmente bloquear operações de E/S. - Mova o código
return try...catch
criado anteriormente para esse bloco.
...
return withContext(Dispatchers.IO) {
return try {
// ...
} catch (throwable: Throwable) {
// ...
}
}
...
O Android Studio mostra o erro abaixo porque não é possível chamar return
em uma função lambda.
Para corrigir esse erro, adicione um rótulo, conforme mostrado no pop-up.
...
//return try {
return@withContext try {
...
Como esse worker é executado muito rapidamente, é recomendável adicionar um atraso no código para emular um trabalho mais lento.
- No lambda
withContext()
, adicione uma chamada para a função utilitáriadelay()
e transmita a constanteDELAY_TIME_MILLIS
. Essa chamada é apenas para que o codelab forneça um atraso entre as mensagens de notificação.
import com.example.bluromatic.DELAY_TIME_MILLIS
import kotlinx.coroutines.delay
...
return withContext(Dispatchers.IO) {
// This is an utility function added to emulate slower work.
delay(DELAY_TIME_MILLIS)
val picture = BitmapFactory.decodeResource(
...
9. Atualizar o WorkManagerBluromaticRepository
O repositório processa todas as interações com o WorkManager. Essa estrutura está de acordo com o princípio de design de separação de conceitos e é um padrão recomendado de arquitetura do Android.
- No arquivo
data/WorkManagerBluromaticRepository.kt
, dentro da classeWorkManagerBluromaticRepository
, crie uma variável particular com o nomeworkManager
e armazene uma instância deWorkManager
nela chamandoWorkManager.getInstance(context)
.
data/WorkManagerBluromaticRepository.kt
import androidx.work.WorkManager
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {
// New code
private val workManager = WorkManager.getInstance(context)
...
Criar e enfileirar a WorkRequest no WorkManager
Muito bem. Agora vamos fazer uma WorkRequest
e pedir para o WorkManager executá-la. Há dois tipos de WorkRequest
s:
OneTimeWorkRequest
: umaWorkRequest
que é executada apenas uma vez.PeriodicWorkRequest
: umaWorkRequest
que é executada repetidas vezes em um ciclo.
É importante que a imagem seja desfocada uma vez, quando o usuário clicar no botão Start.
Esse trabalho ocorre no método applyBlur()
, que é chamado quando você clica no botão Start.
As etapas abaixo são concluídas no método applyBlur()
.
- Para preencher uma nova variável com o nome
blurBuilder
, crie umaOneTimeWorkRequest
para o worker de desfoque e chame a função de extensãoOneTimeWorkRequestBuilder
do WorkManager KTX.
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
}
- Inicie o trabalho chamando o método
enqueue()
no objetoworkManager
.
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
// Start the work
workManager.enqueue(blurBuilder.build())
}
- Execute o app e confira a notificação ao clicar no botão Start.
No momento, a imagem é desfocada no mesmo nível, independente da opção selecionada. Nas próximas etapas, o nível de desfoque vai mudar com base na opção selecionada.
Para confirmar se a imagem está sendo desfocada corretamente, abra o Device Explorer no Android Studio:
Depois navegue até data > data > com.example.bluromatic > files > blur_filter_outputs > <URI> e confirme se a imagem do cupcake está sendo desfocada:
10. Dados de entrada e saída
Desfocar o recurso de imagem no diretório de recursos funciona, mas, para que o Blur-O-Matic seja revolucionário como está destinado a ser, você precisa permitir que o usuário desfoque a imagem exibida na tela e depois mostre o resultado desfocado.
Para fazer isso, fornecemos o URI da imagem mostrada do cupcake como entrada para a WorkRequest
e usamos a saída da WorkRequest
para exibir a imagem desfocada final.
A entrada e a saída são transmitidas para um worker por objetos Data
. Objetos Data
são contêineres leves para pares de chave-valor. Eles armazenam uma pequena quantidade de dados que podem entrar e sair de um worker pela WorkRequest
.
Na próxima etapa, você vai transmitir o URI para o BlurWorker
criando um objeto de dados de entrada.
Criar um objeto de dados de entrada
- No arquivo
data/WorkManagerBluromaticRepository.kt
, dentro da classeWorkManagerBluromaticRepository
, crie uma nova variável particular com o nomeimageUri
. - Preencha a variável com o URI da imagem, chamando o método de contexto
getImageUri()
.
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.getImageUri
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {
private var imageUri: Uri = context.getImageUri() // <- Add this
private val workManager = WorkManager.getInstance(context)
...
O código do app contém a função auxiliar createInputDataForWorkRequest()
para criar objetos de dados de entrada.
data/WorkManagerBluromaticRepository.kt
// For reference - already exists in the app
private fun createInputDataForWorkRequest(blurLevel: Int, imageUri: Uri): Data {
val builder = Data.Builder()
builder.putString(KEY_IMAGE_URI, imageUri.toString()).putInt(BLUR_LEVEL, blurLevel)
return builder.build()
}
Primeiro, a função auxiliar cria um objeto Data.Builder
. Em seguida, coloca imageUri
e blurLevel
nelas como pares de chave-valor. Um objeto de dados é criado e retornado quando chama return builder.build()
.
- Para definir o objeto de dados de entrada para a WorkRequest, chame o método
blurBuilder.setInputData()
. É possível criar e transmitir o objeto de dados em uma etapa chamando a função auxiliarcreateInputDataForWorkRequest()
como argumento. Para chamar a funçãocreateInputDataForWorkRequest()
, transmita as variáveisblurLevel
eimageUri
.
data/WorkManagerBluromaticRepository.kt
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
// New code for input data object
blurBuilder.setInputData(createInputDataForWorkRequest(blurLevel, imageUri))
workManager.enqueue(blurBuilder.build())
}
Acessar o objeto de dados de entrada
Agora, vamos atualizar o método doWork()
na classe BlurWorker
para acessar o URI e o nível de desfoque transmitido pelo objeto de dados de entrada. Se um valor de blurLevel
não tiver sido fornecido, o padrão vai ser 1
.
No método doWork()
:
- Crie uma nova variável com o nome
resourceUri
e a preencha chamandoinputData.getString()
e transmitindo a constanteKEY_IMAGE_URI
usada como chave ao criar o objeto de dados de entrada.
val resourceUri = inputData.getString(KEY_IMAGE_URI)
- Crie uma nova variável com o nome
blurLevel
. Preencha a variável chamandoinputData.getInt()
e transmitindo a constanteBLUR_LEVEL
que foi usada como chave ao criar o objeto de dados de entrada. Caso esse par de chave-valor não tenha sido criado, forneça um valor padrão de1
(um).
workers/BlurWorker.kt
import com.example.bluromatic.KEY_BLUR_LEVEL
import com.example.bluromatic.KEY_IMAGE_URI
...
override fun doWork(): Result {
// ADD THESE LINES
val resourceUri = inputData.getString(KEY_IMAGE_URI)
val blurLevel = inputData.getInt(KEY_BLUR_LEVEL, 1)
// ... rest of doWork()
}
Com o URI, agora vamos desfocar a imagem do cupcake na tela.
- Verifique se a variável
resourceUri
está preenchida. Se não estiver, uma exceção será gerada. O código abaixo usa a instruçãorequire()
(link em inglês), que gera umaIllegalArgumentException
quando o primeiro argumento é falso.
workers/BlurWorker.kt
return@withContext try {
// NEW code
require(!resourceUri.isNullOrBlank()) {
val errorMessage =
applicationContext.resources.getString(R.string.invalid_input_uri)
Log.e(TAG, errorMessage)
errorMessage
}
Como a origem da imagem é transmitida como um URI, precisamos de um objeto ContentResolver para ler o conteúdo apontado pelo URI.
- Adicione um objeto
contentResolver
ao valorapplicationContext
.
workers/BlurWorker.kt
...
require(!resourceUri.isNullOrBlank()) {
// ...
}
val resolver = applicationContext.contentResolver
...
- Como a origem da imagem agora é a transmitida em URI, use
BitmapFactory.decodeStream()
em vez deBitmapFactory.decodeResource()
para criar o objeto Bitmap.
workers/BlurWorker.kt
import android.net.Uri
...
// val picture = BitmapFactory.decodeResource(
// applicationContext.resources,
// R.drawable.android_cupcake
// )
val resolver = applicationContext.contentResolver
val picture = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri))
)
- Transmita a variável
blurLevel
na chamada da funçãoblurBitmap()
.
workers/BlurWorker.kt
//val output = blurBitmap(picture, 1)
val output = blurBitmap(picture, blurLevel)
Criar o objeto de dados de saída
Você terminou esse worker e pode retornar o URI de saída como um objeto de dados de saída em Result.success()
. Fornecer o URI de saída como um objeto de dados de saída facilita o acesso a outros workers para mais operações. Essa abordagem vai ser útil na próxima seção durante a criação de uma cadeia de workers.
Para isso, siga as etapas abaixo:
- Antes do código
Result.success()
, crie uma nova variável com o nomeoutputData
. - Para preencher essa variável, chame a função
workDataOf()
e use a constanteKEY_IMAGE_URI
para a chave e a variáveloutputUri
como o valor. A funçãoworkDataOf()
cria um objeto de dados do par de chave-valor transmitido.
workers/BlurWorker.kt
import androidx.work.workDataOf
// ...
val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())
- Atualize o código
Result.success()
para usar esse novo objeto de dados como argumento.
workers/BlurWorker.kt
//Result.success()
Result.success(outputData)
- Remova o código que mostra a notificação, já que ela não é mais necessária, porque o objeto de dados de saída agora usa o URI.
workers/BlurWorker.kt
// REMOVE the following notification code
//makeStatusNotification(
// "Output is $outputUri",
// applicationContext
//)
Executar seu app
Nesse ponto, ao executar o app, você espera que ele seja compilado. A imagem desfocada aparece no Device Explorer, mas ainda não é mostrada na tela.
Talvez seja necessário usar a função Synchronize para mostrar as imagens:
Bom trabalho! Você desfocou uma imagem de entrada usando o WorkManager
.
11. Encadear seu trabalho
No momento, você está realizando uma única tarefa: desfocar a imagem. Essa tarefa é uma ótima etapa inicial, mas o app ainda não tem algumas funcionalidades importantes:
- O app não limpa arquivos temporários.
- O app não salva a imagem em um arquivo permanente.
- O app sempre desfoca a imagem no mesmo nível.
Você pode usar uma cadeia de trabalho do WorkManager para adicionar essa funcionalidade. O WorkManager permite que você crie WorkerRequest
s separadas que são executadas em ordem ou em paralelo.
Nesta seção, você vai criar uma cadeia de trabalho parecida com esta:
As caixas representam as WorkRequest
s.
Outro recurso de encadeamento é a capacidade de aceitar entrada e produzir saída. A saída de uma WorkRequest
se torna a entrada da próxima WorkRequest
na cadeia.
Você já tem um CoroutineWorker
para desfocar uma imagem, mas também precisa de um CoroutineWorker
para limpar arquivos temporários e um CoroutineWorker
para salvar a imagem de forma permanente.
Criar o CleanupWorker
O CleanupWorker
exclui os arquivos temporários, se houver.
- Clique com o botão direito do mouse no pacote
com.example.bluromatic.workers
no painel do projeto Android e selecione New -> Kotlin Class/File. - Dê o nome
CleanupWorker
à nova classe Kotlin. - Copie o código do CleanupWorker.kt, conforme mostrado no exemplo de código abaixo.
Como a manipulação de arquivos está fora do escopo deste codelab, você pode copiar o código abaixo para o CleanupWorker
.
workers/CleanupWorker.kt
package com.example.bluromatic.workers
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.OUTPUT_PATH
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.io.File
/**
* Cleans up temporary files generated during blurring process
*/
private const val TAG = "CleanupWorker"
class CleanupWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
override suspend fun doWork(): Result {
/** Makes a notification when the work starts and slows down the work so that it's easier
* to see each WorkRequest start, even on emulated devices
*/
makeStatusNotification(
applicationContext.resources.getString(R.string.cleaning_up_files),
applicationContext
)
return withContext(Dispatchers.IO) {
delay(DELAY_TIME_MILLIS)
return@withContext try {
val outputDirectory = File(applicationContext.filesDir, OUTPUT_PATH)
if (outputDirectory.exists()) {
val entries = outputDirectory.listFiles()
if (entries != null) {
for (entry in entries) {
val name = entry.name
if (name.isNotEmpty() && name.endsWith(".png")) {
val deleted = entry.delete()
Log.i(TAG, "Deleted $name - $deleted")
}
}
}
}
Result.success()
} catch (exception: Exception) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_cleaning_file),
exception
)
Result.failure()
}
}
}
}
Criar o SaveImageToFileWorker
A classe SaveImageToFileWorker
salva o arquivo temporário em um permanente.
O SaveImageToFileWorker
recebe a entrada e saída. A entrada é uma String
do URI de imagem temporariamente desfocado, armazenada com a chave KEY_IMAGE_URI
. A saída é uma String
do URI da imagem desfocada salvo, armazenado com a chave KEY_IMAGE_URI
.
- Clique com o botão direito do mouse no pacote
com.example.bluromatic.workers
no painel do projeto Android e selecione New -> Kotlin Class/File. - Dê o nome
SaveImageToFileWorker
à nova classe Kotlin. - Copie o código do SaveImageToFileWorker.kt conforme mostrado no código de exemplo abaixo.
Como a manipulação de arquivos está fora do escopo deste codelab, você pode copiar o código abaixo para o SaveImageToFileWorker
. No código fornecido, observe como os valores resourceUri
e output
são extraídos e armazenados com a chave KEY_IMAGE_URI
. Esse processo é muito semelhante ao código criado anteriormente para os objetos de dados de entrada e saída.
workers/SaveImageToFileWorker.kt
package com.example.bluromatic.workers
import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.KEY_IMAGE_URI
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.Date
/**
* Saves the image to a permanent file
*/
private const val TAG = "SaveImageToFileWorker"
class SaveImageToFileWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
private val title = "Blurred Image"
private val dateFormatter = SimpleDateFormat(
"yyyy.MM.dd 'at' HH:mm:ss z",
Locale.getDefault()
)
override suspend fun doWork(): Result {
// Makes a notification when the work starts and slows down the work so that
// it's easier to see each WorkRequest start, even on emulated devices
makeStatusNotification(
applicationContext.resources.getString(R.string.saving_image),
applicationContext
)
return withContext(Dispatchers.IO) {
delay(DELAY_TIME_MILLIS)
val resolver = applicationContext.contentResolver
return@withContext try {
val resourceUri = inputData.getString(KEY_IMAGE_URI)
val bitmap = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri))
)
val imageUrl = MediaStore.Images.Media.insertImage(
resolver, bitmap, title, dateFormatter.format(Date())
)
if (!imageUrl.isNullOrEmpty()) {
val output = workDataOf(KEY_IMAGE_URI to imageUrl)
Result.success(output)
} else {
Log.e(
TAG,
applicationContext.resources.getString(R.string.writing_to_mediaStore_failed)
)
Result.failure()
}
} catch (exception: Exception) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_saving_image),
exception
)
Result.failure()
}
}
}
}
Criar uma cadeia de trabalho
Atualmente, o código cria e executa apenas uma WorkRequest
.
Nesta etapa, você vai modificar o código para criar e executar uma cadeia de WorkRequests, em vez de apenas uma solicitação de imagem de desfoque.
Na cadeia de WorkRequests, sua primeira solicitação de trabalho é limpar os arquivos temporários.
- Em vez de chamar
OneTimeWorkRequestBuilder
, chameworkManager.beginWith()
.
Chamar o método beginWith()
retorna um objeto WorkContinuation
e cria o ponto de partida para uma cadeia de WorkRequest
s com a primeira solicitação de trabalho.
data/WorkManagerBluromaticRepository.kt
import androidx.work.OneTimeWorkRequest
import com.example.bluromatic.workers.CleanupWorker
// ...
override fun applyBlur(blurLevel: Int) {
// Add WorkRequest to Cleanup temporary images
var continuation = workManager.beginWith(OneTimeWorkRequest.from(CleanupWorker::class.java))
// Add WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
...
É possível adicionar mais solicitações de trabalho a essa cadeia chamando o método then()
e transmitindo um objeto WorkRequest
.
- Remova a chamada
workManager.enqueue(blurBuilder.build())
, que colocava apenas uma solicitação de trabalho na fila. - Adicione a próxima solicitação de trabalho à cadeia, chamando o método
.then()
.
data/WorkManagerBluromaticRepository.kt
...
//workManager.enqueue(blurBuilder.build())
// Add the blur work request to the chain
continuation = continuation.then(blurBuilder.build())
...
- Crie uma solicitação de trabalho para salvar a imagem e a adicionar à cadeia.
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.SaveImageToFileWorker
...
continuation = continuation.then(blurBuilder.build())
// Add WorkRequest to save the image to the filesystem
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
.build()
continuation = continuation.then(save)
...
- Para iniciar o trabalho, chame o método
enqueue()
no objeto de continuação.
data/WorkManagerBluromaticRepository.kt
...
continuation = continuation.then(save)
// Start the work
continuation.enqueue()
...
Esse código produz e executa esta cadeia de WorkRequests: uma WorkRequest
do CleanupWorker
seguida por uma WorkRequest
do BlurWorker
seguida por uma WorkRequest
do SaveImageToFileWorker
.
- Execute o app.
Agora você pode clicar em Start e conferir as notificações quando os diferentes workers forem executados. A imagem desfocada ainda está disponível no Device Explorer. Em uma próxima seção, você vai adicionar um botão extra para que os usuários possam conferir a imagem desfocada no dispositivo.
Nas capturas de tela abaixo, observe que a mensagem de notificação mostra qual worker está em execução.
Observe que a pasta de saída contém várias imagens desfocadas, ou seja, imagens que estão em estágios desfocados intermediários, e a imagem final mostra a imagem com o nível de desfoque selecionado.
Ótimo trabalho! Agora você pode limpar os arquivos temporários, desfocar e salvar uma imagem.
12. Acessar o código da solução
Para fazer o download do código do codelab concluído, use estes comandos:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git $ cd basic-android-kotlin-compose-training-workmanager $ git checkout intermediate
Se preferir, você pode fazer o download do repositório como um arquivo ZIP, descompactar e abrir no Android Studio.
Confira o código da solução deste codelab no GitHub (link em inglês).
13. Conclusão
Parabéns! Você concluiu o app Blu-O-Matic e, no processo, aprendeu a:
- adicionar a WorkManager ao projeto;
- programar uma
OneTimeWorkRequest
; - usar parâmetros de entrada e saída;
- Encadear trabalhos com
WorkRequest
s.
O WorkManager envolve muito mais do que o conteúdo abordado neste codelab, incluindo trabalho repetitivo, uma biblioteca de suporte para testes, solicitações de trabalho paralelas e mesclagem de entradas.
Para saber mais, acesse a documentação Agendar tarefas com o WorkManager.