Pekerjaan Latar Belakang dengan WorkManager - Kotlin

Ada banyak opsi di Android untuk pekerjaan latar belakang yang dapat ditangguhkan. Codelab ini mencakup WorkManager, library sederhana, fleksibel, dan memiliki kompatibilitas mundur 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 itu WorkManager

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 yang 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 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_kotlin 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:

  • BlurApplication: Class ini berisi penyiapan Aplikasi.
  • 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-ktx:$versions.work"
}

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

build.gradle

versions.work = "2.3.4"

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.

Langkah 2 - Tambahkan konstruktor

Tambahkan dependensi ke Worker untuk class BlurWorker:

class BlurWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
}

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 properti applicationContext. Anda akan memerlukan ini untuk berbagai manipulasi bitmap yang akan Anda lakukan.
  2. Buat Bitmap dari gambar pengujian:
val picture = BitmapFactory.decodeResource(
        appContext.resources,
        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: Timber.e(throwable, "Error applying blur")
  7. Dalam pernyataan catch, tampilkan Result.failure()

Kode yang sudah selesai untuk langkah ini ada di bawah.

BlurWorker.kt

package com.example.background.workers

import android.content.Context
import android.graphics.BitmapFactory
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.example.background.R
import timber.log.Timber

class BlurWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {

    override fun doWork(): Result {
        val appContext = applicationContext

        makeStatusNotification("Blurring image", appContext)

        return try {
            val picture = BitmapFactory.decodeResource(
                    appContext.resources,
                    R.drawable.test)

            val output = blurBitmap(picture, appContext)

            // Write bitmap to a temp file
            val outputUri = writeBitmapToFile(appContext, output)

            makeStatusNotification("Output is $outputUri", appContext)

            Result.success()
        } catch (throwable: Throwable) {
            Timber.e(throwable, "Error applying blur")
            Result.failure()
        }
    }
}

Langkah 4 - Dapatkan WorkManager di ViewModel

Buat variabel untuk instance WorkManager di ViewModel:

BlurViewModel.kt

private val workManager = WorkManager.getInstance(application)

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.kt

Internal fun applyBlur(blurLevel: Int) {
   workManager.enqueue(OneTimeWorkRequest.from(BlurWorker::class.java))
}

Langkah 6 - Jalankan kode Anda.

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


Anda juga dapat membuka Device File Explorer di Android Studio:

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


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 imageUri.

Buat metode pribadi bernama createInputDataForUri. Metode ini harus:

  1. Membuat objek Data.Builder.
  2. Jika imageUri 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.kt

/**
 * Creates the input data bundle which includes the Uri to operate on
 * @return Data which contains the Image Uri as a String
 */
private fun createInputDataForUri(): Data {
    val builder = Data.Builder()
    imageUri?.let {
        builder.putString(KEY_IMAGE_URI, imageUri.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.kt

internal fun applyBlur(blurLevel: Int) {
    val blurRequest = OneTimeWorkRequestBuilder<BlurWorker>()
            .setInputData(createInputDataForUri())
            .build()

    workManager.enqueue(blurRequest)
}

Langkah 3 - Perbarui doWork() BlurWorker untuk mendapatkan input

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

BlurWorker.kt

override fun doWork(): Result {
    val appContext = applicationContext

    // ADD THIS LINE
    val resourceUri = inputData.getString(KEY_IMAGE_URI)

    // ... rest of doWork()
}

Langkah 4 - Buramkan URI yang ditentukan

Dengan URI, Anda dapat memburamkan gambar yang dipilih pengguna:

BlurWorker.kt

override fun doWork(): Result {
    val appContext = applicationContext

    val resourceUri = inputData.getString(KEY_IMAGE_URI)

    makeStatusNotification("Blurring image", appContext)

    return try {
        // REMOVE THIS
        //    val picture = BitmapFactory.decodeResource(
        //            appContext.resources,
        //            R.drawable.test)

        if (TextUtils.isEmpty(resourceUri)) {
            Timber.e("Invalid input uri")
            throw IllegalArgumentException("Invalid input uri")
        }

        val resolver = appContext.contentResolver

        val picture = BitmapFactory.decodeStream(
                resolver.openInputStream(Uri.parse(resourceUri)))

        val output = blurBitmap(picture, appContext)

        // Write bitmap to a temp file
        val outputUri = writeBitmapToFile(appContext, output)

        Result.success()
    } catch (throwable: Throwable) {
        Timber.e(throwable)
        Result.failure()
    }
}

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. Tampilkan ini ke WorkManager menggunakan metode Result.success(Data outputData).

BlurWorker.kt

Ubah baris Result.success() di doWork() menjadi:

val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())

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 mengklik Synchronize untuk melihat gambar Anda:

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:

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 - Perluas Pekerja

Tambahkan dependensi ke Worker untuk class CleanupWorker:

class CleanupWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
}

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.kt

package com.example.background.workers

import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.example.background.OUTPUT_PATH
import java.io.File
import timber.log.Timber

/**
 * Cleans up temporary files generated during blurring process
 */
class CleanupWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {

    override 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("Cleaning up old temporary files", applicationContext)
        sleep()

        return 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()
                            Timber.i("Deleted $name - $deleted")
                        }
                    }
                }
            }
            Result.success()
        } catch (exception: Exception) {
            Timber.e(exception)
            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.

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.kt

package com.example.background.workers

import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import androidx.work.workDataOf
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.example.background.KEY_IMAGE_URI
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import timber.log.Timber

/**
 * Saves the image to a permanent file
 */
class SaveImageToFileWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {

    private val Title = "Blurred Image"
    private val dateFormatter = SimpleDateFormat(
            "yyyy.MM.dd 'at' HH:mm:ss z",
            Locale.getDefault()
    )

    override 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("Saving image", applicationContext)
        sleep()

        val resolver = applicationContext.contentResolver
        return 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 {
                Timber.e("Writing to MediaStore failed")
                Result.failure()
            }
        } catch (exception: Exception) {
            Timber.e(exception)
            Result.failure()
        }
    }
}

