Pekerjaan latar belakang dengan WorkManager - Kotlin

1. Pengantar

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 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 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)
  • Bekerja 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 menyimpan hasilnya ke file. Apakah itu monster Loch Ness atau kapal selam mainan evelopera? Dengan Blur-O-Matic, tidak akan ada yang tahu.

Gambar aplikasi dalam keadaan lengkap, dengan gambar placeholder cupcake, 3 opsi keburaman untuk diterapkan pada gambar, dan 2 tombol. Satu untuk mulai memburamkan gambar, dan satu lagi untuk melihat gambar yang diburamkan.

Gambar yang sudah diburamkan seperti yang terlihat setelah mengklik 'See File'.

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

2. Mempersiapkan

Langkah 1 - Download Kode

Klik link berikut guna mendownload semua kode untuk codelab ini:

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

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

Langkah 2 - Jalankan aplikasi

Jalankan aplikasi. Anda akan melihat layar berikut:

9e4707e0fbdd93c7.png

Layar harus memiliki tombol pilihan tempat Anda dapat memilih 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 benar-benar memburamkan gambar, dan beberapa metode praktis yang nantinya akan digunakan untuk menampilkan Notifications, menyimpan bitmap ke file, 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.
  • res/activity_blur.xml: file tata letak untuk BlurActivity.

***** Hanya file-file ini yang akan menjadi tempat Anda menulis kode.

3. Menambahkan WorkManager ke aplikasi Anda

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

app/build.gradle

dependencies {
    // WorkManager dependency
    implementation "androidx.work:work-runtime-ktx:$versions.work"
}

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

build.gradle

versions.work = "2.7.1"

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

4. Membuat WorkRequest pertama Anda

Pada langkah ini, Anda akan mengambil gambar di folder res/drawable bernama android_cupcake.png 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 Kotlin 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 cupcake yang ditampilkan.

Untuk melihat dengan lebih baik kapan pekerjaan dijalankan, Anda akan menggunakan makeStatusNotification() WorkerUtil. Metode ini akan memungkinkan Anda menampilkan banner notifikasi dengan mudah di bagian atas layar.

Ganti metode doWork(), lalu implementasikan hal berikut. Anda dapat melihat kode lengkap di akhir bagian:

  1. Dapatkan Context dengan memanggil properti applicationContext. Tetapkan ke val baru yang diberi nama appContext. Anda akan memerlukan ini untuk berbagai manipulasi bitmap yang akan Anda lakukan.
  2. Tampilkan notifikasi status menggunakan fungsi, makeStatusNotification untuk memberi tahu pengguna tentang memburamkan gambar.
  3. Buat Bitmap dari gambar cupcake:
val picture = BitmapFactory.decodeResource(
        appContext.resources,
        R.drawable.android_cupcake)
  1. Dapatkan versi buram bitmap dengan memanggil metode blurBitmap dari WorkerUtils.
  2. Tulis bitmap tersebut ke file sementara dengan memanggil metode writeBitmapToFile dari WorkerUtils. Pastikan untuk menyimpan URI yang ditampilkan ke variabel lokal.
  3. Buat Notifikasi yang menampilkan URI dengan memanggil metode makeStatusNotification dari WorkerUtils.
  4. Kembalikan Result.success().
  5. Gabungkan kode dari langkah 3-6 dalam pernyataan try/catch. Tangkap Throwable generik.
  6. Pada pernyataan catch, cetak pesan error menggunakan Laporan log: Log.e(TAG, "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 android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.example.background.R

private const val TAG = "BlurWorker"
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.android_cupcake)

            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) {
            Log.e(TAG, "Error applying blur")
            Result.failure()
        }
    }
}

Langkah 4 - Dapatkan WorkManager di ViewModel

Buat variabel class 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's:

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. Perhatikan bahwa untuk melihat hasil yang lebih buram, Anda harus memilih opsi 'More blurred' atau 'The most blurred'.

ed497b57e1f527be.png

Untuk mengonfirmasi bahwa gambar berhasil diburamkan, Anda 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 cupcake tersebut telah diburamkan:

e1f61035d680ba03.png

5. Menambahkan input dan output

