Pekerjaan Latar Belakang dengan WorkManager

1. Sebelum memulai

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.

Prasyarat

Yang akan Anda pelajari

Yang akan Anda lakukan

  • Memodifikasi aplikasi awal untuk menggunakan WorkManager.
  • Mengimplementasikan permintaan pekerjaan untuk memburamkan gambar.
  • Mengimplementasikan grup pekerjaan serial dengan membuat rantai pekerjaan.
  • Meneruskan data ke dan keluar dari pekerjaan yang dijadwalkan.

Yang akan Anda butuhkan

  • Versi stabil terbaru Android Studio
  • Koneksi internet

2. Ringkasan aplikasi

Saat ini, smartphone hampir terlalu sempurna dalam mengambil gambar. Tidak ada lagi hari-hari ketika seorang fotografer mengambil gambar yang cukup buram dari sesuatu yang misterius.

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

Layar memiliki tombol pilihan yang dapat Anda gunakan untuk memilih tingkat blur gambar. Mengklik tombol Start akan memberikan efek blur pada gambar dan menyimpan gambar.

Saat ini, aplikasi tidak menerapkan blur atau menyimpan gambar akhir.

Codelab ini berfokus pada menambahkan WorkManager ke aplikasi, membuat pekerja untuk membersihkan file sementara yang dibuat ketika memburamkan gambar, memburamkan gambar, dan menyimpan salinan akhir gambar yang dapat Anda lihat saat mengklik tombol See File. Anda juga akan mempelajari cara memantau status pekerjaan latar belakang dan mengupdate UI aplikasi sebagaimana mestinya.

3. Menjelajahi aplikasi awal Blur-O-Matic

Mendapatkan kode awal

Untuk memulai, download kode awal:

Atau, Anda dapat membuat clone repositori GitHub untuk kode tersebut:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git
$ cd basic-android-kotlin-compose-training-workmanager
$ git checkout starter

Anda dapat menjelajahi kode untuk aplikasi Blur-o-matic di repositori GitHub ini.

Menjalankan kode awal

Untuk memahami kode awal, selesaikan langkah-langkah berikut:

  1. Buka project dengan kode awal di Android Studio.
  2. Jalankan aplikasi di perangkat Android atau di emulator.

2bdb6fdc2567e96.png

Layar memiliki tombol pilihan yang memungkinkan Anda memilih tingkat blur gambar. Saat Anda mengklik tombol Start, aplikasi akan memberikan efek blur pada gambar dan menyimpan gambar.

Saat ini, aplikasi tidak menerapkan tingkat blur apa pun saat Anda mengklik tombol Start.

Panduan kode awal

Dalam tugas ini, Anda akan memahami struktur project. Daftar berikut memberikan panduan terkait file dan folder penting dalam project.

  • WorkerUtils: Metode praktis yang nantinya Anda gunakan untuk menampilkan Notifications dan kode untuk menyimpan bitmap ke file.
  • BlurViewModel: Model tampilan ini menyimpan status aplikasi dan berinteraksi dengan repositori.
  • WorkManagerBluromaticRepository: Class tempat Anda memulai pekerjaan latar belakang dengan WorkManager.
  • Constants: Class statis dengan beberapa konstanta yang Anda gunakan selama codelab.
  • BluromaticScreen: Berisi fungsi composable untuk UI dan berinteraksi dengan BlurViewModel. Fungsi composable menampilkan gambar dan menyertakan tombol pilihan untuk memilih tingkat blur yang diinginkan.

4. Apa yang dimaksud dengan WorkManager?

WorkManager adalah bagian dari Android Jetpack dan Komponen Arsitektur untuk tugas latar belakang yang memerlukan kombinasi eksekusi oportunistik dan terjamin. Eksekusi oportunistik berarti WorkManager 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. Beberapa manfaatnya antara lain:

  • Dukungan untuk tugas satu kali atau berkala asinkron.
  • Dukungan untuk batasan, seperti kondisi jaringan, ruang penyimpanan, dan status pengisian daya.
  • Perantaian permintaan pekerjaan yang kompleks, seperti 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 untuk menampilkan status permintaan tugas dengan mudah di UI aplikasi.

5. Kapan harus menggunakan WorkManager

