Pekerjaan Latar Belakang dengan WorkManager - Java

Ada banyak opsi di Android untuk pekerjaan latar belakang yang dapat ditangguhkan. Codelab ini mencakup WorkManager, library sederhana yang kompatibel dan fleksibel untuk pekerjaan latar belakang yang dapat ditangguhkan. WorkManager adalah penjadwal tugas yang direkomendasikan di Android untuk pekerjaan yang dapat ditangguhkan, dan dijamin akan dieksekusi.

Apa WorkManager itu

WorkManager adalah bagian dari Android Jetpack dan Komponen Arsitektur untuk pekerjaan latar belakang yang memerlukan kombinasi eksekusi oportunistik dan terjamin. Eksekusi oportunistik berarti WorkManager akan melakukan pekerjaan latar belakang Anda sesegera mungkin. Eksekusi terjamin berarti WorkManager akan menangani logika untuk memulai pekerjaan dalam berbagai situasi, meskipun Anda keluar dari aplikasi.

WorkManager adalah library sederhana, namun sangat fleksibel yang memiliki banyak manfaat tambahan. Ini mencakup:

  • Dukungan untuk tugas satu kali atau berkala asinkron
  • Dukungan untuk batasan seperti kondisi jaringan, ruang penyimpanan, dan status pengisian daya
  • Merangkai permintaan pekerjaan yang kompleks, termasuk menjalankan pekerjaan secara paralel
  • Output dari satu permintaan pekerjaan digunakan sebagai input untuk permintaan berikutnya
  • Menangani kompatibilitas API level kembali ke API level 14 (lihat catatan)
  • Berfungsi dengan atau tanpa layanan Google Play
  • Mengikuti praktik terbaik kesehatan sistem
  • Dukungan LiveData agar dapat dengan mudah menampilkan status permintaan pekerjaan di UI

Kapan harus menggunakan WorkManager

Library WorkManager adalah pilihan tepat untuk tugas yang berguna untuk diselesaikan, bahkan jika pengguna keluar dari layar tertentu atau aplikasi Anda.

Beberapa contoh tugas yang menggunakan WorkManager dengan baik:

  • Mengupload log
  • Menerapkan filter ke gambar dan menyimpan gambar
  • Menyinkronkan data lokal dengan jaringan secara berkala

WorkManager menawarkan eksekusi terjamin, dan tidak semua tugas memerlukannya. Dengan demikian, tidak semua tugas akan dijalankan di thread utama. Untuk detail selengkapnya tentang kapan harus menggunakan WorkManager, lihat Panduan pemrosesan latar belakang.

Yang akan Anda build

Saat ini, smartphone hampir sempurna dalam mengambil gambar. Lewatlah sudah hari-hari saat seorang fotografer dapat mengambil gambar yang cukup buram dari sesuatu yang misterius.

Dalam codelab ini, Anda akan menjalankan Blur-O-Matic, sebuah aplikasi untuk memburamkan foto dan gambar serta menyimpan hasilnya ke file. Apakah itu monster Loch Ness atau kapal selam mainan evelopera? Dengan Blur-O-Matic, tidak akan ada yang tahu.

Foto ikan kakap bergaris hibrida oleh Peggy Greb, USDA Agricultural Research Service.

Yang akan Anda pelajari

  • Menambahkan WorkManager ke project Anda
  • Menjadwalkan tugas sederhana
  • Parameter input dan output
  • Perantaian pekerjaan
  • Pekerjaan unik
  • Menampilkan status pekerjaan di UI
  • Membatalkan pekerjaan
  • Batasan pekerjaan

Yang Anda butuhkan

Jika Anda mengalami masalah pada tahap tertentu...

Jika Anda mengalami masalah dengan codelab di tahap tertentu, atau jika Anda ingin melihat status akhir kode, Anda dapat menggunakan link berikut:

Download kode akhir

Atau jika mau, Anda dapat meng-clone codelab WorkManager yang telah selesai dari GitHub:

$ git clone -b java https://github.com/googlecodelabs/android-workmanager

Langkah 1 - Download Kode

Klik link berikut guna mendownload semua kode untuk codelab ini:

Download kode awal

Atau jika mau, Anda dapat meng-clone codelab navigasi dari GitHub:

