顺应趋势,或转向RxJava和LiveData



在院子里的2018年。 越来越多地找到单词RxJava和LiveData。 但是,如果碰巧您的应用仍然受android-priority-jobqueue库或AsyncTask之类的老式解决方案支配(是的,它确实发生了),那么本文就特别适合您。 我根据他们的理念分享这些方法。 第一个涉及显示器上工作的某种依赖性,第二个涉及执行任务,在该任务中,View会监听它,并且视生命周期中的事件而定(例如,旋转屏幕时),它不会中断。 削减开支后,我建议考虑两种方法都迁移到一堆RxJava和LiveData。

为了说明这一点,我将使用小型Work类,该类是一些冗长的操作,需要将其移至后台线程。 在一个测试应用程序中,我在屏幕上放置了一个旋转的ProgressBar,以查看何时在主线程中完成工作。

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上,类似的实现不会有太大区别。

我们还创建了一个Observable,它将在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() } 

通常,使用RxBinding库可以稍微补充RxJava的实现。 它为Android组件提供反应性绑定。 特别是在这种情况下,您可以使用RxView.clicks()获得一个Observable,该Observable允许您监听按钮的单击:

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

错误处理发生在onErrorReturn语句中,以便不终止按钮上的单击事件流。 因此,如果在执行工作期间发生错误,则它将不会到达最终订阅,并且将继续处理点击。
在实施此方法时,必须记住,应谨慎处理以静态方式返回subscribe()的Disposable存储。 在调用dispose()方法之前,它可以存储到订阅服务器的隐式链接,这可能导致内存泄漏。
您还需要注意错误处理,以免意外完成原始流。

android-priority-jobqueue


在这里,我们有一个特定的经理来管理操作,显示内容订阅了他的当前状态。 与生命周期息息相关的LiveData非常适合此类管理器和UI之间的层角色。 为了直接在此示例中完成工作,我建议使用RxJava,它可以轻松地将代码执行转移到后台线程。

我们还需要辅助Resource包装器类,该类将包含有关操作状态,错误和结果的信息。

 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 } } 

现在,我们准备编写一个包含LiveData实例的WorkViewModel类,并使用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中的Subject来实现。 但是,我认为LiveData可以更好地处理生命周期,因为它最初是为此而调整的,而使用Subject时,在停止流和错误处理方面会遇到很多问题。 我认为RxJava和LiveData的共生是最可行的:第一个接收并处理数据流,并通知第二个数据流的变化,您已经可以关注生命周期了。

因此,我们研究了在后台线程中执行工作的两种最常见方法,即从旧式图书馆向更现代的过渡。 对于一次性的次要操作,裸露的RxJava是完美的,因为它使您可以非常灵活地处理数据并控制应在其上发生的流。 同时,如果需要与生命周期进行更好的交互,则最好使用LiveData,它最初是为解决此类问题而设计的。

完整版本的源代码可以在这里找到: GitHub

我很高兴在评论中回答您的问题!

Source: https://habr.com/ru/post/zh-CN426999/


All Articles