Library WorkManager adalah pilihan tepat untuk tugas yang perlu Anda selesaikan. Menjalankan tugas ini tidak bergantung pada aplikasi yang terus berjalan setelah pekerjaan diantrekan. Tugas akan tetap berjalan meskipun aplikasi ditutup atau pengguna kembali ke layar utama.

Beberapa contoh tugas yang menggunakan WorkManager dengan baik:

  • Membuat kueri untuk berita terbaru secara berkala.
  • Menerapkan filter ke gambar, lalu menyimpan gambar.
  • Menyinkronkan data lokal dengan jaringan secara berkala.

WorkManager adalah salah satu opsi untuk menjalankan tugas dari thread utama, tetapi tidak dapat digunakan untuk menjalankan semua jenis tugas dari thread utama. Coroutine adalah opsi lain yang dibahas dalam codelab sebelumnya.

Untuk detail selengkapnya tentang kapan harus menggunakan WorkManager, lihat Panduan pekerjaan latar belakang.

6. Menambahkan WorkManager ke aplikasi Anda

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

app/build.gradle.kts

dependencies {
    // WorkManager dependency
    implementation("androidx.work:work-runtime-ktx:2.8.1")
}

Anda harus menggunakan versi rilis stabil work-runtime-ktx terbaru di aplikasi Anda.

Jika Anda mengubah versi, pastikan untuk mengklik Sync Now guna menyinkronkan project Anda dengan file gradle yang telah diupdate.

7. Dasar-dasar WorkManager

Ada beberapa class WorkManager yang perlu Anda ketahui:

  • Worker/CoroutineWorker: Worker adalah class yang melakukan pekerjaan secara sinkron di thread latar belakang. Karena kita tertarik dengan pekerjaan asinkron, kita dapat menggunakan CoroutineWorker, yang memiliki interoperabilitas dengan Coroutine Kotlin. Dalam aplikasi ini, Anda memperluas dari class CoroutineWorker dan mengganti metode doWork(). Metode ini adalah tempat Anda menempatkan kode untuk pekerjaan yang sebenarnya, yang ingin Anda lakukan di latar belakang.
  • WorkRequest: Class ini mewakili permintaan untuk melakukan beberapa pekerjaan. WorkRequest adalah tempat Anda menentukan apakah pekerja perlu dijalankan satu kali atau secara berkala. Batasan juga dapat ditempatkan di WorkRequest yang mengharuskan kondisi tertentu terpenuhi sebelum pekerjaan berjalan. Salah satu contohnya adalah perangkat sedang diisi daya sebelum memulai pekerjaan yang diminta. Anda meneruskan CoroutineWorker sebagai bagian dari pembuatan WorkRequest.
  • 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 menentukan class BlurWorker baru, yang berisi kode untuk memberikan efek blur pada gambar. Saat Anda mengklik tombol Start, WorkManager akan membuat dan mengantrekan objek WorkRequest.

8. Membuat BlurWorker

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 memburamkan gambar.

  1. Klik kanan paket com.example.bluromatic.workers di panel project Android Anda, lalu pilih New -> Kotlin Class/File.
  2. Beri nama class Kotlin baru BlurWorker. Perluas dari CoroutineWorker dengan parameter konstruktor yang diperlukan.

workers/BlurWorker.kt

import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import android.content.Context

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

Class BlurWorker memperluas class CoroutineWorker, bukan class Worker yang lebih umum. Implementasi class CoroutineWorker dari doWork() adalah fungsi penangguhan yang memungkinkannya menjalankan kode asinkron yang tidak dapat dilakukan Worker. Seperti dijelaskan dalam panduan Threading di WorkManager, "CoroutineWorker adalah penerapan yang direkomendasikan untuk pengguna Kotlin".

Pada tahap ini, Android Studio menggambar garis goyang merah di bawah class BlurWorker yang menunjukkan error.

9e96aa94f82c6990.png

Jika Anda menempatkan kursor di atas teks class BlurWorker, IDE akan menampilkan pop-up dengan informasi tambahan terkait error tersebut.

cdc4bbefa7a9912b.png

Pesan error menunjukkan bahwa Anda tidak mengganti metode doWork() sebagaimana diperlukan.

Pada metode doWork(), tulis kode untuk memberikan efek blur pada gambar cupcake yang ditampilkan.