$ git clone -b start_java https://github.com/googlecodelabs/android-workmanager

Langkah 2 - Dapatkan Gambar

Jika Anda menggunakan perangkat yang telah mendownload atau mengambil gambar di perangkat, berarti Anda telah siap.

Jika Anda menggunakan perangkat baru (seperti emulator yang baru dibuat), sebaiknya ambil gambar atau download gambar dari web menggunakan perangkat. Pilih sesuatu yang misterius.

Langkah 3 - Jalankan aplikasi

Jalankan aplikasi. Anda akan melihat layar berikut (pastikan Anda memberikan izin untuk mengakses foto dari perintah awal dan jika gambar dinonaktifkan, buka kembali aplikasi):

Anda dapat memilih gambar dan membuka layar berikutnya yang memiliki tombol pilihan. Di layar tersebut, Anda dapat menentukan tingkat keburaman gambar. Menekan tombol Go pada akhirnya akan memburamkan dan menyimpan gambar.

Sekarang, aplikasi tidak menerapkan efek buram.

Kode awal berisi:

  • WorkerUtils**:** Class ini berisi kode untuk pemburaman yang sebenarnya, dan beberapa metode praktis yang nantinya akan digunakan untuk menampilkan Notifications dan memperlambat aplikasi.
  • BlurActivity***:** Aktivitas yang menampilkan gambar dan menyertakan tombol pilihan untuk memilih tingkat keburaman.
  • BlurViewModel***:** Model tampilan ini menyimpan semua data yang diperlukan untuk menampilkan BlurActivity. Kode ini juga akan menjadi class tempat Anda memulai pekerjaan latar belakang menggunakan WorkManager.
  • Constants**:** Class statis dengan beberapa konstanta yang akan Anda gunakan selama codelab.
  • SelectImageActivity**:** Aktivitas pertama yang memungkinkan Anda untuk memilih gambar.
  • res/activity_blur.xml dan res/activity_select.xml: File tata letak untuk setiap aktivitas.

***** Ini adalah satu-satunya file tempat Anda menuliskan kode.

WorkManager memerlukan dependensi gradle di bawah ini. Dependensi ini sudah disertakan dalam file build:

app/build.gradle

dependencies {
    // Other dependencies
    implementation "androidx.work:work-runtime:$versions.work"
}

Anda harus mendapatkan versi terbaru work-runtime dari sini dan menempatkan versi yang benar. Untuk saat ini, versi terbaru adalah:

build.gradle

versions.work = "2.3.3"

Jika Anda mengupdate versi ke yang lebih baru, pastikan untuk memilih Sync Now guna menyinkronkan project Anda dengan file gradle yang diubah.

Pada langkah ini, Anda akan mengambil gambar di folder res/drawable bernama test.jpg dan menjalankan beberapa fungsi di dalamnya di latar belakang. Fungsi ini akan memburamkan gambar dan menyimpannya ke file sementara.

Dasar-dasar WorkManager

Ada beberapa class WorkManager yang perlu Anda ketahui:

  • Worker: Ini adalah tempat Anda menempatkan kode untuk pekerjaan yang sebenarnya yang ingin Anda lakukan di latar belakang. Anda akan memperluas class ini dan mengganti metode doWork().
  • WorkRequest: Ini mewakili permintaan untuk melakukan beberapa pekerjaan. Anda akan meneruskan Worker sebagai bagian dari pembuatan WorkRequest. Saat membuat WorkRequest, Anda juga dapat menentukan hal-hal seperti Constraints terkait kapan Worker harus dijalankan.
  • WorkManager: Class ini sebenarnya menjadwalkan WorkRequest Anda dan menjalankannya. Class ini menjadwalkan WorkRequest dengan cara menyebarkan beban pada resource sistem, sekaligus memenuhi batasan yang Anda tetapkan.

Dalam kasus ini, Anda akan menentukan BlurWorker baru yang akan berisi kode untuk memburamkan gambar. Saat tombol Go diklik, WorkRequest akan dibuat lalu diantrekan oleh WorkManager.

Langkah 1 - Buat BlurWorker

Pada paket workers, buat class baru bernama BlurWorker.

Class ini harus memperluas Worker.

Langkah 2 - Tambahkan konstruktor

Tambahkan konstruktor ke class BlurWorker:

