Existem muitas opções no Android para trabalhos em segundo plano adiáveis. Este codelab abrange a WorkManager, uma biblioteca compatível, flexível e simples para trabalhos em segundo plano adiáveis. A WorkManager é a programadora de tarefas recomendada para usar no Android para trabalhos adiáveis, com garantia de execução.
O que é a WorkManager?
A 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. Na execução oportunista, a WorkManager fará o trabalho em segundo plano o quanto antes. Na execução garantida, ela cuidará da lógica para iniciar o trabalho em diversas situações, mesmo se você sair do app.
A WorkManager é uma biblioteca simples, mas incrivelmente flexível, que oferece diversos outros benefícios. São eles:
- Compatibilidade com tarefas únicas e periódicas assíncronas
- Compatibilidade com restrições, como condições de rede, espaço de armazenamento e status de carregamento
- Encadeamento de solicitações de trabalho complexas, incluindo a execução de trabalhos em paralelo
- Saída de uma solicitação de trabalho usada como entrada para a próxima
- Gerencia a compatibilidade de nível da API de volta até o nível 14. Consulte a observação
- Funciona com ou sem o Google Play Services
- Segue as práticas recomendadas de integridade do sistema
- Compatibilidade do LiveData para exibir o estado da solicitação de trabalho de forma simples na IU
Quando usar a WorkManager?
A biblioteca WorkManager é uma boa opção para tarefas que oferecem uma conclusão útil, mesmo que o usuário saia de uma tela específica do seu app.
Alguns exemplos de tarefas que fazem um bom uso da WorkManager:
- Upload de registros
- Aplicação de filtros a imagens e salvamento da imagem
- Sincronização periódica de dados locais com a rede
A WorkManager oferece execução garantida, mas nem todas as tarefas exigem isso. Por isso, ela não é uma exigência para executar todas as tarefas da linha de execução principal. Para saber mais sobre quando usar a WorkManager, confira o Guia para o processamento em segundo plano.
O que você criará
Atualmente, os smartphones são muito bons para tirar fotos. Os dias em que um fotógrafo conseguia tirar uma foto desfocada de algo misterioso ficaram no passado.
Neste codelab, você trabalhará no Blur-O-Matic, um app que desfoca fotos e imagens e salva o resultado em um arquivo. Aquilo era o monstro do Lago Ness ou um submarino de brinquedo? (em inglês). Com o Blur-O-Matic, ninguém jamais saberá.
Foto de um robalo-riscado de Peggy Greb, Serviço de Pesquisa Agrícola do Departamento Agrícola dos EUA (USDA, na sigla em inglês). |
O que você aprenderá
- Como adicionar a WorkManager ao seu projeto
- Como programar uma tarefa simples
- Parâmetros de entrada e saída
- Como fazer o encadeamento de trabalhos
- Trabalhos únicos
- Como exibir o status de trabalho na IU
- Como cancelar trabalhos
- Restrições de trabalho
Pré-requisitos
- A versão estável mais recente do Android Studio.
- Você também precisa conhecer o
LiveData
e oViewModel
. Caso você ainda não conheça essas classes, confira o codelab Componentes compatíveis com ciclo de vida do Android (especificamente para ViewModel e LiveData) ou o codelab Room com View (uma introdução aos componentes de arquitetura).
Se você não entender algum ponto…
Se você não entender algum ponto deste codelab ou quiser olhar o estado final do código, use o link a seguir:
Ou, se preferir, você pode clonar o codelab concluído da WorkManager no GitHub:
$ git clone -b java https://github.com/googlecodelabs/android-workmanager
Etapa 1: fazer o download do código
Clique no link abaixo para fazer o download de todo o código para este codelab:
Ou, se preferir, clone o codelab de navegação no GitHub:
$ git clone -b start_java https://github.com/googlecodelabs/android-workmanager
Etapa 2: obter uma imagem
Se você está usando um dispositivo no qual já fez o download de imagens ou tirou fotos, podemos começar.
Se está usando um dispositivo novo (como um emulador recém-criado), tire uma foto ou faça o download de uma imagem na Web com ele. Escolha algo misterioso.
Etapa 3: executar o app
Execute o app. As telas a seguir serão exibidas. Conceda as permissões de acesso às fotos na solicitação inicial e, se a imagem for desativada, reabra o app:
Você pode selecionar uma imagem e avançar para a próxima tela, que tem botões de opção para selecionar o nível de desfoque que você quer aplicar na imagem. Quando o botão Go for pressionado, a imagem será desfocada e salva.
Por enquanto, o app não aplica nenhum desfoque.
O código inicial contém o seguinte:
- ****
WorkerUtils
: essa classe contém o código para desfoque e alguns métodos práticos que você usará posteriormente para exibirNotifications
e deixar o app mais lento. - *****
BlurActivity
: a atividade que mostra a imagem e inclui botões de opção para selecionar o nível de desfoque. - *****
BlurViewModel
: esse modelo de visualização armazena todos os dados necessários para exibir aBlurActivity
. Ele também será a classe em que você iniciará o trabalho em segundo plano usando a WorkManager. - ****
Constants
: uma classe estática com algumas constantes que serão usadas durante o codelab. - ****
SelectImageActivity
: a primeira atividade que permite selecionar uma imagem. res/activity_blur.xml
eres/activity_select.xml
: os arquivos de layout de cada atividade.
***** Esses são os únicos arquivos em que você programará códigos.
A WorkManager
requer a dependência do Gradle abaixo. Ela já foi incluída nos arquivos de compilação:
app/build.gradle
dependencies {
// Other dependencies
implementation "androidx.work:work-runtime:$versions.work"
}
Clique aqui para encontrar a versão mais recente disponível de work-runtime
e inclua a versão correta. No momento, a versão mais recente é:
build.gradle
versions.work = "2.3.3"
Se você atualizar sua versão para uma mais recente, use Sync Now para sincronizar seu projeto com os arquivos do Gradle alterados.
Nesta etapa, você usará uma imagem na pasta res/drawable
chamada test.jpg
e executará algumas funções nela em segundo plano. Essas funções desfocam a imagem e a salvam em um arquivo temporário.
Noções básicas da WorkManager
Há algumas classes da WorkManager que você precisa conhecer:
Worker
: é nessa classe que você coloca o código do trabalho que quer realizar em segundo plano. Você ampliará essa classe e substituirá o métododoWork()
.WorkRequest
: representa uma solicitação para realizar algum trabalho. Você transmitirá oWorker
como parte da criação daWorkRequest
. Ao criar aWorkRequest
, você também pode especificar itens comoConstraints
ou quando oWorker
deve ser executado.WorkManager
: essa classe programa suasWorkRequest
e as executa. Ela programaWorkRequest
s de modo a distribuir a carga nos recursos do sistema, respeitando as restrições especificadas.
No seu caso, você definirá um novo BlurWorker
, que conterá o código para desfocar uma imagem. Quando o botão Go for clicado, uma WorkRequest
será criada e colocada na fila pela WorkManager
.
Etapa 1: criar a BlurWorker
No pacote workers
, crie uma nova classe chamada BlurWorker
.
Ela precisa ampliar o Worker
.
Etapa 2: adicionar um construtor
Adicione um construtor à classe BlurWorker
:
public class BlurWorker extends Worker {
public BlurWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
}
Etapa 3: substituir e implementar doWork()
O Worker
desfocará a imagem res/test.jpg
.
Substitua o método doWork()
e implemente o seguinte:
- Acesse um
Context
chamandogetApplicationContext()
. Ele será necessário para várias manipulações de bitmap que você está prestes a fazer. - Crie um
Bitmap
com a imagem de teste:
Bitmap picture = BitmapFactory.decodeResource(
applicationContext.getResources(),
R.drawable.test);
- Acesse uma versão desfocada do bitmap chamando o método estático
blurBitmap
deWorkerUtils
. - Grave esse bitmap em um arquivo temporário chamando o método estático
writeBitmapToFile
deWorkerUtils
. Salve o URI retornado em uma variável local. - Faça uma notificação exibir o URI chamando o método estático
makeStatusNotification
deWorkerUtils
. - Retorne
Result.success();
. - Una o código das etapas 2 a 6 em uma instrução try/catch. Capture um
Throwable
genérico. - Na declaração de captura, emita um erro log statement:
Log.e(TAG, "Error applying blur", throwable);
. - Na declaração de captura, retorne
Result.failure();
.
O código completo desta etapa é mostrado abaixo.
BlurWorker.java
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;
import com.example.background.R;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
public class BlurWorker extends Worker {
public BlurWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
private static final String TAG = BlurWorker.class.getSimpleName();
@NonNull
@Override
public Result doWork() {
Context applicationContext = getApplicationContext();
try {
Bitmap picture = BitmapFactory.decodeResource(
applicationContext.getResources(),
R.drawable.test);
// Blur the bitmap
Bitmap output = WorkerUtils.blurBitmap(picture, applicationContext);
// Write bitmap to a temp file
Uri outputUri = WorkerUtils.writeBitmapToFile(applicationContext, output);
WorkerUtils.makeStatusNotification("Output is "
+ outputUri.toString(), applicationContext);
// If there were no errors, return SUCCESS
return Result.success();
} catch (Throwable throwable) {
// Technically WorkManager will return Result.failure()
// but it's best to be explicit about it.
// Thus if there were errors, we're return FAILURE
Log.e(TAG, "Error applying blur", throwable);
return Result.failure();
}
}
}
Etapa 4: acessar a WorkManager no ViewModel
Crie uma variável para uma instância de WorkManager
no ViewModel
e a instancie no construtor do ViewModel
:
BlurViewModel.java
private WorkManager mWorkManager;
// BlurViewModel constructor
public BlurViewModel(@NonNull Application application) {
super(application);
mWorkManager = WorkManager.getInstance(application);
//...rest of the constructor
}
Etapa 5: colocar uma WorkRequest na fila na WorkManager
Muito bem. Agora vamos fazer uma WorkRequest e pedir para a WorkManager executá-la. Há dois tipos de WorkRequest
s:
OneTimeWorkRequest:
umaWorkRequest
que será executada apenas uma vez.PeriodicWorkRequest:
umaWorkRequest
que será repetida em um ciclo.
Só queremos que a imagem seja desfocada uma vez quando o botão Go for clicado. O método applyBlur
é chamado quando o botão Go é clicado, então crie uma OneTimeWorkRequest
usando BlurWorker
. Em seguida, coloque sua WorkRequest.
na fila usando a instância da WorkManager
.
Adicione a seguinte linha de código ao método applyBlur() do BlurViewModel
:
BlurViewModel.java
void applyBlur(int blurLevel) {
mWorkManager.enqueue(OneTimeWorkRequest.from(BlurWorker.class));
}
Etapa 6: executar o código
Execute o código. Ele será compilado e você verá a notificação quando pressionar o botão Go.
Você também pode abrir o Device File Explorer no Android Studio:
Depois, navegue até data>data>com.example.background>files>blur_filter_outputs><URI> e confirme se o peixe foi desfocado:
Desfocar a imagem de teste é muito bom, mas, para que o Blur-O-Matic seja o app revolucionário de edição de imagens que está destinado a ser, você precisa permitir que os usuários desfoquem as próprias imagens.
Para fazer isso, forneceremos o URI da imagem do usuário selecionada como entrada da nossa WorkRequest
.
Etapa 1: criar um objeto de entrada Data
A entrada e a saída são transmitidas por objetos Data
. Objetos Data
são contêineres leves para pares de chave-valor. O objetivo deles é armazenar uma quantidade pequena de dados que podem ser transmitidos de/para WorkRequest
s.
O URI será transmitido da imagem do usuário para um pacote. Ele será armazenado em uma variável chamada mImageUri
.
Crie um método particular chamado createInputDataForUri
. Esse método vai:
- criar um objeto
Data.Builder
; - se
mImageUri
for umURI
não nulo, adicioná-lo ao objetoData
usando o métodoputString
. Esse método usa uma chave e um valor. Você pode usar a constanteKEY_IMAGE_URI
de string da classeConstants
; - chamar
build()
no objetoData.Builder
para criar o objetoData
e retorná-lo.
Veja abaixo o método createInputDataForUri
completo:
BlurViewModel.java
/**
* Creates the input data bundle which includes the Uri to operate on
* @return Data which contains the Image Uri as a String
*/
private Data createInputDataForUri() {
Data.Builder builder = new Data.Builder();
if (mImageUri != null) {
builder.putString(KEY_IMAGE_URI, mImageUri.toString());
}
return builder.build();
}
Etapa 2: transmitir o objeto Data para a WorkRequest
Mude o método applyBlur
para que ele faça o seguinte:
- Crie um novo
OneTimeWorkRequest.Builder
. - Chame
setInputData
, transmitindo o resultado decreateInputDataForUri
. - Crie a
OneTimeWorkRequest
. - Coloque essa solicitação na fila usando a
WorkManager
.
Veja abaixo o método applyBlur
completo:
BlurViewModel.java
void applyBlur(int blurLevel) {
OneTimeWorkRequest blurRequest =
new OneTimeWorkRequest.Builder(BlurWorker.class)
.setInputData(createInputDataForUri())
.build();
mWorkManager.enqueue(blurRequest);
}
Etapa 3: atualizar o doWork() do BlurWorker para acessar a entrada
Agora, vamos atualizar o método doWork()
do BlurWorker
para acessar o URI transmitido do objeto Data
:
BlurWorker.java
public Result doWork() {
Context applicationContext = getApplicationContext();
// ADD THIS LINE
String resourceUri = getInputData().getString(Constants.KEY_IMAGE_URI);
//... rest of doWork()
}
Essa variável não será usada até que você conclua as próximas etapas.
Etapa 4: desfocar o URI fornecido
Com o URI, é possível desfocar a imagem que o usuário selecionou:
BlurWorker.java
public Worker.Result doWork() {
Context applicationContext = getApplicationContext();
String resourceUri = getInputData().getString(Constants.KEY_IMAGE_URI);
try {
// REPLACE THIS CODE:
// Bitmap picture = BitmapFactory.decodeResource(
// applicationContext.getResources(),
// R.drawable.test);
// WITH
if (TextUtils.isEmpty(resourceUri)) {
Log.e(TAG, "Invalid input uri");
throw new IllegalArgumentException("Invalid input uri");
}
ContentResolver resolver = applicationContext.getContentResolver();
// Create a bitmap
Bitmap picture = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri)));
//...rest of doWork
Etapa 5: URI temporário de saída
Terminamos esse worker e agora podemos retornar Result.success()
. Forneceremos o OutputURI como Data de saída para facilitar o acesso a essa imagem temporária por outros workers para mais operações. Isso será útil no próximo capítulo, quando criarmos uma cadeia de workers. Para fazer isso:
- Crie um novo
Data
, assim como fez com a entrada, e armazeneoutputUri
como umaString
. Use a mesma chave (KEY_IMAGE_URI
). - Transmita isso para o método
Result.success()
doWorker
.
BlurWorker.java
Essa linha precisa seguir a linha WorkerUtils.makeStatusNotification
e substituir Result.success()
em doWork()
:
Data outputData = new Data.Builder()
.putString(KEY_IMAGE_URI, outputUri.toString())
.build();
return Result.success(outputData);
Etapa 6: executar o app
Agora, você executará o app. Ele precisa ser compilado e ter o mesmo comportamento.
Você também pode abrir o Device File Explorer no Android Studio e navegar até data/data/com.example.background/files/blur_filter_outputs/<URI>, como fez na última etapa.
Talvez seja necessário usar a função Synchronize para ver as imagens:
Bom trabalho! Você desfocou uma imagem de entrada usando a WorkManager
.
No momento, você está fazendo uma única tarefa: desfocar a imagem. Esse é um ótimo primeiro passo, mas faltam algumas funções básicas:
- Os arquivos temporários não são limpos.
- A imagem não é salva em um arquivo permanente.
- A imagem sempre é desfocada no mesmo nível.
Usaremos uma cadeia de trabalho da WorkManager para adicionar essas funções.
A WorkManager permite que você crie WorkerRequest
s separadas que são executadas em ordem ou paralelamente. Nesta etapa, você criará uma cadeia de trabalho semelhante a esta:
As WorkRequest
s são representadas como caixas.
Outra característica interessante do encadeamento é que a saída de uma WorkRequest
se torna a entrada da próxima WorkRequest
na cadeia. A entrada e a saída transmitidas entre cada WorkRequest
são mostradas como texto azul.
Etapa 1: criar workers de limpeza e salvamento
Primeiro, defina todas as classes de Worker
necessárias. Você já tem um Worker
para desfocar uma imagem, mas também precisa de um que limpe arquivos temporários e um que salve a imagem permanentemente.
Crie duas novas classes no pacote worker
que ampliem Worker
.
A primeira será chamada de CleanupWorker
e a segunda de SaveImageToFileWorker
.
Etapa 2: adicionar um construtor
Adicione um construtor à classe CleanupWorker
:
public class CleanupWorker extends Worker {
public CleanupWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
}
Etapa 3: substituir e implementar doWork() para CleanerWorker
O CleanupWorker
não precisa receber nenhuma entrada nem transmitir nenhuma saída. Os arquivos temporários, se houver, serão sempre excluídos. Como este não é um codelab sobre manipulação de arquivos, você pode copiar o código para o CleanupWorker
abaixo:
CleanupWorker.java
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.example.background.Constants;
import java.io.File;
public class CleanupWorker extends Worker {
public CleanupWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
private static final String TAG = CleanupWorker.class.getSimpleName();
@NonNull
@Override
public Result doWork() {
Context applicationContext = getApplicationContext();
// 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
WorkerUtils.makeStatusNotification("Cleaning up old temporary files",
applicationContext);
WorkerUtils.sleep();
try {
File outputDirectory = new File(applicationContext.getFilesDir(),
Constants.OUTPUT_PATH);
if (outputDirectory.exists()) {
File[] entries = outputDirectory.listFiles();
if (entries != null && entries.length > 0) {
for (File entry : entries) {
String name = entry.getName();
if (!TextUtils.isEmpty(name) && name.endsWith(".png")) {
boolean deleted = entry.delete();
Log.i(TAG, String.format("Deleted %s - %s",
name, deleted));
}
}
}
}
return Worker.Result.success();
} catch (Exception exception) {
Log.e(TAG, "Error cleaning up", exception);
return Worker.Result.failure();
}
}
}
Etapa 4: substituir e implementar doWork() para SaveImageToFileWorker
O SaveImageToFileWorker
receberá entrada e saída. A entrada é uma String
armazenada com a chave KEY_IMAGE_URI
. A saída também será uma String
armazenada com a chave KEY_IMAGE_URI
.
Como este ainda não é um codelab sobre manipulação de arquivos, o código é apresentado abaixo, com dois TODO
s para você preencher o código adequado para entrada e saída. Ele é muito parecido com o código que você programou na última etapa para entrada e saída, já que usa as mesmas chaves.
SaveImageToFileWorker.java
import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.example.background.Constants;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class SaveImageToFileWorker extends Worker {
public SaveImageToFileWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
}
private static final String TAG = SaveImageToFileWorker.class.getSimpleName();
private static final String TITLE = "Blurred Image";
private static final SimpleDateFormat DATE_FORMATTER =
new SimpleDateFormat("yyyy.MM.dd 'at' HH:mm:ss z", Locale.getDefault());
@NonNull
@Override
public Result doWork() {
Context applicationContext = getApplicationContext();
// 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
WorkerUtils.makeStatusNotification("Saving image", applicationContext);
WorkerUtils.sleep();
ContentResolver resolver = applicationContext.getContentResolver();
try {
String resourceUri = getInputData()
.getString(Constants.KEY_IMAGE_URI);
Bitmap bitmap = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri)));
String outputUri = MediaStore.Images.Media.insertImage(
resolver, bitmap, TITLE, DATE_FORMATTER.format(new Date()));
if (TextUtils.isEmpty(outputUri)) {
Log.e(TAG, "Writing to MediaStore failed");
return Result.failure();
}
Data outputData = new Data.Builder()
.putString(Constants.KEY_IMAGE_URI, outputUri)
.build();
return Result.success(outputData);
} catch (Exception exception) {
Log.e(TAG, "Unable to save image to Gallery", exception);
return Worker.Result.failure();
}
}
}
Etapa 5: modificar a notificação do BlurWorker
Agora que temos uma cadeia de Worker
s para salvar a imagem na pasta correta, podemos modificar a notificação para informar aos usuários quando o trabalho for iniciado e ficar lento para facilitar a visualização do início de cada WorkRequest
, mesmo em dispositivos emulados. A versão final do BlurWorker
fica assim:
BlurWorker.java
@NonNull
@Override
public Worker.Result doWork() {
Context applicationContext = getApplicationContext();
// 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
WorkerUtils.makeStatusNotification("Blurring image", applicationContext);
WorkerUtils.sleep();
String resourceUri = getInputData().getString(KEY_IMAGE_URI);
try {
if (TextUtils.isEmpty(resourceUri)) {
Log.e(TAG, "Invalid input uri");
throw new IllegalArgumentException("Invalid input uri");
}
ContentResolver resolver = applicationContext.getContentResolver();
// Create a bitmap
Bitmap picture = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri)));
// Blur the bitmap
Bitmap output = WorkerUtils.blurBitmap(picture, applicationContext);
// Write bitmap to a temp file
Uri outputUri = WorkerUtils.writeBitmapToFile(applicationContext, output);
Data outputData = new Data.Builder()
.putString(KEY_IMAGE_URI, outputUri.toString())
.build();
// If there were no errors, return SUCCESS
return Result.success(outputData);
} catch (Throwable throwable) {
// Technically WorkManager will return Result.failure()
// but it's best to be explicit about it.
// Thus if there were errors, we're return FAILURE
Log.e(TAG, "Error applying blur", throwable);
return Result.failure();
}
}
Etapa 6: criar uma cadeia de WorkRequest
Você precisa modificar o método applyBlur
do BlurViewModel
para executar uma cadeia de WorkRequest
s em vez de apenas uma delas. Atualmente, o código é assim:
BlurViewModel.java
void applyBlur(int blurLevel) {
OneTimeWorkRequest blurRequest =
new OneTimeWorkRequest.Builder(BlurWorker.class)
.setInputData(createInputDataForUri())
.build();
mWorkManager.enqueue(blurRequest);
}
Em vez de chamar WorkManager.enqueue()
, chame WorkManager.beginWith()
. Isso retorna uma WorkContinuation
, que define uma cadeia de WorkRequest
s. Você pode adicionar itens a essa cadeia de solicitações de trabalho chamando o método then()
. Por exemplo, se tiver três objetos WorkRequest
(workA
, workB
e workC
), você pode fazer o seguinte:
// Example code. Don't copy to the project
WorkContinuation continuation = mWorkManager.beginWith(workA);
continuation.then(workB) // FYI, then() returns a new WorkContinuation instance
.then(workC)
.enqueue(); // Enqueues the WorkContinuation which is a chain of work
Isso produziria e executaria a seguinte cadeia de WorkRequests:
Crie uma cadeia com uma WorkRequest
CleanupWorker
, uma BlurImage
WorkRequest
e uma SaveImageToFile
WorkRequest
em applyBlur
. Transmita a entrada para a BlurImage
WorkRequest
.
O código resultante será o seguinte:
BlurViewModel.java
void applyBlur(int blurLevel) {
// Add WorkRequest to Cleanup temporary images
WorkContinuation continuation =
mWorkManager.beginWith(OneTimeWorkRequest.from(CleanupWorker.class));
// Add WorkRequest to blur the image
OneTimeWorkRequest blurRequest = new OneTimeWorkRequest.Builder(BlurWorker.class)
.setInputData(createInputDataForUri())
.build();
continuation = continuation.then(blurRequest);
// Add WorkRequest to save the image to the filesystem
OneTimeWorkRequest save =
new OneTimeWorkRequest.Builder(SaveImageToFileWorker.class)
.build();
continuation = continuation.then(save);
// Actually start the work
continuation.enqueue();
}
Ele será compilado e executado. A imagem que você escolheu desfocar será salva na pasta Imagens:
Etapa 7: repetir o BlurWorker
Está na hora de adicionar um recurso para desfocar a imagem em níveis diferentes. Pegue o parâmetro blurLevel
transmitido para applyBlur
e adicione essa quantidade de operações WorkRequest
de desfoque à cadeia. Apenas a primeira WorkRequest
precisa receber a entrada do URI.
Faça o teste e compare com o código abaixo:
BlurViewModel.java
void applyBlur(int blurLevel) {
// Add WorkRequest to Cleanup temporary images
WorkContinuation continuation = mWorkManager.beginWith(OneTimeWorkRequest.from(CleanupWorker.class));
// Add WorkRequests to blur the image the number of times requested
for (int i = 0; i < blurLevel; i++) {
OneTimeWorkRequest.Builder blurBuilder =
new OneTimeWorkRequest.Builder(BlurWorker.class);
// Input the Uri if this is the first blur operation
// After the first blur operation the input will be the output of previous
// blur operations.
if ( i == 0 ) {
blurBuilder.setInputData(createInputDataForUri());
}
continuation = continuation.then(blurBuilder.build());
}
// Add WorkRequest to save the image to the filesystem
OneTimeWorkRequest save = new OneTimeWorkRequest.Builder(SaveImageToFileWorker.class)
.build();
continuation = continuation.then(save);
// Actually start the work
continuation.enqueue();
}
Ótimo trabalho! Agora você pode desfocar uma imagem o quanto quiser. Quanto mistério!
Agora que você já usou as cadeias, está na hora de abordar outro recurso poderoso da WorkManager: cadeias de trabalho únicas.
Às vezes, você quer que apenas uma cadeia de trabalho seja executada por vez. Por exemplo, talvez você tenha uma cadeia de trabalho que sincroniza os dados locais com o servidor, então é recomendável deixar a primeira sincronização de dados terminar antes de iniciar uma nova. Para fazer isso, use beginUniqueWork
em vez de beginWith
e forneça um nome de String
exclusivo. Isso nomeia toda a cadeia de solicitações de trabalho para que você possa consultá-las em conjunto.
Use beginUniqueWork
para garantir que a cadeia de trabalho para desfoque do arquivo seja única. Transmita IMAGE_MANIPULATION_WORK_NAME
como a chave. Você também precisa transmitir uma ExistingWorkPolicy
. Suas opções são REPLACE
, KEEP
ou APPEND
.
Você usará REPLACE
porque, se o usuário decidir desfocar outra imagem antes que a atual seja concluída, precisamos interromper o processo atual e começar a desfocar a nova imagem.
O código para iniciar a continuação de trabalho único é mostrado abaixo:
BlurViewModel.java
// REPLACE THIS CODE:
// WorkContinuation continuation =
// mWorkManager.beginWith(OneTimeWorkRequest.from(CleanupWorker.class));
// WITH
WorkContinuation continuation = mWorkManager
.beginUniqueWork(IMAGE_MANIPULATION_WORK_NAME,
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequest.from(CleanupWorker.class));
O Blur-O-Matic agora desfocará apenas uma imagem por vez.
Esta seção usa muito LiveData. Por isso, você precisa conhecê-lo para entender totalmente o que está acontecendo. O LiveData é um armazenador de dados observáveis com reconhecimento de ciclo de vida.
Consulte a documentação ou o codelab Componentes compatíveis com ciclo de vida do Android se esta for a primeira vez que você trabalha com o LiveData ou os observáveis.
A próxima grande mudança que você fará é mudar o que é exibido no app durante a execução do trabalho.
Você pode ver o status de qualquer WorkRequest
acessando um LiveData
que tenha um objeto WorkInfo
. WorkInfo
é um objeto que contém detalhes sobre o estado atual de uma WorkRequest
, incluindo:
- se o trabalho é
BLOCKED
,CANCELLED
,ENQUEUED
,FAILED
,RUNNING
ouSUCCEEDED
; - se a
WorkRequest
for concluída, todos os dados de saída do trabalho.
A tabela a seguir mostra três maneiras diferentes de acessar objetos LiveData<WorkInfo>
ou LiveData<List<WorkInfo>>
e o que cada uma faz.
Tipo | Método da WorkManager | Descrição |
Acessar o trabalho usando um ID |
| Cada |
Acessar o trabalho usando um nome da cadeia única |
| Como você acabou de ver, as |
Acessar o trabalho usando uma tag |
| Por fim, você pode incluir uma tag em qualquer WorkRequest com uma string. Inclua a mesma tag em várias |
Você incluirá uma tag na WorkRequest
SaveImageToFileWorker
para poder acessá-la usando getWorkInfosByTagLiveData
. Você usará uma tag para identificar o trabalho em vez de usar o ID da WorkManager porque, se o usuário desfocar várias imagens, todas as WorkRequest
s de salvamento de imagem terão a mesma tag, mas não o mesmo ID. Também é possível escolher a tag.
Você não usaria getWorkInfosForUniqueWorkLiveData
, porque isso retornaria WorkInfo
para todas as WorkRequest
s de desfoque e a WorkRequest
de limpeza também. Seria necessário usar uma lógica extra para encontrar a WorkRequest
de salvamento de imagem.
Etapa 1: incluir uma tag no trabalho
Em applyBlur
, ao criar o SaveImageToFileWorker
, inclua uma tag no trabalho usando a constante TAG_OUTPUT
de String
:
BlurViewModel.java
OneTimeWorkRequest save = new OneTimeWorkRequest.Builder(SaveImageToFileWorker.class)
.addTag(TAG_OUTPUT) // This adds the tag
.build();
Etapa 2: acessar o WorkInfo
Agora que você incluiu uma tag no trabalho, é possível acessar o WorkInfo
:
- Declare uma nova variável chamada
mSavedWorkInfo
, que é umLiveData<List<WorkInfo>>
. - No construtor
BlurViewModel
, acesseWorkInfo
usandoWorkManager.getWorkInfosByTagLiveData
. - Adicione um getter para
mSavedWorkInfo
.
O código necessário é o seguinte:
BlurViewModel.java
// New instance variable for the WorkInfo class
private LiveData<List<WorkInfo>> mSavedWorkInfo;
// Placed this code in the BlurViewModel constructor
mSavedWorkInfo = mWorkManager.getWorkInfosByTagLiveData(TAG_OUTPUT);
// Add a getter method for mSavedWorkInfo
LiveData<List<WorkInfo>> getOutputWorkInfo() { return mSavedWorkInfo; }
Etapa 3: exibir o WorkInfo
Agora que você tem um LiveData
para seu WorkInfo
, é possível observá-lo na BlurActivity
. No observador:
- Confira se a lista de
WorkInfo
não é nula e se ela tem objetosWorkInfo
. Se não tiver, isso significa que o botão Go ainda não foi clicado, então retorne. - Acesse o primeiro
WorkInfo
na lista. Haverá somente umWorkInfo
marcado comTAG_OUTPUT
porque tornamos a cadeia de trabalho única. - Use
workInfo.getState().isFinished();
para conferir se o trabalho tem um status concluído. - Se não estiver concluído, chame
showWorkInProgress()
, que oculta e mostra as visualizações adequadas. - Se estiver concluído, chame
showWorkFinished()
, que oculta e mostra as visualizações adequadas.
O código fica assim:
BlurActivity.java
// Show work status, added in onCreate()
mViewModel.getOutputWorkInfo().observe(this, listOfWorkInfos -> {
// If there are no matching work info, do nothing
if (listOfWorkInfos == null || listOfWorkInfos.isEmpty()) {
return;
}
// We only care about the first output status.
// Every continuation has only one worker tagged TAG_OUTPUT
WorkInfo workInfo = listOfWorkInfos.get(0);
boolean finished = workInfo.getState().isFinished();
if (!finished) {
showWorkInProgress();
} else {
showWorkFinished();
}
});
Etapa 4: executar o app
Execute o app. Ele será compilado e executado e agora mostrará uma barra de progresso quando estiver funcionando, assim como o botão de cancelamento:
Cada WorkInfo
também tem um método getOutputData
, que permite acessar o objeto Data
de saída com a imagem salva final. Exibiremos um botão com a mensagem See File sempre que uma imagem desfocada estiver pronta para exibição.
Etapa 1: criar mOutputUri
Crie uma variável no BlurViewModel
para o URI final e forneça getters e setters. Para transformar uma String
em um Uri
, use o método uriOrNull
.
Use o código abaixo:
BlurViewModel.java
// New instance variable for the WorkInfo
private Uri mOutputUri;
// Add a getter and setter for mOutputUri
void setOutputUri(String outputImageUri) {
mOutputUri = uriOrNull(outputImageUri);
}
Uri getOutputUri() { return mOutputUri; }
Etapa 2: criar o botão "See File"
Já existe um botão no layout activity_blur.xml
que está oculto. Ele está na BlurActivity
e pode ser acessado pela vinculação de visualizações como seeFileButton
.
Configure o listener de clique para esse botão. Ele precisa acessar o URI e abrir uma atividade para vê-lo. Use o código abaixo:
BlurActivity.java
// Inside onCreate()
binding.seeFileButton.setOnClickListener(view -> {
Uri currentUri = mViewModel.getOutputUri();
if (currentUri != null) {
Intent actionView = new Intent(Intent.ACTION_VIEW, currentUri);
if (actionView.resolveActivity(getPackageManager()) != null) {
startActivity(actionView);
}
}
});
Etapa 3: definir o URI e mostrar o botão
Há alguns ajustes finais que você precisa aplicar ao observador de WorkInfo
para que isso funcione:
- Se o
WorkInfo
for concluído, acesse os dados de saída usandoworkInfo.getOutputData().
. - Em seguida, acesse o URI de saída. Lembre-se de que ele está armazenado com a chave
Constants.KEY_IMAGE_URI
. - Se o URI não estiver vazio, ele foi salvo corretamente. Mostre o
seeFileButton
e chamesetOutputUri
no modelo de visualização com o URI.
BlurActivity.java
// Replace the observer code we added in previous steps with this one.
// Show work info, goes inside onCreate()
mViewModel.getOutputWorkInfo().observe(this, listOfWorkInfo -> {
// If there are no matching work info, do nothing
if (listOfWorkInfo == null || listOfWorkInfo.isEmpty()) {
return;
}
// We only care about the first output status.
// Every continuation has only one worker tagged TAG_OUTPUT
WorkInfo workInfo = listOfWorkInfo.get(0);
boolean finished = workInfo.getState().isFinished();
if (!finished) {
showWorkInProgress();
} else {
showWorkFinished();
Data outputData = workInfo.getOutputData();
String outputImageUri = outputData.getString(Constants.KEY_IMAGE_URI);
// If there is an output file show "See File" button
if (!TextUtils.isEmpty(outputImageUri)) {
mViewModel.setOutputUri(outputImageUri);
binding.seeFileButton.setVisibility(View.VISIBLE);
}
}
});
Etapa 4: executar o código
Execute o código. Você verá o novo botão clicável See File, que leva ao arquivo gerado:
Você incluiu o botão Cancel Work, então vamos adicionar o código para que ele faça algo. Com a WorkManager, é possível cancelar trabalhos usando o ID, por tag e por nome de cadeia única.
Neste caso, é recomendável cancelar o trabalho por nome de cadeia única, já que você quer cancelar todo o trabalho na cadeia, não apenas uma etapa específica.
Etapa 1: cancelar o trabalho por nome
No modelo de visualização, programe o método para cancelar o trabalho:
BlurViewModel.java
/**
* Cancel work using the work's unique name
*/
void cancelWork() {
mWorkManager.cancelUniqueWork(IMAGE_MANIPULATION_WORK_NAME);
}
Etapa 2: chamar o método de cancelamento
Em seguida, vincule o botão cancelButton
para chamar cancelWork
:
BlurActivity.java
// In onCreate()
// Hookup the Cancel button
binding.cancelButton.setOnClickListener(view -> mViewModel.cancelWork());
Etapa 3: executar e cancelar o trabalho
Execute o app. Ele deve ser compilado sem problemas. Comece a desfocar uma imagem e, em seguida, clique no botão de cancelamento. A cadeia inteira será cancelada.
Por último, mas não menos importante, a WorkManager
é compatível com Constraints
. Para o Blur-O-Matic, você usará a restrição de que o dispositivo precisa estar sendo carregado durante o salvamento.
Etapa 1: criar e adicionar uma restrição de carregamento
Para criar um objeto Constraints
, use um Constraints.Builder
. Em seguida, defina as restrições que você quer e as adicione à WorkRequest
, conforme mostrado abaixo:
BlurViewModel.java
// In the applyBlur method
// Create charging constraint
Constraints constraints = new Constraints.Builder()
.setRequiresCharging(true)
.build();
// Add WorkRequest to save the image to the filesystem
OneTimeWorkRequest save = new OneTimeWorkRequest.Builder(SaveImageToFileWorker.class)
.setConstraints(constraints) // This adds the Constraints
.addTag(TAG_OUTPUT)
.build();
continuation = continuation.then(save);
Etapa 2: testar com o emulador ou o dispositivo
Agora você pode executar o Blur-O-Matic. Se você está usando um dispositivo, pode removê-lo ou conectá-lo à fonte de energia. Em um emulador, você pode mudar o status de carregamento na janela Extended controls:
Quando o dispositivo não estiver carregando, ele precisará ficar no estado de carregamento até que você o conecte a uma fonte de energia.
Parabéns! Você concluiu o app Blu-O-Matic e, no processo, aprendeu a:
- adicionar a WorkManager ao projeto;
- programar uma
OneOffWorkRequest
; - usar parâmetros de entrada e saída;
- encadear trabalhos com
WorkRequest
s; - nomear cadeias
WorkRequest
únicas; - incluir tags em
WorkRequest
s; - exibir
WorkInfo
na IU; - cancelar
WorkRequest
s; - adicionar restrições a uma
WorkRequest
.
Excelente trabalho! Para ver o estado final do código e todas as modificações, confira:
Ou, se preferir, você pode clonar o codelab da WorkManager no GitHub:
$ git clone -b java https://github.com/googlecodelabs/android-workmanager
A 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, consulte a documentação da WorkManager.