Mengikuti tren, atau bergerak menuju RxJava dan LiveData



Di halaman 2018 tahun. Semakin banyak kata-kata RxJava dan LiveData ditemukan. Tetapi jika itu terjadi bahwa aplikasi Anda masih dikuasai oleh solusi kuno seperti perpustakaan android-priority-jobqueue atau AsyncTask (ya, itu terjadi), maka artikel ini khusus untuk Anda. Saya berbagi pendekatan ini berdasarkan filosofi mereka. Yang pertama melibatkan ketergantungan tertentu dari pekerjaan pada tampilan, yang kedua - pelaksanaan tugas di mana View mendengarkannya dan itu tidak mengganggu tergantung pada peristiwa dalam siklus hidup (misalnya, ketika layar diputar). Di bawah potongan, saya sarankan mempertimbangkan migrasi ke sekelompok RxJava dan LiveData untuk kedua pendekatan.

Sebagai ilustrasi, saya akan menggunakan kelas Work kecil, yang merupakan beberapa operasi panjang yang perlu dipindahkan ke utas latar belakang. Dalam aplikasi pengujian, saya meletakkan ProgressBar yang berputar di layar untuk melihat kapan pekerjaan akan dilakukan di utas utama.

class Work { fun doWork() = try { for (i in 0 until 10) { Thread.sleep(500) } "work is done" } catch (e: InterruptedException) { "work is cancelled" } } 

AsyncTask


Dengan pendekatan ini, setiap AsyncTask dibuat untuk setiap tugas, yang dibatalkan di onPause () atau onStop (). Ini dilakukan agar konteks aktivitas tidak bocor. Untuk menunjukkan apa yang dimaksud dengan ini, saya membuat sketsa contoh kecil.

Pertama, kami memodifikasi AsyncTask standar sedikit sehingga dapat dibatalkan dan mengembalikan kesalahan dari itu:

 class AsyncTaskCancellable<Params, Result>( private val job: Job<Params, Result>, private var callback: AsyncTaskCallback<Result>?) : AsyncTask<Params, Void, AsyncTaskCancellable.ResultContainer<Result>>(), WorkManager.Cancellable { interface Job<Params, Result> { fun execute(params: Array<out Params>): Result } override fun doInBackground(vararg params: Params): AsyncTaskCancellable.ResultContainer<Result> { return try { ResultContainer(job.execute(params)) } catch (throwable: Throwable) { ResultContainer(throwable) } } override fun onPostExecute(result: AsyncTaskCancellable.ResultContainer<Result>) { super.onPostExecute(result) if (result.error != null) { callback?.onError(result.error!!) } else { callback?.onDone(result.result!!) } } override fun cancel() { cancel(true) callback = null } class ResultContainer<T> { var error: Throwable? = null var result: T? = null constructor(result: T) { this.result = result } constructor(error: Throwable) { this.error = error } } } 

Tambahkan peluncuran pekerjaan ke manajer:

 class WorkManager { fun doWorkInAsyncTask(asyncTaskCallback: AsyncTaskCallback<String>): Cancellable { return AsyncTaskCancellable(object : AsyncTaskCancellable.Job<Void, String> { override fun execute(params: Array<out Void>) = Work().doWork() }, asyncTaskCallback).apply { execute() } } } 

Kami memulai tugas, sebelumnya membatalkan yang sekarang, jika ada:

 class MainActivity : AppCompatActivity() { ... loadWithAsyncTask.setOnClickListener { asyncTaskCancellable?.cancel() asyncTaskCancellable = workManager.doWorkInAsyncTask(object : AsyncTaskCallback<String> { override fun onDone(result: String) { onSuccess(result) } override fun onError(throwable: Throwable) { this@MainActivity.onError(throwable) } }) } ... } 

Jangan lupa untuk membatalkannya di onPause ():

 override fun onPause() { asyncTaskCancellable?.cancel() super.onPause() } 

Di sini AsyncTask berhenti dan panggilan balik diatur ulang untuk menghapus tautan ke MainActivity. Pendekatan ini berlaku ketika Anda perlu melakukan tugas yang cepat dan tidak signifikan, yang hasilnya tidak menakutkan untuk hilang (misalnya, ketika Anda membalik layar saat aktivitas akan diciptakan kembali).

Pada RxJava, implementasi serupa tidak akan jauh berbeda.

Kami juga membuat Observable, yang akan dieksekusi di Schedulers.computation () , dan mengembalikannya untuk berlangganan lebih lanjut.

 class WorkManager { ... fun doWorkInRxJava(): Observable<String> { return Observable.fromCallable { Work().doWork() }.subscribeOn(Schedulers.computation()) } ... } 

Kami mengirim panggilan balik ke aliran utama dan berlangganan untuk bekerja:

 class MainActivity : AppCompatActivity() { ... loadWithRx.setOnClickListener { _ -> rxJavaSubscription?.dispose() rxJavaSubscription = workManager.doWorkInRxJava() .observeOn(AndroidSchedulers.mainThread()) .subscribe({ onSuccess(it) }, { onError(it) }) } ... } 

Jangan lupa untuk membersihkan sendiri di onPause ():

 override fun onPause() { rxJavaSubscription?.dispose() super.onPause() } 