Pemburaman aset gambar pada direktori resource telah dilakukan dengan baik, tetapi agar Blur-O-Matic benar-benar menjadi aplikasi pengeditan gambar yang revolusioner seperti yang diharapkan, Anda harus mengizinkan pengguna memburamkan gambar yang mereka lihat di layar, lalu dapat untuk menampilkan hasil yang diburamkan.

Untuk melakukannya, kita akan memberikan URI gambar cupcake yang ditampilkan sebagai input ke WorkRequest yang ditampilkan, lalu menggunakan output WorkRequest kami untuk menampilkan gambar akhir yang diburamkan.

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.

Di BlurViewModel, buat metode pribadi bernama createInputDataForUri. Metode ini harus:

  1. Membuat objek Data.Builder. Mengimpor androidx.work.Data bila diminta.
  2. Jika imageUri adalah 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 di BlurViewModel sehingga metode tersebut:

  1. Membuat OneTimeWorkRequestBuilder baru.
  2. Memanggil setInputData, meneruskan hasil dari createInputDataForUri.
  3. Mem-build OneTimeWorkRequest.
  4. Mengantrekan permintaan pekerjaan menggunakan permintaan WorkManager agar pekerjaan akan dapat dijadwalkan untuk dijalankan.

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, sekarang mari kita buramkan gambar cupcake di layar.

  1. Hapus kode sebelumnya yang mendapatkan resource gambar.

val picture = BitmapFactory.decodeResource(appContext.resources, R.drawable.android_cupcake)

  1. Pastikan resourceUri yang diperoleh dari Data yang diteruskan tidak kosong.
  2. Tetapkan variabel picture untuk menjadi gambar yang diteruskan seperti berikut:

val picture = BitmapFactory.decodeStream(

appContext.contentResolver.

  `openInputStream(Uri.parse(resourceUri)))`

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.android_cupcake)

        if (TextUtils.isEmpty(resourceUri)) {
            Log.e(TAG, "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) {
        Log.e(TAG, "Error applying blur")
        throwable.printStackTrace()
        Result.failure()
    }
}

Langkah 5 - Buat output URI sementara

Sekarang Anda sudah selesai dengan Pekerja ini dan dapat menampilkan URI output di Result.success(). Berikan URI Output 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 Anda membuat Rantai pekerja. Untuk melakukannya:

  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 harus menjalankan aplikasi. Aplikasi harus mengompilasi dan memiliki perilaku yang sama tempat Anda dapat melihat gambar yang diburamkan melalui Device File Explorer, tetapi belum di layar.

Untuk memeriksa gambar buram lainnya, Anda dapat membuka Device File Explorer di Android Studio dan membuka data/data/com.example.background/files/blur_filter_outputs/<URI> seperti yang Anda lakukan di langkah terakhir.

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

7e717ffd6b3d9d52.png

Bagus sekali! Anda berhasil memburamkan gambar input menggunakan WorkManager.

6. Membuat rantai Pekerjaan Anda

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 workers yang memperluas Worker.

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

Langkah 2 - Perluas Pekerja

Perluas class CleanupWorker dari class Worker. Tambahkan parameter konstruktor yang diperlukan.

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 manipulasi file berada di luar cakupan codelab ini, Anda dapat menyalin kode untuk CleanupWorker di bawah ini:

CleanupWorker.kt

package com.example.background.workers

import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.example.background.OUTPUT_PATH
import java.io.File

/**
 * Cleans up temporary files generated during blurring process
 */
private const val TAG = "CleanupWorker"
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()
                            Log.i(TAG, "Deleted $name - $deleted")
                        }
                    }
                }
            }
            Result.success()
        } catch (exception: Exception) {
            exception.printStackTrace()
            Result.failure()
        }
    }
}

Langkah 4 - Ganti dan implementasikan doWork() untuk SaveImageToFileWorker

SaveImageToFileWorker akan mengambil input dan output. Inputnya adalah String dari URI gambar yang diburamkan sementara yang disimpan dengan kunci KEY_IMAGE_URI. Dan outputnya juga String, URI gambar buram yang disimpan dengan kunci KEY_IMAGE_URI.

