Den Trends folgen oder sich in Richtung RxJava und LiveData bewegen



Im Hof ​​2018 Jahr. Zunehmend werden die Wörter RxJava und LiveData gefunden. Wenn Ihre Anwendung jedoch immer noch von altmodischen Lösungen wie der Android-Priority-Jobqueue-Bibliothek oder AsyncTask beherrscht wird (ja, das passiert), ist dieser Artikel speziell für Sie. Ich teile diese Ansätze basierend auf ihrer Philosophie. Die erste beinhaltet eine gewisse Abhängigkeit der Arbeit von der Anzeige, die zweite - die Ausführung einer Aufgabe, bei der View darauf hört und die nicht abhängig von Ereignissen im Lebenszyklus (z. B. wenn der Bildschirm gedreht wird) unterbrochen wird. Unter dem Strich schlage ich vor, die Migration auf eine Reihe von RxJava und LiveData für beide Ansätze in Betracht zu ziehen.

Zur Veranschaulichung werde ich die kleine Work-Klasse verwenden, eine langwierige Operation, die in den Hintergrund-Thread verschoben werden muss. In einer Testanwendung habe ich eine sich drehende ProgressBar auf dem Bildschirm platziert, um zu sehen, wann im Haupt-Thread gearbeitet wird.

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

AsyncTask


Bei diesem Ansatz wird jede AsyncTask für jede Aufgabe erstellt, die in onPause () oder onStop () abgebrochen wird. Dies geschieht, damit der Aktivitätskontext nicht ausläuft. Um zu zeigen, was damit gemeint ist, habe ich ein kleines Beispiel skizziert.

Zuerst ändern wir die Standard-AsyncTask ein wenig, damit sie rückgängig gemacht werden kann, und geben einen Fehler zurück:

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

Fügen Sie dem Manager den Start der Arbeit hinzu:

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

Wir starten die Aufgabe und brechen zuvor die aktuelle ab, falls es eine gibt:

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

Vergessen Sie nicht, es in onPause () abzubrechen:

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

Hier stoppt AsyncTask und der Rückruf wird zurückgesetzt, um die Verbindung zu MainActivity zu löschen. Dieser Ansatz ist anwendbar, wenn Sie eine schnelle und unbedeutende Aufgabe ausführen müssen, deren Ergebnis nicht unheimlich zu verlieren ist (z. B. wenn Sie den Bildschirm umdrehen, wenn die Aktivität neu erstellt wird).

Unter RxJava unterscheidet sich eine ähnliche Implementierung nicht wesentlich.

Wir erstellen auch ein Observable, das auf Schedulers.computation () ausgeführt wird , und geben es zur weiteren Subskription zurück.

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

Wir senden Rückrufe an den Hauptstrom und abonnieren die Arbeit:

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

Vergessen Sie nicht, in onPause () selbst aufzuräumen:

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

Im Allgemeinen kann die Implementierung mit RxJava mithilfe der RxBinding-Bibliothek geringfügig ergänzt werden. Es bietet reaktive Bindungen für Android-Komponenten. In diesem Fall können Sie insbesondere RxView.clicks () verwenden , um ein Observable zu erhalten, mit dem Sie Schaltflächenklicks abhören können:

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

Die Fehlerbehandlung erfolgt in der Anweisung onErrorReturn, um den Fluss von Klickereignissen auf einer Schaltfläche nicht zu beenden. Wenn also während der Ausführung der Arbeit ein Fehler auftritt, erreicht er nicht das endgültige Abonnement und die Klicks werden weiter verarbeitet.
Bei der Implementierung dieses Ansatzes muss beachtet werden, dass der statische Speicher von Disposable, der subscribe () zurückgibt, mit Vorsicht angegangen werden sollte. Bis zum Aufrufen der dispose () -Methode können implizite Links zu Ihren Abonnenten gespeichert werden, was zu Speicherverlusten führen kann.
Sie müssen auch bei der Fehlerbehandlung vorsichtig sein, um den ursprünglichen Stream nicht versehentlich zu beenden.

android-priority-jobqueue


Hier haben wir einen bestimmten Manager, der die Operationen verwaltet, und die Anzeige abonniert seinen aktuellen Status. LiveData, das an den Lebenszyklus gebunden ist, eignet sich hervorragend für die Rolle einer Ebene zwischen einem solchen Manager und der Benutzeroberfläche. Um die Arbeit direkt in diesem Beispiel auszuführen, empfehle ich die Verwendung von RxJava, wodurch die Codeausführung einfach in den Hintergrundthread übertragen werden kann.

Wir benötigen auch die Hilfsressourcen-Wrapper-Klasse, die Informationen über den Status der Operation, den Fehler und das Ergebnis enthält.

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

Jetzt können wir eine WorkViewModel-Klasse schreiben, die die LiveData-Instanz enthält, und sie mithilfe von Resource über Änderungen im Status der Arbeit informieren. Im Beispiel habe ich ein wenig geschummelt und WorkViewModel zu einem Singleton gemacht. Ich verwende RxJava in der Statik, aber ich werde es über LiveData abonnieren, damit es keine Lecks gibt.

 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 } 

Wir ergänzen den WorkManager, indem wir Arbeiten starten, um die Einheitlichkeit aufrechtzuerhalten, d. H. Damit die Arbeit immer über diesen Manager beginnt:

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

Und wir fügen in MainActivity eine Interaktion mit all dem hinzu. Von WorkViewModel erhalten wir den Status der aktuellen Arbeit, während die Anzeige lebhaft ist, und wir starten eine neue Arbeit durch Drücken der Taste:

 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() } ... } 

In ungefähr der gleichen Weise kann dies unter Verwendung eines Betreffs von RxJava implementiert werden. Meiner Meinung nach handhabt LiveData die Lebenszyklusverarbeitung jedoch besser, da es ursprünglich darauf abgestimmt war, während bei Subject beim Stoppen des Streams und bei der Fehlerbehandlung viele Probleme auftreten können. Ich denke, dass die Symbiose von RxJava und LiveData am sinnvollsten ist: Die erste empfängt und verarbeitet Datenströme und benachrichtigt über die Änderungen der zweiten, die Sie bereits mit Blick auf den Lebenszyklus abonnieren können.

Daher untersuchten wir den Übergang von archaischen zu moderneren Bibliotheken für die beiden häufigsten Methoden, um im Hintergrund-Thread zu arbeiten. Für eine einmalige kleinere Operation ist nacktes RxJava perfekt, da Sie damit sehr flexibel mit Daten arbeiten und die Streams steuern können, in denen dies auftreten soll. Wenn eine feinere Interaktion mit dem Lebenszyklus erforderlich ist, ist es gleichzeitig besser, LiveData zu verwenden, das ursprünglich zur Lösung eines solchen Problems entwickelt wurde.

Die Vollversion der Quelle finden Sie hier: GitHub

Gerne beantworte ich Ihre Fragen in den Kommentaren!

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


All Articles