public class BlurWorker extends Worker {
    public BlurWorker(
        @NonNull Context appContext,
        @NonNull WorkerParameters workerParams) {
            super(appContext, workerParams);
    }
}

Langkah 3 - Ganti dan implementasikan doWork()

Worker Anda akan memburamkan gambar res/test.jpg.

Ganti metode doWork(), lalu implementasikan hal berikut:

  1. Dapatkan Context dengan memanggil getApplicationContext(). Anda akan memerlukan ini untuk berbagai manipulasi bitmap yang akan Anda lakukan.
  2. Buat Bitmap dari gambar pengujian:
Bitmap picture = BitmapFactory.decodeResource(
    applicationContext.getResources(),
    R.drawable.test);
  1. Dapatkan versi buram bitmap dengan memanggil metode blurBitmap statis dari WorkerUtils.
  2. Tulis bitmap tersebut ke file sementara dengan memanggil metode writeBitmapToFile statis dari WorkerUtils. Pastikan untuk menyimpan URI yang ditampilkan ke variabel lokal.
  3. Buat Notifikasi yang menampilkan URI dengan memanggil metode makeStatusNotification statis dari WorkerUtils.
  4. Tampilkan Result.success();
  5. Gabungkan kode dari langkah 2-6 dalam pernyataan try/catch. Tangkap Throwable generik.
  6. Dalam pernyataan catch, tampilkan Laporan log error: Log.e(TAG, "Error applying blur", throwable);
  7. Dalam pernyataan catch, tampilkan Result.failure();

Kode yang sudah selesai untuk langkah ini ada di bawah.

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();
        }
    }
}

Langkah 4 - Dapatkan WorkManager di ViewModel

Buat variabel untuk instance WorkManager di ViewModel dan buat instance di konstruktor ViewModel:

BlurViewModel.java

private WorkManager mWorkManager;

// BlurViewModel constructor
public BlurViewModel(@NonNull Application application) {
  super(application);
  mWorkManager = WorkManager.getInstance(application);

  //...rest of the constructor
}

Langkah 5 - Antrekan WorkRequest di WorkManager

Baik, saatnya membuat WorkRequest dan memberi tahu WorkManager untuk menjalankannya. Ada dua jenis WorkRequest:

  • OneTimeWorkRequest: WorkRequest yang hanya akan dieksekusi satu kali.
  • PeriodicWorkRequest: WorkRequest yang akan diulang pada siklus.

Kita hanya ingin gambar diburamkan satu kali saat tombol Go diklik. Metode applyBlur dipanggil saat tombol Go diklik, jadi buat OneTimeWorkRequest dari BlurWorker di sana. Lalu, gunakan instance WorkManager untuk mengantrekan WorkRequest.

Tambahkan baris kode berikut ke metode applyBlur() BlurViewModel:

BlurViewModel.java

void applyBlur(int blurLevel) {
   mWorkManager.enqueue(OneTimeWorkRequest.from(BlurWorker.class));
}

Langkah 6 - Jalankan kode Anda.

Jalankan kode. Kode harus mengompilasi dan Anda akan melihat Notifikasi saat Anda menekan tombol Go.

7ef0320960f4d756.png

Anda juga dapat membuka Device File Explorer di Android Studio:

cf10a1af6e84f5ff.png

Lalu buka data>data>com.example.background>files>blur_filter_outputs><URI> dan pastikan bahwa ikan tersebut sudah diburamkan:

7f5eba3559b44cbb.png

Pemburaman gambar pengujian sudah berhasil, tetapi agar Blur-O-Matic benar-benar menjadi aplikasi pengeditan gambar yang revolusioner seperti yang diharapkan, Anda harus mengizinkan pengguna memburamkan gambar mereka sendiri.

Untuk melakukannya, kita akan memberikan URI gambar pilihan pengguna sebagai input ke WorkRequest.

Langkah 1 - Buat Objek input data

Input dan output diteruskan ke dan dikeluarkan melalui objek Data. Objek Data adalah container ringan untuk key-value pair. Objek tersebut dimaksudkan untuk menyimpan sejumlah kecil data yang dapat diteruskan ke dan keluar dari WorkRequest.

Anda akan meneruskan URI untuk gambar pengguna ke dalam paket. URI tersebut disimpan dalam variabel bernama mImageUri.

