
في الفناء 2018 العام. تم العثور على الكلمات RxJava و LiveData بشكل متزايد. ولكن إذا حدث ذلك ، فلا يزال تطبيقك محكومًا بحلول قديمة الطراز مثل مكتبات android-Priority-jobqueue أو مكتبات AsyncTask (نعم ، يحدث ذلك) ، فإن هذه المقالة مناسبة لك بشكل خاص. أشارك هذه الأساليب على أساس فلسفتهم. يتضمن الأول اعتمادًا معينًا على العمل على الشاشة ، والثاني - تنفيذ مهمة يستمع إليها العرض ولا يقاطع اعتمادًا على الأحداث في دورة الحياة (على سبيل المثال ، عندما يتم تدوير الشاشة). تحت هذا الخفض ، أقترح التفكير في الانتقال إلى مجموعة من RxJava و LiveData لكلا النهجين.
للتوضيح ، سأستخدم فئة العمل الصغيرة ، وهي بعض العمليات الطويلة التي يجب نقلها إلى مؤشر ترابط الخلفية. في تطبيق اختبار ، قمت بوضع شريط التقدم على الشاشة لمعرفة متى سيتم العمل في الخيط الرئيسي.
class Work { fun doWork() = try { for (i in 0 until 10) { Thread.sleep(500) } "work is done" } catch (e: InterruptedException) { "work is cancelled" } }
مهمة غير متزامنة
باستخدام هذا الأسلوب ، يتم إنشاء كل AsyncTask لكل مهمة ، والتي يتم إلغاؤها في onPause () أو onStop (). يتم ذلك بحيث لا يتسرب سياق النشاط. لإظهار المقصود بهذا ، قمت برسم مثال صغير.
أولاً ، نقوم بتعديل AsyncTask القياسي قليلاً حتى يمكن التراجع عنه وإرجاع خطأ منه:
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 } } }
أضف إطلاق العمل إلى المدير:
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() } } }
نبدأ المهمة ، بإلغاء المهمة الحالية مسبقًا ، إذا كانت هناك مهمة:
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) } }) } ... }
لا تنس أن تلغيه في onPause ():
override fun onPause() { asyncTaskCancellable?.cancel() super.onPause() }
هنا يتوقف AsyncTask ويتم إعادة تعيين رد الاتصال لمسح الارتباط بـ MainActivity. هذا النهج قابل للتطبيق عندما تحتاج إلى أداء مهمة سريعة وغير مهمة ، والنتيجة ليست مخيفة لنفقدها (على سبيل المثال ، عندما تقلب الشاشة عندما يتم إعادة إنشاء النشاط).
على RxJava ، لن يختلف التنفيذ المماثل كثيرًا.
نقوم أيضًا بإنشاء ملف قابل للرصد ، والذي سيتم تنفيذه على
Schedulers.computation () ، وإعادته لمزيد من الاشتراك.
class WorkManager { ... fun doWorkInRxJava(): Observable<String> { return Observable.fromCallable { Work().doWork() }.subscribeOn(Schedulers.computation()) } ... }
نرسل الاسترجاعات إلى التدفق الرئيسي والاشتراك في العمل:
class MainActivity : AppCompatActivity() { ... loadWithRx.setOnClickListener { _ -> rxJavaSubscription?.dispose() rxJavaSubscription = workManager.doWorkInRxJava() .observeOn(AndroidSchedulers.mainThread()) .subscribe({ onSuccess(it) }, { onError(it) }) } ... }
لا تنس تنظيف نفسك في onPause ():
override fun onPause() { rxJavaSubscription?.dispose() super.onPause() }
بشكل عام ، يمكن استكمال التنفيذ باستخدام RxJava قليلاً باستخدام مكتبة RxBinding. يوفر روابط تفاعلية لمكونات Android. على وجه الخصوص ، في هذه الحالة ، يمكنك استخدام
RxView.clicks () للحصول على ملحوظة تسمح لك بالاستماع إلى نقرات الأزرار:
class MainActivity : AppCompatActivity() { ... rxJavaSubscription = RxView.clicks(loadWithRx) .concatMap { workManager.doWorkInRxJava() .observeOn(AndroidSchedulers.mainThread()) .doOnNext { result -> onSuccess(result) } .onErrorReturn { error -> onError(error) "" } } .subscribe() ... }
تحدث معالجة الخطأ في عبارة onErrorReturn حتى لا تنهي تدفق أحداث النقر على زر. وبالتالي ، إذا حدث خطأ أثناء تنفيذ العمل ، فلن يصل إلى الاشتراك النهائي ، وستستمر معالجة النقرات.
عند تنفيذ هذا النهج ، يجب أن نتذكر أن تخزين Disposable ، الذي يعيد الاشتراك () ، في ثابت يجب تناوله بحذر. حتى يتم استدعاء طريقة التخلص () ، يمكنه تخزين الارتباطات الضمنية للمشتركين ، مما قد يؤدي إلى حدوث تسرب للذاكرة.
تحتاج أيضًا إلى توخي الحذر عند التعامل مع الأخطاء حتى لا تنهي البث الأصلي عن طريق الخطأ.
الروبوت - أولوية العمل -
هنا لدينا مدير معين يدير العمليات ، ويشترك العرض في حالته الحالية. LiveData ، المرتبطة بدورة الحياة ، ممتازة لدور الطبقة بين هذا المدير وواجهة المستخدم. للقيام بالعمل مباشرة في هذا المثال ، أقترح استخدام RxJava ، مما يجعل من السهل نقل تنفيذ التعليمات البرمجية إلى مؤشر ترابط الخلفية.
سنحتاج أيضًا إلى فئة برنامج تضمين الموارد المساعدة ، والتي ستحتوي على معلومات حول حالة العملية والخطأ والنتيجة.
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 } }
الآن نحن على استعداد لكتابة فئة WorkViewModel التي ستحتوي على مثيل LiveData وإخطارها بالتغييرات في حالة العمل باستخدام Resource. في المثال ، خدعت القليل وجعلت WorkViewModel مجرد أغنية فردية. أستخدم RxJava في الاستاتيكا ، لكني سأشترك فيها من خلال LiveData ، لذلك لن يكون هناك تسريبات.
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 }
نكمل WorkManager من خلال إطلاق العمل للحفاظ على التوحيد ، أي بحيث يبدأ هذا العمل دائمًا من خلال هذا المدير:
class WorkManager { ... fun doOnLiveData() { WorkViewModel.instance.startWork(Work()) } ... }
ونضيف التفاعل مع كل هذا في MainActivity. من WorkViewModel ، نحصل على حالة العمل الحالي ، بينما تكون الشاشة حية ، ونبدأ عملًا جديدًا بالنقر فوق الزر:
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() } ... }
بنفس الطريقة تقريبًا ، يمكن تنفيذ ذلك باستخدام موضوع من RxJava. ومع ذلك ، في رأيي ، فإن LiveData تتعامل مع معالجة دورة الحياة بشكل أفضل ، لأنه تم ضبطها في الأصل لهذا ، بينما مع الموضوع يمكنك مواجهة العديد من المشاكل مع إيقاف الدفق ومعالجة الأخطاء. أعتقد أن تكامل RxJava و LiveData هو الأكثر قابلية للتطبيق: حيث يتلقى الأول ويعالج تدفقات البيانات ويبلغ عن التغييرات الثانية ، والتي يمكنك الاشتراك فيها بالفعل مع التركيز على دورة الحياة.
وهكذا ، درسنا الانتقال من المكتبات القديمة إلى أكثر حداثة بالنسبة للطريقتين الأكثر شيوعًا للقيام بالعمل في موضوع الخلفية. بالنسبة لعملية بسيطة لمرة واحدة ، فإن RxJava العاري مثالي ، لأنه يسمح لك بالعمل بمرونة كبيرة مع البيانات والتحكم في التدفقات التي يجب أن يحدث فيها ذلك. في الوقت نفسه ، إذا كان التفاعل الأدق مع دورة الحياة مطلوبًا ، فمن الأفضل استخدام LiveData ، التي تم تصميمها في الأصل لحل هذه المشكلة.
يمكن العثور على النسخة الكاملة للمصدر هنا:
GitHubيسعدني أن أجيب على أسئلتكم في التعليقات!