
Dans la cour 2018 année. De plus en plus, les mots RxJava et LiveData sont trouvés. Mais s'il se trouve que votre application est toujours régie par des solutions à l'ancienne comme la bibliothèque android-priority-jobqueue ou AsyncTask (oui, cela arrive), cet article est spécialement pour vous. Je partage ces approches en fonction de leur philosophie. La première implique une certaine dépendance du travail sur l'écran, la seconde - l'exécution d'une tâche dans laquelle View l'écoute et elle ne s'interrompt pas en fonction des événements du cycle de vie (par exemple, lorsque l'écran est tourné). Sous la coupe, je suggère d'envisager la migration vers un tas de RxJava et LiveData pour les deux approches.
Pour illustrer, j'utiliserai la petite classe Work, qui est une opération longue qui doit être déplacée vers le thread d'arrière-plan. Dans une application de test, j'ai placé un ProgressBar tournant sur l'écran pour voir quand le travail serait effectué dans le thread principal.
class Work { fun doWork() = try { for (i in 0 until 10) { Thread.sleep(500) } "work is done" } catch (e: InterruptedException) { "work is cancelled" } }
AsyncTask
Avec cette approche, chaque AsyncTask est créée pour chaque tâche, qui est annulée dans onPause () ou onStop (). Cela est fait pour que le contexte d'activité ne fuit pas. Pour montrer ce que cela signifie, j'ai esquissé un petit exemple.
Tout d'abord, nous modifions un peu la tâche AsyncTask standard afin qu'elle puisse être annulée et en retournons une erreur:
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 } } }
Ajoutez le lancement du travail au manager:
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() } } }
Nous commençons la tâche, en annulant précédemment celle en cours, s'il y en a une:
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) } }) } ... }
N'oubliez pas de l'annuler en onPause ():
override fun onPause() { asyncTaskCancellable?.cancel() super.onPause() }
Ici, AsyncTask s'arrête et le rappel est réinitialisé pour effacer le lien vers MainActivity. Cette approche est applicable lorsque vous devez effectuer une tâche rapide et insignifiante, dont le résultat n'est pas effrayant à perdre (par exemple, lorsque vous retournez l'écran lorsque l'activité est recréée).
Sur RxJava, une implémentation similaire ne différera pas beaucoup.
Nous créons également un observable, qui sera exécuté sur
Schedulers.computation () , et le renvoyons pour un abonnement supplémentaire.
class WorkManager { ... fun doWorkInRxJava(): Observable<String> { return Observable.fromCallable { Work().doWork() }.subscribeOn(Schedulers.computation()) } ... }
Nous envoyons des rappels au flux principal et nous nous abonnons pour travailler:
class MainActivity : AppCompatActivity() { ... loadWithRx.setOnClickListener { _ -> rxJavaSubscription?.dispose() rxJavaSubscription = workManager.doWorkInRxJava() .observeOn(AndroidSchedulers.mainThread()) .subscribe({ onSuccess(it) }, { onError(it) }) } ... }
N'oubliez pas de nettoyer par vous-même dans onPause ():
override fun onPause() { rxJavaSubscription?.dispose() super.onPause() }
En général, l'implémentation avec RxJava peut être légèrement complétée à l'aide de la bibliothèque RxBinding. Il fournit des liaisons réactives pour les composants Android. En particulier, dans ce cas, vous pouvez utiliser
RxView.clicks () pour obtenir un observable qui vous permet d'écouter les clics sur les boutons:
class MainActivity : AppCompatActivity() { ... rxJavaSubscription = RxView.clicks(loadWithRx) .concatMap { workManager.doWorkInRxJava() .observeOn(AndroidSchedulers.mainThread()) .doOnNext { result -> onSuccess(result) } .onErrorReturn { error -> onError(error) "" } } .subscribe() ... }
La gestion des erreurs se produit dans l'instruction onErrorReturn afin de ne pas interrompre le flux des événements de clic sur un bouton. Ainsi, si une erreur se produit lors de l'exécution du travail, il n'atteindra pas le dernier abonnement et les clics continueront à être traités.
Lors de la mise en œuvre de cette approche, il faut se rappeler que le stockage de Disposable, qui renvoie subscribe (), en statique doit être abordé avec prudence. Jusqu'à ce que la méthode dispose () soit appelée, elle peut stocker des liens implicites vers vos abonnés, ce qui peut entraîner des fuites de mémoire.
Vous devez également faire attention à la gestion des erreurs afin de ne pas terminer accidentellement le flux d'origine.
android-priority-jobqueue
Ici, nous avons un certain gestionnaire qui gère les opérations, et l'écran souscrit à son état actuel. LiveData, qui est lié au cycle de vie, est excellent pour le rôle d'une couche entre un tel gestionnaire et l'interface utilisateur. Pour effectuer le travail directement dans cet exemple, je suggère d'utiliser RxJava, ce qui facilite le transfert de l'exécution du code vers le thread d'arrière-plan.
Nous aurons également besoin de la classe d'habillage de ressources auxiliaire, qui contiendra des informations sur l'état de l'opération, l'erreur et le résultat.
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 } }
Nous sommes maintenant prêts à écrire une classe WorkViewModel qui contiendra l'instance LiveData et à lui notifier les modifications de l'état du travail à l'aide de Resource. Dans l'exemple, j'ai un peu triché et je viens de faire de WorkViewModel un singleton. J'utilise RxJava en statique, mais je m'y abonnerai via LiveData, donc il n'y aura pas de fuite.
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 }
Nous complétons le WorkManager en lançant le travail pour maintenir l'uniformité, c'est-à-dire pour que le travail commence toujours par ce gestionnaire:
class WorkManager { ... fun doOnLiveData() { WorkViewModel.instance.startWork(Work()) } ... }
Et nous ajoutons une interaction avec tout cela dans MainActivity. De WorkViewModel nous obtenons l'état de l'œuvre en cours, tandis que l'affichage est vivant, et nous commençons une nouvelle œuvre en cliquant sur le bouton:
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() } ... }
De la même manière, cela peut être implémenté à l'aide d'un objet de RxJava. Cependant, à mon avis, LiveData gère mieux le traitement du cycle de vie, car il a été réglé à l'origine pour cela, tandis qu'avec Subject, vous pouvez rencontrer une tonne de problèmes avec l'arrêt du flux et la gestion des erreurs. Je pense que la symbiose de RxJava et LiveData est la plus viable: le premier reçoit et traite les flux de données et notifie les changements du second, auquel vous pouvez déjà vous abonner en gardant un œil sur le cycle de vie.
Ainsi, nous avons examiné la transition des bibliothèques archaïques vers les plus modernes pour les deux méthodes les plus courantes de travail dans le thread d'arrière-plan. Pour une opération mineure ponctuelle, RxJava nu est parfait, car il vous permet de travailler de manière très flexible avec les données et de contrôler les flux sur lesquels cela doit se produire. Dans le même temps, si une interaction plus fine avec le cycle de vie est nécessaire, il est préférable d'utiliser LiveData, qui a été initialement conçu pour résoudre un tel problème.
La version complète de la source peut être trouvée ici:
GitHubJe me ferai un plaisir de répondre à vos questions dans les commentaires!