Buat metode pribadi bernama createInputDataForUri. Metode ini harus:

  1. Membuat objek Data.Builder.
  2. Jika mImageUri bukan URI non-null, tambahkan ke objek Data menggunakan metode putString. Metode ini mengambil kunci dan nilai. Anda dapat menggunakan konstanta String KEY_IMAGE_URI dari class Constants.
  3. Panggil build() pada objek Data.Builder untuk membuat objek Data Anda, lalu menampilkannya.

Berikut adalah metode createInputDataForUri yang telah selesai:

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();
}

Langkah 2 - Teruskan Objek data ke WorkRequest

Anda akan mengubah metode applyBlur sehingga:

  1. Membuat OneTimeWorkRequest.Builder baru.
  2. Memanggil setInputData, meneruskan hasil dari createInputDataForUri.
  3. Mem-build OneTimeWorkRequest.
  4. Mengantrekan permintaan tersebut menggunakan WorkManager.

Berikut adalah metode applyBlur yang telah selesai:

BlurViewModel.java

void applyBlur(int blurLevel) {
   OneTimeWorkRequest blurRequest =
                new OneTimeWorkRequest.Builder(BlurWorker.class)
                        .setInputData(createInputDataForUri())
                        .build();

   mWorkManager.enqueue(blurRequest);
}

Langkah 3 - Perbarui doWork() BlurWorker untuk mendapatkan input

Sekarang mari kita perbarui metode doWork() BlurWorker untuk mendapatkan URI yang kita teruskan dari objek Data:

BlurWorker.java

public Result doWork() {

       Context applicationContext = getApplicationContext();

        // ADD THIS LINE
       String resourceUri = getInputData().getString(Constants.KEY_IMAGE_URI);

        //... rest of doWork()
}

Variabel ini tidak digunakan sampai Anda menyelesaikan langkah berikutnya.

Langkah 4 - Buramkan URI yang ditentukan

Dengan URI, Anda dapat memburamkan gambar yang dipilih pengguna:

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

Langkah 5 - Buat output URI sementara

Kita sudah selesai dengan Pekerja ini, dan sekarang kita dapat menampilkan Result.success(). Kita akan menyediakan OutputURI sebagai Data output agar gambar sementara ini dapat diakses dengan mudah oleh pekerja lain untuk operasi selanjutnya. Hal ini akan berguna dalam bab berikutnya saat kita membuat Rantai pekerja. Untuk melakukan ini:

  1. Buat Data baru, seperti yang Anda lakukan dengan input, dan simpan outputUri sebagai String. Gunakan kunci yang sama, KEY_IMAGE_URI
  2. Teruskan ini ke metode Result.success() Worker.

BlurWorker.java

Baris ini harus mengikuti baris WorkerUtils.makeStatusNotification dan mengganti Result.success() di doWork():

Data outputData = new Data.Builder()
    .putString(KEY_IMAGE_URI, outputUri.toString())
    .build();
return Result.success(outputData);

Langkah 6 - Jalankan aplikasi Anda

Pada tahap ini, Anda dapat menjalankan aplikasi. Aplikasi harus mengompilasi dan memiliki perilaku yang sama.

Anda juga dapat membuka Device File Explorer di Android Studio dan menavigasikan ke data/data/com.example.background/files/blur_filter_outputs/<URI> seperti yang Anda lakukan di langkah sebelumnya.

Perhatikan bahwa Anda mungkin perlu memilih Synchronize untuk melihat gambar Anda:

7e717ffd6b3d9d52.png

Bagus sekali! Anda berhasil memburamkan gambar input menggunakan WorkManager.

Saat ini, Anda sedang melakukan satu tugas pekerjaan: memburamkan gambar. Pekerjaan ini adalah langkah pertama yang bagus, tetapi tidak memiliki beberapa fungsi inti:

  • Pekerjaan ini tidak membersihkan file sementara.
  • Pekerjaan ini tidak benar-benar menyimpan gambar ke file permanen.
  • Pekerjaan ini selalu memburamkan gambar dengan jumlah yang sama.

Kita akan menggunakan rantai pekerjaan WorkManager untuk menambahkan fungsi ini.