Ikuti langkah-langkah berikut untuk memperbaiki error dan menerapkan metode doWork():

  1. Tempatkan kursor di dalam kode class dengan mengklik teks "BlurWorker".
  2. Dari menu Android Studio, pilih Code > Override Methods...
  3. Dari pop-up Override Members, pilih doWork().
  4. Klik OK.

8f495f0861ed19ff.png

  1. Tepat sebelum deklarasi class, buat variabel bernama TAG dan tetapkan nilai BlurWorker. Perhatikan bahwa variabel ini tidak secara khusus terkait dengan metode doWork(), tetapi Anda akan menggunakannya nanti dalam panggilan ke Log().

workers/BlurWorker.kt

private const val TAG = "BlurWorker"

class BlurWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
...
  1. Untuk melihat dengan lebih baik kapan tugas dieksekusi, Anda perlu menggunakan fungsi makeStatusNotification() WorkerUtil. Fungsi ini memungkinkan Anda menampilkan banner notifikasi dengan mudah di bagian atas layar.

Dalam metode doWork(), gunakan fungsi makeStatusNotification() untuk menampilkan notifikasi status dan memberi tahu pengguna bahwa worker blur telah mulai membuat gambar menjadi blur.

workers/BlurWorker.kt

import com.example.bluromatic.R
...
override suspend fun doWork(): Result {

    makeStatusNotification(
        applicationContext.resources.getString(R.string.blurring_image),
        applicationContext
    )
...
  1. Tambahkan blok kode return try...catch, tempat pekerjaan pemburaman gambar yang sebenarnya dilakukan.

workers/BlurWorker.kt

...
        makeStatusNotification(
            applicationContext.resources.getString(R.string.blurring_image),
            applicationContext
        )

        return try {
        } catch (throwable: Throwable) {
        }
...
  1. Di blok try, tambahkan panggilan ke Result.success().
  2. Di blok catch, tambahkan panggilan ke Result.failure().

workers/BlurWorker.kt

...
        makeStatusNotification(
            applicationContext.resources.getString(R.string.blurring_image),
            applicationContext
        )

        return try {
            Result.success()
        } catch (throwable: Throwable) {
            Result.failure()
        }
...
  1. Di blok try, buat variabel baru bernama picture, lalu isi dengan bitmap yang ditampilkan dari memanggil metode BitmapFactory.decodeResource() dan meneruskan paket resource aplikasi serta ID resource gambar cupcake.

workers/BlurWorker.kt

...
        return try {
            val picture = BitmapFactory.decodeResource(
                applicationContext.resources,
                R.drawable.android_cupcake
            )

            Result.success()
...
  1. Buramkan bitmap dengan memanggil fungsi blurBitmap(), lalu teruskan variabel picture dan nilai 1 (satu) untuk parameter blurLevel.
  2. Simpan hasilnya dalam variabel baru bernama output.

workers/BlurWorker.kt

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

            val output = blurBitmap(picture, 1)

            Result.success()
...
  1. Buat variabel baru outputUri dan isi dengan panggilan ke fungsi writeBitmapToFile().
  2. Pada panggilan ke writeBitmapToFile(), teruskan konteks aplikasi dan variabel output sebagai argumen.

workers/BlurWorker.kt

...
            val output = blurBitmap(picture, 1)

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

            Result.success()
...
  1. Tambahkan kode untuk menampilkan pesan notifikasi kepada pengguna yang berisi variabel outputUri.

workers/BlurWorker.kt

...
            val outputUri = writeBitmapToFile(applicationContext, output)

            makeStatusNotification(
                "Output is $outputUri",
                applicationContext
            )

            Result.success()
...
  1. Di blok catch, catat pesan error ke dalam log untuk menunjukkan adanya error saat mencoba memburamkan gambar. Panggilan ke Log.e() meneruskan variabel TAG yang telah ditentukan sebelumnya, pesan yang sesuai, dan pengecualian yang ditampilkan.

workers/BlurWorker.kt

...
        } catch (throwable: Throwable) {
            Log.e(
                TAG,
                applicationContext.resources.getString(R.string.error_applying_blur),
                throwable
            )
            Result.failure()
        }
...

CoroutineWorker, berjalan sebagai Dispatchers.Default secara default, tetapi dapat diubah dengan memanggil withContext() dan meneruskan dispatcher yang diinginkan.

  1. Buat blok withContext().
  2. Dalam panggilan ke withContext(), teruskan Dispatchers.IO sehingga fungsi lambda berjalan di kumpulan thread khusus untuk operasi IO yang berpotensi memblokir.
  3. Pindahkan kode return try...catch yang telah ditulis sebelumnya ke blok ini.
...
        return withContext(Dispatchers.IO) {

            return try {
                // ...
            } catch (throwable: Throwable) {
                // ...
            }
        }
...

Android Studio menampilkan error berikut karena Anda tidak dapat memanggil return dari dalam fungsi lambda.

2d81a484b1edfd1d.png

Error ini dapat diperbaiki dengan menambahkan label seperti yang ditunjukkan di pop-up.

...
            //return try {
            return@withContext try {
...

Karena Worker ini berjalan sangat cepat, sebaiknya tambahkan penundaan dalam kode untuk mengemulasikan tugas yang berjalan lebih lambat.

  1. Di dalam lambda withContext(), tambahkan panggilan ke fungsi utilitas delay() dan teruskan konstanta DELAY_TIME_MILLIS. Panggilan ini hanya untuk codelab guna memberikan penundaan antar-pesan notifikasi.
import com.example.bluromatic.DELAY_TIME_MILLIS
import kotlinx.coroutines.delay

...
        return withContext(Dispatchers.IO) {

            // This is an utility function added to emulate slower work.
            delay(DELAY_TIME_MILLIS)

                val picture = BitmapFactory.decodeResource(
...

9. Mengupdate WorkManagerBluromaticRepository

Repositori menangani semua interaksi dengan WorkManager. Struktur ini mematuhi prinsip desain pemisahan fokus dan merupakan pola arsitektur Android yang direkomendasikan.

  • Di file data/WorkManagerBluromaticRepository.kt, dalam class WorkManagerBluromaticRepository, buat variabel pribadi bernama workManager dan simpan instance WorkManager di dalamnya dengan memanggil WorkManager.getInstance(context).

data/WorkManagerBluromaticRepository.kt

import androidx.work.WorkManager
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {

    // New code
    private val workManager = WorkManager.getInstance(context)
...

Membuat dan mengantrekan WorkRequest di WorkManager

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

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

Anda hanya ingin gambar menjadi blur satu kali saat tombol Start diklik.

Tugas ini terjadi di metode applyBlur(), yang Anda panggil saat mengklik tombol Start.

Langkah-langkah berikut diselesaikan dalam metode applyBlur().

  1. Isi variabel baru bernama blurBuilder dengan membuat OneTimeWorkRequest untuk pekerja pemburaman dan memanggil fungsi ekstensi OneTimeWorkRequestBuilder dari WorkManager KTX.

data/WorkManagerBluromaticRepository.kt

import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
    // Create WorkRequest to blur the image
    val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
}
  1. Mulai pekerjaan dengan memanggil metode enqueue() pada objek workManager Anda.

data/WorkManagerBluromaticRepository.kt

import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
    // Create WorkRequest to blur the image
    val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()

    // Start the work
    workManager.enqueue(blurBuilder.build())
}
  1. Jalankan aplikasi dan lihat notifikasi saat Anda mengklik tombol Start.

Pada saat ini, gambar menjadi blur dalam tingkat yang sama, terlepas dari opsi yang Anda pilih. Pada langkah-langkah berikutnya, jumlah pemburaman akan berubah berdasarkan opsi yang dipilih.

f2b3591b86d1999d.png

Untuk mengonfirmasi bahwa gambar berhasil menjadi blur, Anda dapat membuka Device Explorer di Android Studio:

6bc555807e67f5ad.png

Lalu buka data > data > com.example.bluromatic > files > blur_filter_outputs > <URI> dan pastikan gambar cupcake telah blur:

fce43c920a61a2e3.png

10. Data input dan data output

Pemburaman aset gambar di 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 menampilkan hasil yang diburamkan.

Untuk melakukannya, berikan URI gambar cupcake yang ditampilkan sebagai input ke WorkRequest, lalu gunakan output WorkRequest kami untuk menampilkan gambar akhir yang diburamkan.

ce8ec44543479fe5.png

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

Pada langkah berikutnya, Anda akan meneruskan URI ke BlurWorker dengan membuat objek data input.

Membuat objek data input

  1. Di file data/WorkManagerBluromaticRepository.kt, di dalam class WorkManagerBluromaticRepository, buat variabel pribadi baru bernama imageUri.
  2. Isi variabel dengan URI gambar dengan memanggil metode konteks getImageUri().

data/WorkManagerBluromaticRepository.kt

import com.example.bluromatic.getImageUri
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {

    private var imageUri: Uri = context.getImageUri() // <- Add this
    private val workManager = WorkManager.getInstance(context)
...

Kode aplikasi berisi fungsi bantuan createInputDataForWorkRequest() untuk membuat objek data input.

data/WorkManagerBluromaticRepository.kt

// For reference - already exists in the app
private fun createInputDataForWorkRequest(blurLevel: Int, imageUri: Uri): Data {
    val builder = Data.Builder()
    builder.putString(KEY_IMAGE_URI, imageUri.toString()).putInt(BLUR_LEVEL, blurLevel)
    return builder.build()
}

Pertama, fungsi bantuan membuat objek Data.Builder. Selanjutnya, kode ini akan menempatkan imageUri dan blurLevel ke dalamnya sebagai key-value pair. Objek Data kemudian dibuat dan ditampilkan saat memanggil return builder.build().

  1. Guna menetapkan objek data input untuk WorkRequest, Anda perlu memanggil metode blurBuilder.setInputData(). Anda dapat membuat dan meneruskan objek data dalam satu langkah dengan memanggil fungsi bantuan createInputDataForWorkRequest() sebagai argumen. Untuk panggilan ke fungsi createInputDataForWorkRequest(), teruskan variabel blurLevel dan variabel imageUri.

data/WorkManagerBluromaticRepository.kt

override fun applyBlur(blurLevel: Int) {
     // Create WorkRequest to blur the image
    val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()

    // New code for input data object
    blurBuilder.setInputData(createInputDataForWorkRequest(blurLevel, imageUri))

    workManager.enqueue(blurBuilder.build())
}

Mengakses objek data input

Sekarang, mari kita update metode doWork() di class BlurWorker untuk mendapatkan URI dan tingkat blur yang diteruskan oleh objek data input. Jika nilai untuk blurLevel tidak diberikan, nilai tersebut akan ditetapkan secara default ke 1.

Dalam metode doWork():

  1. Buat variabel baru bernama resourceUri dan isi variabel dengan memanggil inputData.getString() dan meneruskan KEY_IMAGE_URI yang konstan, yang digunakan sebagai kunci saat membuat objek data input.

val resourceUri = inputData.getString(KEY_IMAGE_URI)

  1. Buat variabel baru bernama blurLevel. Isi variabel dengan memanggil inputData.getInt() dan meneruskan BLUR_LEVEL yang konstan, yang digunakan sebagai kunci saat membuat objek data input. Jika key-value pair ini belum dibuat, berikan nilai default 1 (satu).

workers/BlurWorker.kt

import com.example.bluromatic.KEY_BLUR_LEVEL
import com.example.bluromatic.KEY_IMAGE_URI
...
override fun doWork(): Result {

    // ADD THESE LINES
    val resourceUri = inputData.getString(KEY_IMAGE_URI)
    val blurLevel = inputData.getInt(KEY_BLUR_LEVEL, 1)

    // ... rest of doWork()
}

Dengan URI, sekarang mari kita buramkan gambar cupcake di layar.

  1. Pastikan variabel resourceUri telah diisi. Jika tidak terisi, kode Anda akan menampilkan pengecualian. Kode berikutnya menggunakan pernyataan require() yang menampilkan IllegalArgumentException jika argumen pertama bernilai false.

workers/BlurWorker.kt

return@withContext try {
    // NEW code
    require(!resourceUri.isNullOrBlank()) {
        val errorMessage =
            applicationContext.resources.getString(R.string.invalid_input_uri)
            Log.e(TAG, errorMessage)
            errorMessage
    }

Karena sumber gambar diteruskan sebagai URI, kita memerlukan objek ContentResolver untuk membaca konten yang ditunjuk oleh URI.

  1. Tambahkan objek contentResolver ke nilai applicationContext.

workers/BlurWorker.kt

...
    require(!resourceUri.isNullOrBlank()) {
        // ...
    }
    val resolver = applicationContext.contentResolver
...
  1. Karena sumber gambar sekarang diteruskan dalam URI, gunakan BitmapFactory.decodeStream(), bukan BitmapFactory.decodeResource(), untuk membuat objek Bitmap.

workers/BlurWorker.kt

import android.net.Uri
...
//     val picture = BitmapFactory.decodeResource(
//         applicationContext.resources,
//         R.drawable.android_cupcake
//     )

    val resolver = applicationContext.contentResolver

    val picture = BitmapFactory.decodeStream(
        resolver.openInputStream(Uri.parse(resourceUri))
    )
  1. Teruskan variabel blurLevel dalam panggilan ke fungsi blurBitmap().

workers/BlurWorker.kt

//val output = blurBitmap(picture, 1)
val output = blurBitmap(picture, blurLevel)

Membuat objek data output

Sekarang Anda sudah selesai dengan Pekerja ini dan dapat menampilkan URI output sebagai objek data output di Result.success(). Menyediakan URI output sebagai objek data output akan membuatnya mudah diakses oleh pekerja lain untuk operasi lebih lanjut. Pendekatan ini berguna di bagian berikutnya saat Anda membuat rantai pekerja.

Untuk melakukannya, selesaikan langkah-langkah berikut:

  1. Sebelum kode Result.success(), buat variabel baru bernama outputData.
  2. Isi variabel ini dengan memanggil fungsi workDataOf(), lalu gunakan KEY_IMAGE_URI konstan untuk kunci dan variabel outputUri sebagai nilai. Fungsi workDataOf() membuat objek Data dari pasangan kunci dan nilai yang diteruskan.

workers/BlurWorker.kt

import androidx.work.workDataOf
// ...
val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())
  1. Update kode Result.success() untuk mengambil objek Data baru ini sebagai argumen.

workers/BlurWorker.kt

//Result.success()
Result.success(outputData)
  1. Hapus kode yang menampilkan notifikasi karena tidak diperlukan lagi setelah objek Data output sekarang menggunakan URI.

workers/BlurWorker.kt

// REMOVE the following notification code
//makeStatusNotification(
//    "Output is $outputUri",
//    applicationContext
//)

Menjalankan aplikasi

Pada tahap ini, saat menjalankan aplikasi, Anda dapat mengharapkannya dikompilasi. Anda dapat melihat gambar yang menjadi blur melalui Device Explorer, tetapi belum di layar.

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

a658ad6e65f0ce5d.png

Bagus sekali! Anda berhasil memburamkan gambar input menggunakan WorkManager.

11. Membuat rantai Pekerjaan Anda

Saat ini, Anda sedang melakukan satu tugas pekerjaan: memburamkan gambar. Tugas ini adalah langkah pertama yang bagus, tetapi aplikasi masih belum memiliki beberapa fungsi inti:

  • Aplikasi tidak membersihkan file sementara.
  • Aplikasi tidak benar-benar menyimpan gambar ke file permanen.
  • Aplikasi selalu memburamkan gambar dalam jumlah yang sama.

Anda dapat menggunakan rantai pekerjaan WorkManager untuk menambahkan fungsi ini. WorkManager memungkinkan Anda membuat WorkerRequest terpisah yang berjalan secara berurutan atau paralel.

Di bagian ini, Anda akan membuat rantai pekerjaan yang terlihat seperti berikut:

c883bea5a5beac45.png

Kotak tersebut mewakili WorkRequest.

Fitur lain dari perantaian adalah kemampuannya untuk menerima input dan menghasilkan output. Output satu WorkRequest menjadi input WorkRequest berikutnya dalam rantai.

Anda sudah memiliki CoroutineWorker untuk memburamkan gambar, tetapi Anda juga memerlukan CoroutineWorker untuk membersihkan file sementara dan CoroutineWorker untuk menyimpan gambar secara permanen.

Membuat CleanupWorker

CleanupWorker menghapus file sementara, jika ada.

  1. Klik kanan paket com.example.bluromatic.workers di panel project Android Anda, lalu pilih New -> Kotlin Class/File.
  2. Beri nama class Kotlin baru CleanupWorker.
  3. Salin kode untuk CleanupWorker.kt, seperti ditunjukkan dalam contoh kode berikut.

Karena manipulasi file berada di luar cakupan codelab ini, Anda dapat menyalin kode berikut untuk CleanupWorker.

workers/CleanupWorker.kt

package com.example.bluromatic.workers

import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.OUTPUT_PATH
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.io.File

/**
 * Cleans up temporary files generated during blurring process
 */
private const val TAG = "CleanupWorker"

class CleanupWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {

    override suspend fun doWork(): Result {
        /** Makes a notification when the work starts and slows down the work so that it's easier
         * to see each WorkRequest start, even on emulated devices
         */
        makeStatusNotification(
            applicationContext.resources.getString(R.string.cleaning_up_files),
            applicationContext
        )

        return withContext(Dispatchers.IO) {
            delay(DELAY_TIME_MILLIS)

            return@withContext try {
                val outputDirectory = File(applicationContext.filesDir, OUTPUT_PATH)
                if (outputDirectory.exists()) {
                    val entries = outputDirectory.listFiles()
                    if (entries != null) {
                        for (entry in entries) {
                            val name = entry.name
                            if (name.isNotEmpty() && name.endsWith(".png")) {
                                val deleted = entry.delete()
                                Log.i(TAG, "Deleted $name - $deleted")
                            }
                        }
                    }
                }
                Result.success()
            } catch (exception: Exception) {
                Log.e(
                    TAG,
                    applicationContext.resources.getString(R.string.error_cleaning_file),
                    exception
                )
                Result.failure()
            }
        }
    }
}

Membuat SaveImageToFileWorker

Class SaveImageToFileWorker menyimpan file sementara ke file permanen.

SaveImageToFileWorker mengambil input dan output. Inputnya adalah String dari URI gambar yang diburamkan sementara, yang disimpan dengan KEY_IMAGE_URI kunci. Output-nya adalah String dari URI gambar yang diburamkan dan disimpan dengan kunci KEY_IMAGE_URI.

de0ee97cca135cf8.png

  1. Klik kanan paket com.example.bluromatic.workers di panel project Android Anda, lalu pilih New -> Kotlin Class/File.
  2. Beri nama class Kotlin baru SaveImageToFileWorker.
  3. Salin kode SaveImageToFileWorker.kt seperti ditunjukkan dalam kode contoh berikut.

Karena manipulasi file berada di luar cakupan codelab ini, Anda dapat menyalin kode berikut untuk SaveImageToFileWorker. Dalam kode yang diberikan, perhatikan bagaimana nilai resourceUri dan output diambil dan disimpan dengan kunci KEY_IMAGE_URI. Proses ini sangat mirip dengan kode yang Anda tulis sebelumnya untuk objek data input dan output.

workers/SaveImageToFileWorker.kt

package com.example.bluromatic.workers

import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.KEY_IMAGE_URI
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.Date

/**
 * Saves the image to a permanent file
 */
private const val TAG = "SaveImageToFileWorker"

class SaveImageToFileWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {

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

    override suspend fun doWork(): Result {
        // Makes a notification when the work starts and slows down the work so that
        // it's easier to see each WorkRequest start, even on emulated devices
        makeStatusNotification(
            applicationContext.resources.getString(R.string.saving_image),
            applicationContext
        )

        return withContext(Dispatchers.IO) {
            delay(DELAY_TIME_MILLIS)

            val resolver = applicationContext.contentResolver
            return@withContext try {
                val resourceUri = inputData.getString(KEY_IMAGE_URI)
                val bitmap = BitmapFactory.decodeStream(
                    resolver.openInputStream(Uri.parse(resourceUri))
                )
                val imageUrl = MediaStore.Images.Media.insertImage(
                    resolver, bitmap, title, dateFormatter.format(Date())
                )
                if (!imageUrl.isNullOrEmpty()) {
                    val output = workDataOf(KEY_IMAGE_URI to imageUrl)

                    Result.success(output)
                } else {
                    Log.e(
                        TAG,
                        applicationContext.resources.getString(R.string.writing_to_mediaStore_failed)
                    )
                    Result.failure()
                }
            } catch (exception: Exception) {
                Log.e(
                    TAG,
                    applicationContext.resources.getString(R.string.error_saving_image),
                    exception
                )
                Result.failure()
            }
        }
    }
}

Membuat rantai pekerjaan

Saat ini, kode ini hanya membuat dan menjalankan satu WorkRequest.

Dalam langkah ini, Anda akan memodifikasi kode untuk membuat dan mengeksekusi rantai WorkRequest, bukan hanya satu permintaan pemburaman gambar.

Dalam rantai WorkRequest, permintaan pekerjaan pertama Anda adalah membersihkan file sementara.

  1. Jangan panggil OneTimeWorkRequestBuilder, tetapi panggil workManager.beginWith().

Memanggil metode beginWith() akan menampilkan objek WorkContinuation dan membuat titik awal untuk rantai WorkRequest dengan permintaan pekerjaan pertama di rantai tersebut.

data/WorkManagerBluromaticRepository.kt

import androidx.work.OneTimeWorkRequest
import com.example.bluromatic.workers.CleanupWorker
// ...
    override fun applyBlur(blurLevel: Int) {
        // Add WorkRequest to Cleanup temporary images
        var continuation = workManager.beginWith(OneTimeWorkRequest.from(CleanupWorker::class.java))

        // Add WorkRequest to blur the image
        val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
...

Anda dapat menambahkan ke rantai permintaan pekerjaan ini dengan memanggil metode then() dan meneruskan objek WorkRequest.

  1. Hapus panggilan ke workManager.enqueue(blurBuilder.build()), yang hanya mengantrekan satu permintaan pekerjaan.
  2. Tambahkan permintaan pekerjaan berikutnya ke rantai dengan memanggil metode .then().

data/WorkManagerBluromaticRepository.kt

...
//workManager.enqueue(blurBuilder.build())

// Add the blur work request to the chain
continuation = continuation.then(blurBuilder.build())
...
  1. Buat permintaan pekerjaan untuk menyimpan gambar dan menambahkannya ke rantai.

data/WorkManagerBluromaticRepository.kt

import com.example.bluromatic.workers.SaveImageToFileWorker

...
continuation = continuation.then(blurBuilder.build())

// Add WorkRequest to save the image to the filesystem
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
    .build()
continuation = continuation.then(save)
...
  1. Untuk memulai pekerjaan, panggil metode enqueue() pada objek kelanjutan.

data/WorkManagerBluromaticRepository.kt

...
continuation = continuation.then(save)

// Start the work
continuation.enqueue()
...

Kode ini menghasilkan dan menjalankan rantai WorkRequest berikut: CleanupWorker WorkRequest, diikuti dengan BlurWorker WorkRequest, diikuti dengan SaveImageToFileWorker WorkRequest.

  1. Jalankan aplikasi.

Anda sekarang dapat mengklik Start dan melihat notifikasi saat worker yang berbeda dieksekusi. Anda masih dapat melihat gambar yang menjadi blur di Device Explorer, dan di bagian selanjutnya, Anda akan menambahkan tombol tambahan agar pengguna dapat melihat gambar yang menjadi blur di perangkat.

Dalam screenshot berikut, perhatikan bahwa pesan notifikasi menampilkan worker mana yang sedang berjalan.

bbe0fdd79e3bca27.png

5d43bbfff1bfebe5.png

da2d31fa3609a7b1.png

Perhatikan bahwa folder output berisi beberapa gambar yang menjadi blur—gambar yang berada di tengah tahap pemberian efek blur dan gambar akhir yang menampilkan gambar dengan tingkat blur yang Anda pilih.

Hebat! Sekarang, Anda dapat membersihkan file sementara, memburamkan gambar, dan menyimpannya.

12. Mendapatkan kode solusi

Untuk mendownload kode codelab yang sudah selesai, Anda dapat menggunakan perintah berikut:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git
$ cd basic-android-kotlin-compose-training-workmanager
$ git checkout intermediate

Atau, Anda dapat mendownload repositori sebagai file ZIP, lalu mengekstraknya, dan membukanya di Android Studio.

Jika Anda ingin melihat kode solusi untuk codelab ini, lihat kode tersebut di GitHub.

13. Kesimpulan

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

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 Menjadwalkan tugas dengan WorkManager.