Langkah 5 - Ubah Notifikasi BlurWorker

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

BlurWorker.kt

class BlurWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {

override fun doWork(): Result {
    val appContext = applicationContext

    val resourceUri = inputData.getString(KEY_IMAGE_URI)

    makeStatusNotification("Blurring image", appContext)

    // ADD THIS TO SLOW DOWN THE WORKER
    sleep()
    // ^^^^

    return try {
        if (TextUtils.isEmpty(resourceUri)) {
            Timber.e("Invalid input uri")
            throw IllegalArgumentException("Invalid input uri")
        }

        val resolver = appContext.contentResolver

        val picture = BitmapFactory.decodeStream(
                resolver.openInputStream(Uri.parse(resourceUri)))

        val output = blurBitmap(picture, appContext)

        // Write bitmap to a temp file
        val outputUri = writeBitmapToFile(appContext, output)

        val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())

        Result.success(outputData)
    } catch (throwable: Throwable) {
        Timber.e(throwable)
        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.kt

val blurRequest = OneTimeWorkRequestBuilder<BlurWorker>()
        .setInputData(createInputDataForUri())
        .build()

workManager.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
val continuation = workManager.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:

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

Kode untuk ini ada di bawah ini:

BlurViewModel.kt

internal 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 blurRequest = OneTimeWorkRequest.Builder(BlurWorker::class.java)
            .setInputData(createInputDataForUri())
            .build()

    continuation = continuation.then(blurRequest)

    // Add WorkRequest to save the image to the filesystem
    val save = OneTimeWorkRequest.Builder(SaveImageToFileWorker::class.java).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:

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.kt

internal fun applyBlur(blurLevel: Int) {
    // Add WorkRequest to Cleanup temporary images
    var continuation = workManager
            .beginWith(OneTimeWorkRequest
            .from(CleanupWorker::class.java))

    // Add WorkRequests to blur the image the number of times requested
    for (i in 0 until blurLevel) {
        val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()

        // 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
    val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
            .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!

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.kt

// REPLACE THIS CODE:
// var continuation = workManager
//            .beginWith(OneTimeWorkRequest
//            .from(CleanupWorker::class.java))
// WITH
var continuation = workManager
        .beginUniqueWork(
                IMAGE_MANIPULATION_WORK_NAME,
                ExistingWorkPolicy.REPLACE,
                OneTimeWorkRequest.from(CleanupWorker::class.java)
        )

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<WorkInfo> 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<List<WorkInfo>> 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<List<WorkInfos>> untuk setiap tag tunggal.

Anda akan memberi tag SaveImageToFileWorker WorkRequest, agar Anda bisa mendapatkannya menggunakan getWorkInfosByTag. 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 getWorkInfosForUniqueWork 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.kt

val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
        .addTag(TAG_OUTPUT) // <-- ADD THIS
        .build()

Langkah 2 - Dapatkan WorkInfo

Setelah memberi tag pada pekerjaan, Anda bisa mendapatkan WorkInfo:

  1. Deklarasikan variabel baru bernama outputWorkInfos yang merupakan LiveData<List<WorkInfo>>
  2. Dalam BlurViewModel, tambahkan blok init untuk mendapatkan WorkInfo menggunakan WorkManager.getWorkInfosByTagLiveData

Kode yang Anda butuhkan ada di bawah ini:

BlurViewModel.kt

// New instance variable for the WorkInfo
internal val outputWorkInfos: LiveData<List<WorkInfo>>

// Add an init block to the BlurViewModel class
init {
    // This transformation makes sure that whenever the current work Id changes the WorkInfo
    // the UI is listening to changes
    outputWorkInfos = workManager.getWorkInfosByTagLiveData(TAG_OUTPUT)
} 

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.state().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.kt

// Show work status, added in onCreate()
viewModel.outputWorkInfos.observe(this, workInfosObserver())

// Add this functions
private fun workInfosObserver(): Observer<List<WorkInfo>> {
    return Observer { listOfWorkInfo ->

        // Note that these next few lines grab a single WorkInfo if it exists
        // This code could be in a Transformation in the ViewModel; they are included here
        // so that the entire process of displaying a WorkInfo is in one location.

        // If there are no matching work info, do nothing
        if (listOfWorkInfo.isNullOrEmpty()) {
            return@Observer
        }

        // We only care about the one output status.
        // Every continuation has only one worker tagged TAG_OUTPUT
        val workInfo = listOfWorkInfo[0]

        if (workInfo.state.isFinished) {
            showWorkFinished()
        } else {
            showWorkInProgress()
        }
    }
}

Langkah 4 - Jalankan aplikasi Anda

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

Setiap WorkInfo juga memiliki metode getOutputData yang memungkinkan Anda untuk mendapatkan objek Data output dengan gambar akhir yang disimpan. Di Kotlin, Anda dapat mengakses metode ini menggunakan pengakses sintetis yang dihasilkan bahasa untuk Anda: outputData. Mari kita tampilkan tombol See File setiap kali ada gambar buram yang siap ditampilkan.

Langkah 1 - Buat tombol See File

Sudah ada tombol di tata letak activity_blur.xml yang tersembunyi. Tombol berada di BlurActivity dan disebut outputButton.

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.kt

// Put this inside onCreate()
// Setup view output image file button
binding.seeFileButton.setOnClickListener {
     viewModel.outputUri?.let { currentUri ->
         val actionView = Intent(Intent.ACTION_VIEW, currentUri)
         actionView.resolveActivity(packageManager)?.run {
             startActivity(actionView)
         }
    }
}

Langkah 2 - 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.outputData.
  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 outputButton dan panggil setOutputUri pada model tampilan dengan URI.

BlurActivity.kt

private fun workInfosObserver(): Observer<List<WorkInfo>> {
    return Observer { listOfWorkInfo ->

        // Note that these next few lines grab a single WorkInfo if it exists
        // This code could be in a Transformation in the ViewModel; they are included here
        // so that the entire process of displaying a WorkInfo is in one location.

        // If there are no matching work info, do nothing
        if (listOfWorkInfo.isNullOrEmpty()) {
            return@Observer
        }

        // We only care about the one output status.
        // Every continuation has only one worker tagged TAG_OUTPUT
        val workInfo = listOfWorkInfo[0]

        if (workInfo.state.isFinished) {
            showWorkFinished()

            // Normally this processing, which is not directly related to drawing views on
            // screen would be in the ViewModel. For simplicity we are keeping it here.
            val outputImageUri = workInfo.outputData.getString(KEY_IMAGE_URI)

            // If there is an output file show "See File" button
            if (!outputImageUri.isNullOrEmpty()) {
                viewModel.setOutputUri(outputImageUri as String)
                binding.seeFileButton.visibility = View.VISIBLE
            }
        } else {
            showWorkInProgress()
        }
    }
}

Langkah 3 - Jalankan kode Anda

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

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

BlurViewModel.kt

internal fun cancelWork() {
    workManager.cancelUniqueWork(IMAGE_MANIPULATION_WORK_NAME)
}

Langkah 2 - Panggil metode pembatalan

Lalu, hubungkan tombol cancelButton untuk memanggil cancelWork:

BlurActivity.kt

// In onCreate()
// Hookup the Cancel button
binding.cancelButton.setOnClickListener { viewModel.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.

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.kt

// Put this inside the applyBlur() function
// Create charging constraint
val constraints = Constraints.Builder()
        .setRequiresCharging(true)
        .build()

// Add WorkRequest to save the image to the filesystem
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
        .setConstraints(constraints)
        .addTag(TAG_OUTPUT)
        .build()
continuation = continuation.then(save)

// Actually start the work
continuation.enqueue()

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:

Jika perangkat tidak mengisi daya, perangkat akan menangguhkan SaveImageToFileWorker, yang mengeksekusinya hanya setelah Anda mencolokkan perangkat.

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 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, baca dokumentasi WorkManager atau lanjutkan ke codelab WorkManager lanjutan.