WorkManager memungkinkan Anda membuat WorkerRequest terpisah yang berjalan sesuai urutan atau paralel. Pada langkah ini, Anda akan membuat rantai pekerjaan yang terlihat seperti ini:

54832b34e9c9884a.png

WorkRequest digambarkan sebagai kotak.

Fitur lain yang benar-benar bagus dari perantaian adalah output dari satu WorkRequest menjadi input dari WorkRequest berikutnya dalam rantai. Input dan output yang diteruskan di antara setiap WorkRequest ditampilkan sebagai teks biru.

Langkah 1 - Buat Pembersihan dan Simpan Pekerja

Pertama, Anda akan menentukan semua class Worker yang dibutuhkan. Anda sudah memiliki Worker untuk memburamkan gambar, tetapi Anda juga memerlukan Worker yang membersihkan file sementara dan Worker yang menyimpan gambar secara permanen.

Buat dua class baru di paket worker yang memperluas Worker.

Class pertama harus disebut CleanupWorker, class kedua harus disebut SaveImageToFileWorker.

Langkah 2 - Tambahkan konstruktor

Tambahkan konstruktor ke class CleanupWorker:

public class CleanupWorker extends Worker {
    public CleanupWorker(
            @NonNull Context appContext,
            @NonNull WorkerParameters workerParams) {
        super(appContext, workerParams);
    }
}

Langkah 3 - Ganti dan implementasikan doWork() untuk CleanupWorker

CleanupWorker tidak perlu mengambil input apa pun atau meneruskan output apa pun. Pekerja ini selalu menghapus file sementara jika ada. Karena ini bukan codelab tentang manipulasi file, Anda dapat menyalin kode untuk CleanupWorker di bawah ini:

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();
        }
    }
}

Langkah 4 - Ganti dan implementasikan doWork() untuk SaveImageToFileWorker

SaveImageToFileWorker akan mengambil input dan output. Inputnya adalah String yang disimpan dengan kunci KEY_IMAGE_URI. Dan outputnya juga String yang disimpan dengan kunci KEY_IMAGE_URI.

475a08a82ea675ca.png

Karena codelab ini belum membahas manipulasi file, kodenya ada di bawah ini, berisi dua TODO untuk Anda guna mengisi kode yang sesuai untuk input dan output. Ini sangat mirip dengan kode yang Anda tulis untuk input dan output di langkah sebelumnya (kode ini menggunakan semua kunci yang sama).

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();
        }
    }
}

Langkah 5 - Ubah Notifikasi BlurWorker

Sekarang setelah rantai Worker menangani penyimpanan gambar dalam folder yang benar, kita dapat mengubah notifikasi untuk memberi tahu pengguna saat pekerjaan dimulai dan memperlambat pekerjaan sehingga lebih mudah untuk melihat setiap WorkRequest dimulai, bahkan pada perangkat yang diemulasikan. Versi akhir BlurWorker menjadi:

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();
    }
}

Langkah 6 - Buat Rantai WorkRequest

Anda harus mengubah metode applyBlur BlurViewModel untuk mengeksekusi rantai WorkRequest, bukan hanya satu. Saat ini, kode terlihat seperti ini:

BlurViewModel.java

void applyBlur(int blurLevel) {
    OneTimeWorkRequest blurRequest =
            new OneTimeWorkRequest.Builder(BlurWorker.class)
                    .setInputData(createInputDataForUri())
                    .build();

    mWorkManager.enqueue(blurRequest);
}

Jangan panggil WorkManager.enqueue(), tetapi panggil WorkManager.beginWith(). Tindakan ini akan menampilkan WorkContinuation, yang menentukan rantai WorkRequest. Anda dapat menambahkan ke rantai permintaan pekerjaan ini dengan memanggil metode then(), misalnya, jika Anda memiliki tiga objek WorkRequest, workA, workB, dan workC, Anda dapat melakukan yang berikut ini:

// 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

Tindakan ini akan menghasilkan dan menjalankan rantai WorkRequest berikut:

2c4bf31e5f6522ad.png

Buat rantai CleanupWorker WorkRequest, BlurImage WorkRequest, dan SaveImageToFile WorkRequest di applyBlur. Teruskan input ke BlurImage WorkRequest.

Kode untuk ini ada di bawah ini:

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();
}

