トレンドの追跡、またはRxJavaとLiveDataへの移行





庭で2018年。 単語RxJavaおよびLiveDataがますます発見されています。 しかし、もしあなたのアプリケーションがandroid-priority-jobqueueライブラリやAsyncTaskのような昔ながらのソリューションによって支配されているのであれば(そうです)、この記事は特にあなたのためです。 私はこれらのアプローチを彼らの哲学に基づいて共有しています。 1つ目は、ディスプレイへの作業の特定の依存関係、2つ目は、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



このアプローチでは、タスクごとに独自の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()で実行されるObservableを作成し、さらなるサブスクリプションのためにそれを返します。



 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()を使用して、ボタンクリックをリッスンできる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を使用することをお勧めします。これにより、コード実行をバックグラウンドスレッドに簡単に転送できます。



また、操作のステータス、エラー、および結果に関する情報を含む補助リソースラッパークラスも必要になります。



 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の共生は最も実行可能だと思います。最初のものはデータストリームを受信して​​処理し、2番目の変化を通知します。



したがって、バックグラウンドスレッドで作業を行う最も一般的な2つの方法について、古風なライブラリからより現代的なライブラリへの移行を検討しました。 一度だけのマイナー操作では、裸のRxJavaが最適です。これは、データを非常に柔軟に処理し、これが発生するストリームを制御できるためです。 同時に、ライフサイクルとのより細かいやり取りが必要な場合は、そのような問題を解決するために元々設計されたLiveDataを使用することをお勧めします。



ソースの完全バージョンは、 GitHubにあります。



コメントであなたの質問に答えてうれしいです!



All Articles