
No quintal 2018 ano. Cada vez mais, as palavras RxJava e LiveData são encontradas. Mas se o seu aplicativo ainda é governado por soluções antiquadas, como a biblioteca android-priority-jobqueue ou AsyncTask (sim, acontece), este artigo é especialmente para você. Compartilho essas abordagens com base em sua filosofia. O primeiro envolve uma certa dependência do trabalho na tela, o segundo - a execução de uma tarefa na qual o View o ouve e não interrompe dependendo dos eventos do ciclo de vida (por exemplo, quando a tela é girada). De acordo com o corte, sugiro considerar a migração para um monte de RxJava e LiveData para ambas as abordagens.
Para ilustrar, usarei a pequena classe Work, que é uma operação demorada que precisa ser movida para o thread em segundo plano. Em um aplicativo de teste, coloquei uma ProgressBar giratória na tela para ver quando o trabalho seria feito no 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
Com essa abordagem, para cada tarefa, é criado seu próprio AsyncTask, que é cancelado em onPause () ou onStop (). Isso é feito para que o contexto da atividade não vaze. Para mostrar o que se entende por isso, esbocei um pequeno exemplo.
Primeiro, modificamos um pouco o AsyncTask padrão para que possa ser desfeito e retornamos um erro:
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 } } }
Adicione o lançamento do trabalho ao gerente:
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() } } }
Iniciamos a tarefa, cancelando anteriormente a atual, se houver uma:
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ão se esqueça de cancelá-lo na onPause ():
override fun onPause() { asyncTaskCancellable?.cancel() super.onPause() }
Aqui o AsyncTask para e o retorno de chamada é redefinido para limpar o link para MainActivity. Essa abordagem é aplicável quando você precisa executar uma tarefa rápida e insignificante, cujo resultado não é assustador de perder (por exemplo, quando você vira a tela quando a atividade será recriada).
No RxJava, uma implementação semelhante não será muito diferente.
Também criamos um Observable, que será executado em
Schedulers.computation () , e o devolvemos para assinatura adicional.
class WorkManager { ... fun doWorkInRxJava(): Observable<String> { return Observable.fromCallable { Work().doWork() }.subscribeOn(Schedulers.computation()) } ... }
Enviamos retornos de chamada para o fluxo principal e assinamos o trabalho:
class MainActivity : AppCompatActivity() { ... loadWithRx.setOnClickListener { _ -> rxJavaSubscription?.dispose() rxJavaSubscription = workManager.doWorkInRxJava() .observeOn(AndroidSchedulers.mainThread()) .subscribe({ onSuccess(it) }, { onError(it) }) } ... }
Não esqueça de se limpar na onPause ():
override fun onPause() { rxJavaSubscription?.dispose() super.onPause() }
Em geral, a implementação com o RxJava pode ser ligeiramente complementada usando a biblioteca RxBinding. Ele fornece ligações reativas para componentes do Android. Em particular, nesse caso, você pode usar
RxView.clicks () para obter um Observable que permita ouvir cliques no botão:
class MainActivity : AppCompatActivity() { ... rxJavaSubscription = RxView.clicks(loadWithRx) .concatMap { workManager.doWorkInRxJava() .observeOn(AndroidSchedulers.mainThread()) .doOnNext { result -> onSuccess(result) } .onErrorReturn { error -> onError(error) "" } } .subscribe() ... }
O tratamento de erros ocorre na instrução onErrorReturn para não encerrar o fluxo de eventos de clique em um botão. Portanto, se ocorrer um erro durante a execução do trabalho, ele não alcançará a assinatura final e os cliques continuarão sendo processados.
Ao implementar essa abordagem, deve-se lembrar que o armazenamento de Disposable, que retorna subscribe (), em estática, deve ser abordado com cautela. Até que o método dispose () seja chamado, ele pode armazenar links implícitos para seus assinantes, o que pode levar a vazamentos de memória.
Você também precisa ter cuidado com o tratamento de erros para não terminar acidentalmente o fluxo original.
android-priority-jobqueue
Aqui temos um certo gerente que gerencia as operações, e a tela assina seu status atual. O LiveData, vinculado ao ciclo de vida, é excelente para o papel de uma camada entre esse gerente e a interface do usuário. Para fazer o trabalho diretamente neste exemplo, sugiro usar o RxJava, o que facilita a transferência de execução de código para o thread em segundo plano.
Também precisaremos da classe auxiliar Wrapper Resource, que conterá informações sobre o status da operação, erro e resultado.
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 } }
Agora, estamos prontos para escrever uma classe WorkViewModel que conterá a instância do LiveData e notificaremos sobre alterações no status do trabalho usando o Resource. No exemplo, trapacei um pouco e apenas tornei o WorkViewModel um singleton. Eu uso o RxJava em estática, mas vou assiná-lo através do LiveData, para que não haja vazamentos.
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 }
Complementamos o WorkManager lançando trabalho para manter a uniformidade, ou seja, para que o trabalho sempre comece por esse gerente:
class WorkManager { ... fun doOnLiveData() { WorkViewModel.instance.startWork(Work()) } ... }
E adicionamos interação com tudo isso em MainActivity. No WorkViewModel, obtemos o status do trabalho atual, enquanto a exibição é animada, e iniciamos um novo trabalho clicando no botão:
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() } ... }
Da mesma maneira, isso pode ser implementado usando um Assunto do RxJava. No entanto, na minha opinião, o LiveData lida melhor com o processamento do ciclo de vida, porque foi originalmente ajustado para isso, enquanto no Subject você pode ter muitos problemas com a interrupção do fluxo e a manipulação de erros. Eu acho que a simbiose de RxJava e LiveData é a mais viável: a primeira recebe e processa os fluxos de dados e notifica sobre as alterações da segunda, nas quais você já pode se inscrever com um olho no ciclo de vida.
Assim, examinamos a transição das bibliotecas arcaicas para as mais modernas, para os dois métodos mais comuns de trabalho no segmento de segundo plano. Para uma operação secundária única, o RxJava nu é perfeito, pois permite trabalhar de maneira muito flexível com os dados e controlar os fluxos nos quais isso deve ocorrer. Ao mesmo tempo, se for necessária uma interação mais precisa com o ciclo de vida, é melhor usar o LiveData, que foi originalmente projetado para resolver esse problema.
A versão completa da fonte pode ser encontrada aqui:
GitHubFicarei feliz em responder suas perguntas nos comentários!