Kode harus mengompilasi dan berjalan. Anda seharusnya dapat melihat gambar apa pun yang dipilih untuk diburamkan yang kini telah disimpan di folder Pictures:

e2d29f34bdf01860.png

Langkah 7 - Ulangi BlurWorker

Saatnya menambahkan kemampuan untuk memburamkan gambar dalam jumlah yang berbeda. Ambil parameter blurLevel yang diteruskan ke applyBlur dan tambahkan sejumlah operasi WorkRequest pemburaman tersebut ke rantai. Hanya WorkRequest pertama yang memerlukan dan harus mengambil input URI.

Cobalah sendiri, lalu bandingkan dengan kode di bawah ini:

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();
}

"Pekerjaan" yang hebat! Anda sekarang dapat memburamkan gambar sebanyak atau sesedikit yang Anda inginkan. Benar-benar misterius!

fcb326118dd99959.png

Setelah Anda menggunakan rantai, sekarang saatnya menangani fitur canggih lain dari WorkManager - rantai pekerjaan unik.

Terkadang, Anda hanya ingin menjalankan satu rantai pekerjaan dalam satu waktu. Misalnya, mungkin Anda memiliki rantai pekerjaan yang menyinkronkan data lokal dengan server - Anda mungkin ingin menyelesaikan sinkronisasi data pertama sebelum memulai yang baru. Untuk melakukannya, gunakan beginUniqueWork, bukan beginWith; dan berikan nama String yang unik. Tindakan ini memberikan nama ke seluruh rantai permintaan pekerjaan sehingga Anda dapat merujuk dan mengkuerinya bersama-sama.

Pastikan rantai pekerjaan untuk memburamkan file Anda unik dengan menggunakan beginUniqueWork. Teruskan IMAGE_MANIPULATION_WORK_NAME sebagai kuncinya. Anda juga harus meneruskan ExistingWorkPolicy. Opsi Anda adalah REPLACE, KEEP, atau APPEND.

Anda akan menggunakan REPLACE karena jika pengguna memutuskan untuk memburamkan gambar lain sebelum gambar saat ini selesai, kita ingin menghentikan pemburaman gambar saat ini dan mulai memburamkan gambar baru.

Kode untuk memulai kelanjutan pekerjaan unik Anda ada di bawah ini:

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));

Blur-O-Matic kini hanya akan memburamkan satu gambar dalam satu waktu.

Bagian ini akan sering menggunakan LiveData, jadi agar sepenuhnya memahami apa yang terjadi, Anda harus memahami LiveData. LiveData adalah penyimpan data yang dapat diamati dan berbasis siklus proses.

Anda dapat melihat dokumentasi atau Codelab komponen berbasis Siklus Proses Android jika ini adalah pertama kalinya Anda bekerja dengan LiveData atau observable.

Perubahan besar berikutnya yang akan Anda lakukan adalah untuk benar-benar mengubah apa yang ditampilkan di aplikasi saat Pekerjaan dieksekusi.

Anda bisa mendapatkan status WorkRequest dengan mendapatkan LiveData yang menyimpan objek WorkInfo. WorkInfo adalah objek yang berisi detail tentang status WorkRequest saat ini, termasuk:

Tabel berikut menampilkan tiga cara berbeda untuk mendapatkan objek LiveData<WorkInfo> atau LiveData<List<WorkInfo>> beserta fungsinya.

Jenis

Metode WorkManager

Deskripsi

Mendapatkan pekerjaan menggunakan id

getWorkInfoByIdLiveData

Setiap WorkRequest memiliki ID unik yang dibuat oleh WorkManager; Anda dapat menggunakan ID ini untuk mendapatkan LiveData
tunggal untuk WorkRequest tersebut.

Mendapatkan pekerjaan menggunakan nama rantai unik

getWorkInfosForUniqueWorkLiveData

Seperti yang baru saja Anda lihat, WorkRequest dapat menjadi bagian dari rantai unik. Ini akan menampilkan LiveData
>
untuk semua pekerjaan dalam satu rantai unik WorkRequests.

Mendapatkan pekerjaan menggunakan tag

getWorkInfosByTagLiveData

Terakhir, Anda dapat memberi tag pada WorkRequest mana pun dengan String. Anda dapat memberi tag beberapa WorkRequest dengan tag yang sama untuk mengaitkannya. Tindakan ini akan menampilkan LiveData
>
untuk setiap tag tunggal.