Secara umum, implementasi dengan RxJava dapat sedikit ditambah menggunakan perpustakaan RxBinding. Ini memberikan binding reaktif untuk komponen Android. Secara khusus, dalam hal ini, Anda dapat menggunakan RxView.clicks () untuk mendapatkan Observable yang memungkinkan Anda mendengarkan klik tombol:

 class MainActivity : AppCompatActivity() { ... rxJavaSubscription = RxView.clicks(loadWithRx) .concatMap { workManager.doWorkInRxJava() .observeOn(AndroidSchedulers.mainThread()) .doOnNext { result -> onSuccess(result) } .onErrorReturn { error -> onError(error) "" } } .subscribe() ... } 

Penanganan kesalahan terjadi dalam pernyataan onErrorReturn agar tidak menghentikan aliran acara klik pada tombol. Jadi, jika kesalahan terjadi selama pelaksanaan pekerjaan, maka itu tidak akan mencapai berlangganan akhir, dan klik akan terus diproses.
Ketika menerapkan pendekatan ini, harus diingat bahwa penyimpanan Disposable, yang mengembalikan subscribe (), secara statis harus didekati dengan hati-hati. Sampai metode dispose () dipanggil, ia dapat menyimpan tautan implisit ke pelanggan Anda, yang dapat menyebabkan kebocoran memori.
Anda juga harus berhati-hati dengan penanganan kesalahan agar tidak secara tidak sengaja menyelesaikan aliran asli.

android-priority-jobqueue


Di sini kami memiliki manajer tertentu yang mengelola operasi, dan tampilan berlangganan statusnya saat ini. LiveData, yang terkait dengan siklus hidup, sangat baik untuk peran lapisan antara manajer dan UI tersebut. Untuk melakukan pekerjaan secara langsung dalam contoh ini, saya sarankan menggunakan RxJava, yang membuatnya mudah untuk mentransfer eksekusi kode ke utas latar belakang.

Kita juga akan memerlukan kelas wrapper Resource tambahan, yang akan berisi informasi tentang status operasi, kesalahan, dan hasil.

 class Resource<T> private constructor(val status: Status, val data: T?, val error: Throwable?) { constructor(data: T) : this(Status.SUCCESS, data, null) constructor(error: Throwable) : this(Status.ERROR, null, error) constructor() : this(Status.LOADING, null, null) enum class Status { SUCCESS, ERROR, LOADING } } 

Sekarang kita siap untuk menulis kelas WorkViewModel yang akan berisi instance LiveData dan memberitahukannya tentang perubahan status pekerjaan menggunakan Resource. Dalam contoh, saya sedikit curang dan baru saja membuat WorkViewModel sebuah singleton. Saya menggunakan RxJava dalam statika, tapi saya akan berlangganan melalui LiveData, jadi tidak akan ada kebocoran.

 class WorkViewModel private constructor() { companion object { val instance = WorkViewModel() } private val liveData: MutableLiveData<Resource<String>> = MutableLiveData() private var workSubscription: Disposable? = null fun startWork(work: Work) { liveData.value = Resource() workSubscription?.dispose() workSubscription = Observable.fromCallable { work.doWork() } .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ liveData.value = Resource(it) }, { liveData.value = Resource(it) }) } fun getWork(): LiveData<Resource<String>> = liveData } 

Kami melengkapi WorkManager dengan meluncurkan pekerjaan untuk menjaga keseragaman, mis. sehingga pekerjaan selalu dimulai melalui manajer ini:

 class WorkManager { ... fun doOnLiveData() { WorkViewModel.instance.startWork(Work()) } ... } 

Dan kami menambahkan interaksi dengan semua ini di MainActivity. Dari WorkViewModel, kami mendapatkan status pekerjaan saat ini, sementara tampilan hidup, dan kami memulai pekerjaan baru dengan menekan tombol:

 class MainActivity : AppCompatActivity() { ... WorkViewModel.instance.getWork().observe(this, Observer { when { it?.status == Resource.Status.SUCCESS -> onSuccess(it.data!!) it?.status == Resource.Status.ERROR -> onError(it.error!!) it?.status == Resource.Status.LOADING -> loadWithLiveData.isEnabled = false } }) loadWithLiveData.setOnClickListener { workManager.doOnLiveData() } ... } 

Dengan cara yang kira-kira sama, ini dapat diimplementasikan menggunakan Subjek dari RxJava. Namun, menurut saya, LiveData menangani pemrosesan siklus hidup dengan lebih baik, karena ini awalnya disetel untuk ini, sementara dengan Subjek Anda dapat mengalami banyak masalah dengan menghentikan aliran dan penanganan kesalahan. Saya berpikir bahwa simbiosis RxJava dan LiveData adalah yang paling layak: yang pertama menerima dan memproses aliran data dan memberi tahu tentang perubahan yang kedua, yang Anda sudah bisa berlangganan dengan memperhatikan siklus kehidupan.

Dengan demikian, kami memeriksa transisi dari perpustakaan kuno ke lebih modern untuk dua metode paling umum melakukan pekerjaan di latar belakang. Untuk operasi kecil satu kali, RxJava telanjang sempurna, karena memungkinkan Anda untuk bekerja dengan sangat fleksibel dengan data dan mengontrol aliran di mana ini harus terjadi. Pada saat yang sama, jika interaksi yang lebih baik dengan siklus hidup diperlukan, lebih baik menggunakan LiveData, yang awalnya dirancang untuk menyelesaikan masalah seperti itu.

Versi lengkap dari sumber dapat ditemukan di sini: GitHub

Saya akan dengan senang hati menjawab pertanyaan Anda di komentar!

Source: https://habr.com/ru/post/id426999/


All Articles