4fc29ac70fbecf85.png

Karena codelab ini belum membahas manipulasi file, kodenya diberikan di bawah ini. Perhatikan cara nilai resourceUri dan output diambil dengan kunci KEY_IMAGE_URI. 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 android.util.Log
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

/**
 * Saves the image to a permanent file
 */
private const val TAG = "SaveImageToFileWorker"
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 {
                Log.e(TAG, "Writing to MediaStore failed")
                Result.failure()
            }
        } catch (exception: Exception) {
            exception.printStackTrace()
            Result.failure()
        }
    }
}

Langkah 5 - Ubah Notifikasi BlurWorker

Sekarang setelah rantai Worker menangani penyimpanan gambar dalam folder yang benar, kita dapat memperlambat pekerjaan dengan menggunakan metode sleep() yang ditentukan di class WorkerUtils, 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) {
        throwable.printStackTrace()
        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:

bf3b82eb9fd22349.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.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 sekarang dapat menekan tombol Go dan melihat notifikasi saat pekerja yang berbeda dieksekusi. Anda masih dapat melihat gambar yang diburamkan di Device File Explorer, dan di langkah selanjutnya Anda akan menambahkan tombol tambahan agar pengguna dapat melihat gambar yang diburamkan di perangkat.

Dalam screenshot di bawah, Anda akan melihat bahwa pesan notifikasi menampilkan pekerja mana yang sedang berjalan.

f0bbaf643c24488f.png 42a036f4b24adddb.png

a438421064c385d4.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.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()
}

Buka Device File Explorer untuk melihat gambar yang diburamkan. Perhatikan bahwa folder output berisi beberapa gambar yang diburamkan, gambar yang berada di tengah tahap pemburaman, dan gambar terakhir yang menampilkan gambar yang diburamkan berdasarkan jumlah pemburaman yang dipilih.

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

7. Memastikan pekerjaan unik

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.

8. Memberi tag dan menampilkan status Pekerjaan

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 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. Di BlurViewModel, deklarasikan variabel class 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>>

// Modify the existing init block in the BlurViewModel class to this:
init {
    imageUri = getImageUri(application.applicationContext)
    // 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 tombol Go dan menampilkan tombol Cancel Work serta status progres.
  5. Jika sudah selesai, panggil showWorkFinished() yang menyembunyikan tombol Cancel Work dan status progres serta menampilkan tombol Go.

Berikut kodenya:

Catatan: Impor androidx.lifecycle.Observer bila diminta.

BlurActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    // Observe work status, added in onCreate()
    viewModel.outputWorkInfos.observe(this, workInfosObserver())
}

// Define the observer function
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:

7b70288f69050f0b.png

9. Menampilkan output akhir

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

Di BlurActivity, di dalam onCreate(), 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

override fun onCreate(savedInstanceState: Bundle?) {
   // 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)
                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:

5366222d0b4fb705.png

cd1ecc8b4ca86748.png

10. Membatalkan pekerjaan

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

Di BlurViewModel, tambahkan metode baru bernama cancelWork() untuk membatalkan pekerjaan unik. Di dalam panggilan fungsi cancelUniqueWork di workManager, teruskan tag IMAGE_MANIPULATION_WORK_NAME.

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.

dcb4ccfd261957b1.png

Perlu diperhatikan bahwa sekarang hanya ada tombol GO setelah pekerjaan dibatalkan karena WorkState tidak lagi dalam status FINISHED.

11. Batasan pekerjaan

Terakhir, WorkManager mendukung Constraints. Untuk Blur-O-Matic, Anda akan menggunakan batasan sehingga perangkat harus mengisi daya. Ini berarti permintaan kerja hanya akan dijalankan jika perangkat sedang diisi daya.

Langkah 1 - Buat dan tambahkan batasan pengisian daya

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

Impor androidx.work.Constraints bila diminta.

BlurViewModel.kt

// Put this inside the applyBlur() function, above the save work request.
// 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:

406ce044ca07169f.png

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

302da5ec986ae769.png

12. Selamat

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

  • Menambahkan WorkManager ke Project Anda
  • Menjadwalkan OneTimeWorkRequest
  • 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:

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.