Anda akan memberi tag SaveImageToFileWorker WorkRequest, agar Anda bisa mendapatkannya menggunakan getWorkInfosByTagLiveData. Anda akan menggunakan tag untuk memberi label pekerjaan, bukan menggunakan ID WorkManager, karena jika pengguna memburamkan beberapa gambar, semua WorkRequest gambar yang disimpan akan memiliki tag yang sama, namun bukan ID yang sama. Anda juga dapat memilih tag.

Anda tidak akan menggunakan getWorkInfosForUniqueWorkLiveData karena akan menampilkan WorkInfo untuk semua WorkRequest pemburaman dan WorkRequest pembersihan juga; serta akan membutuhkan logika tambahan untuk menemukan WorkRequest gambar yang disimpan.

Langkah 1 - Beri tag pekerjaan Anda

Di applyBlur, saat membuat SaveImageToFileWorker, beri tag pekerjaan Anda menggunakan konstanta String TAG_OUTPUT :

BlurViewModel.java

OneTimeWorkRequest save = new OneTimeWorkRequest.Builder(SaveImageToFileWorker.class)
        .addTag(TAG_OUTPUT) // This adds the tag
        .build();

Langkah 2 - Dapatkan WorkInfo

Setelah memberi tag pada pekerjaan, Anda bisa mendapatkan WorkInfo:

  1. Deklarasikan variabel baru bernama mSavedWorkInfo yang merupakan LiveData<List<WorkInfo>>
  2. Di konstruktor BlurViewModel, dapatkan WorkInfo menggunakan WorkManager.getWorkInfosByTagLiveData
  3. Tambahkan pengambil untuk mSavedWorkInfo

Kode yang Anda butuhkan ada di bawah ini:

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; }

Langkah 3 - Tampilkan WorkInfo

Setelah memiliki LiveData untuk WorkInfo, Anda dapat mengamatinya di BlurActivity. Dalam observer:

  1. Periksa apakah daftar WorkInfo bukan null dan apakah ada objek WorkInfo di dalamnya - jika tidak ada, maka tombol Go belum diklik. Jadi, kembali.
  2. Dapatkan WorkInfo pertama dalam daftar; hanya akan ada satu WorkInfo yang diberi tag dengan TAG_OUTPUT karena kita membuat rantai pekerjaan unik.
  3. Periksa apakah status pekerjaan telah selesai, menggunakan workInfo.getState().isFinished();
  4. Jika belum selesai, panggil showWorkInProgress() yang menyembunyikan dan menampilkan tampilan yang sesuai.
  5. Jika telah selesai, panggil showWorkFinished() yang akan menyembunyikan dan menampilkan tampilan yang sesuai.

Berikut kodenya:

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();
    }
});

Langkah 4 - Jalankan aplikasi Anda

Jalankan aplikasi Anda - aplikasi harus mengompilasi dan berjalan, dan kini menampilkan status progres saat berfungsi serta tombol batal:

b7d8d3182f91ce23.png

Setiap WorkInfo juga memiliki metode getOutputData yang memungkinkan Anda untuk mendapatkan objek Data output dengan gambar akhir yang disimpan. Mari kita tampilkan tombol See File setiap kali ada gambar buram yang siap ditampilkan.

Langkah 1 - Buat mOutputUri

Buat variabel dalam BlurViewModel untuk URI akhir dan berikan pengambil dan penyetel untuk variabel tersebut. Untuk mengubah String menjadi Uri, Anda dapat menggunakan metode uriOrNull.

Anda dapat menggunakan kode di bawah ini:

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; }

Langkah 2 - Buat tombol See File

Sudah ada tombol di tata letak activity_blur.xml yang tersembunyi. Tombol berada di BlurActivity dan dapat diakses melalui View Binding-nya sebagai seeFileButton.

Siapkan pemroses klik untuk tombol tersebut. Tombol harus mendapatkan URI, lalu membuka aktivitas untuk melihat URI tersebut. Anda dapat menggunakan kode di bawah ini:

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);
        }
    }
});

Langkah 3 - Tetapkan URI dan tampilkan tombol

Ada beberapa penyesuaian terakhir yang perlu Anda terapkan ke observer WorkInfo agar pembuatan tombol berhasil:

  1. Jika WorkInfo selesai, dapatkan data output menggunakan workInfo.getOutputData().
  2. Lalu dapatkan URI output, ingat bahwa URI disimpan dengan kunci Constants.KEY_IMAGE_URI.
  3. Kemudian, jika tidak kosong, URI akan disimpan dengan benar; tampilkan seeFileButton dan panggil setOutputUri pada model tampilan dengan 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);
        }
    }
});

Langkah 4 - Jalankan kode Anda

Jalankan kode. Anda akan melihat tombol See File baru yang dapat diklik, yang akan mengarahkan Anda ke file output yang dihasilkan:

992d0b2390600774.png

bc1dc9414fe2326e.png

Anda menambahkan tombol Cancel Work ini, jadi mari kita tambahkan kode untuk membuatnya melakukan sesuatu. Dengan WorkManager, Anda dapat membatalkan pekerjaan menggunakan ID, dengan tag dan dengan nama rantai unik.

Dalam hal ini, Anda dapat membatalkan pekerjaan dengan nama rantai unik, karena Anda ingin membatalkan semua pekerjaan dalam rantai, bukan satu langkah tertentu.

Langkah 1 - Batalkan pekerjaan dengan nama

Pada model tampilan, tulis metode untuk membatalkan pekerjaan:

BlurViewModel.java

/**
 * Cancel work using the work's unique name
 */
void cancelWork() {
    mWorkManager.cancelUniqueWork(IMAGE_MANIPULATION_WORK_NAME);
}

Langkah 2 - Panggil metode pembatalan

Lalu, hubungkan tombol cancelButton untuk memanggil cancelWork:

BlurActivity.java

// In onCreate()

// Hookup the Cancel button
binding.cancelButton.setOnClickListener(view -> mViewModel.cancelWork());

Langkah 3 - Jalankan dan batalkan pekerjaan Anda

Jalankan aplikasi Anda. Aplikasi harus mengompilasi dengan baik. Mulai buramkan gambar, lalu klik tombol batal. Seluruh rantai dibatalkan.

bdaadc9bb25472cb.png

Terakhir, WorkManager mendukung Constraints. Untuk Blur-O-Matic, Anda akan menggunakan batasan sehingga perangkat harus mengisi daya saat menyimpan.

Langkah 1 - Buat dan tambahkan batasan pengisian daya

Untuk membuat objek Constraints, gunakan Constraints.Builder. Lalu, tetapkan batasan yang diinginkan dan tambahkan ke WorkRequest, seperti yang ditunjukkan di bawah ini:

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);

Langkah 2 - Uji dengan emulator atau perangkat

Sekarang Anda dapat menjalankan Blur-O-Matic. Jika menggunakan perangkat, Anda dapat melepaskan atau mencolokkan perangkat. Pada emulator, Anda dapat mengubah status pengisian daya di jendela Extended controls:

c2e56295cbe73f8.png

Jika perangkat tidak mengisi daya, perangkat akan berhenti dalam status pemuatan sampai Anda mencolokkannya.

b7d8d3182f91ce23.png

Selamat! Anda telah menyelesaikan aplikasi Blur-O-Matic dan dalam prosesnya Anda telah mempelajari:

  • Menambahkan WorkManager ke Project Anda
  • Menjadwalkan OneOffWorkRequest
  • Parameter Input dan Output
  • Membuat rantai pekerjaan WorkRequest
  • Menamai Rantai WorkRequest unik
  • Memberi tag pada WorkRequest
  • Menampilkan WorkInfo di UI
  • Membatalkan WorkRequest
  • Menambahkan batasan ke WorkRequest

"Kerja" bagus! Untuk melihat status akhir kode dan semua perubahan, lihat:

Download kode akhir

Atau jika mau, Anda dapat meng-clone codelab WorkManager dari GitHub:

$ git clone -b java https://github.com/googlecodelabs/android-workmanager

WorkManager mendukung banyak hal, lebih dari yang dapat kita bahas dalam codelab ini, termasuk pekerjaan berulang, support library pengujian, permintaan pekerjaan paralel, dan penggabungan input. Untuk mempelajari lebih lanjut, buka dokumentasi WorkManager.