【问题标题】:How to replace AsyncTask and AsyncTaskLoader with their Rx equivalents?如何用 Rx 等效项替换 AsyncTask 和 AsyncTaskLoader?
【发布时间】:2018-04-03 17:21:32
【问题描述】:

背景

有些文章声称 Rx 可以替代 AsyncTask 和 AsyncTaskLoader。 看到 Rx 通常会使代码更短,我尝试深入研究各种示例和有关其工作原理的想法。

问题

我发现的所有示例和文章,包括 Github 存储库,似乎都不能很好地替代 AsyncTask 和 AsyncTaskLoader:

  1. 找不到如何取消一个或多个任务(包括中断线程)。这对于 AsyncTask 尤为重要,通常用于 RecyclerView 和 ListView。这也可以在 AsyncTaskLoader 上完成,但需要比仅仅调用“取消”更多的工作。对于 AsyncTask,它还提供进度的发布,这是我在 Rx 中找不到示例的另一件事。

  2. 找不到如何在避免内存泄漏的同时避免手动取消订阅,这完全破坏了使用 Rx 的全部意义,因为它应该更短。在某些示例中,回调的数量甚至比普通代码还要多。

我尝试过的

这是我读过的一些关于 Rx 的链接:

问题

如何使用 Rx 替代 AsyncTask 和 AsyncTaskLoader?

例如,我如何将这个微小的 AsyncTaskLoader 示例代码替换为 Rx 等效代码:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        supportLoaderManager.initLoader(1, Bundle.EMPTY, object : LoaderCallbacks<Int> {
            override fun onCreateLoader(id: Int, args: Bundle): Loader<Int> {
                Log.d("AppLog", "onCreateLoader")
                return MyLoader(this@MainActivity)
            }

            override fun onLoadFinished(loader: Loader<Int>, data: Int?) {
                Log.d("AppLog", "done:" + data!!)
            }

            override fun onLoaderReset(loader: Loader<Int>) {}
        })
    }

    private class MyLoader(context: Context) : AsyncTaskLoader<Int>(context) {

        override fun onStartLoading() {
            super.onStartLoading()
            forceLoad()
        }

        override fun loadInBackground(): Int {
            Log.d("AppLog", "loadInBackground")
            try {
                Thread.sleep(10000)
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }
            return 123
        }
    }
}

对于 AsyncTask,通常我们为每个 ViewHolder 设置它,作为一个字段,然后在 onBindViewHolder 以及活动/片段被销毁时取消它(检查所有仍处于挂起或运行的状态)。

类似这样的东西(样本尽可能短,当然应该根据需要进行更改):

class AsyncTaskActivity : AppCompatActivity() {
    internal val mTasks = HashSet<AsyncTask<Void, Int, Void>>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_async_task)
        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
        recyclerView.adapter = object : Adapter<MyViewHolder>() {
            override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
                return MyViewHolder(LayoutInflater.from(this@AsyncTaskActivity).inflate(android.R.layout.simple_list_item_1, parent, false))
            }

            @SuppressLint("StaticFieldLeak")
            override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
                holder.tv.text = "loading..."
                if (holder.task != null) {
                    holder.task!!.cancel(true)
                    mTasks.remove(holder.task!!)
                }
                holder.task = object : AsyncTask<Void, Int, Void>() {
                    override fun doInBackground(vararg voids: Void): Void? {
                        for (i in 0..100)
                            try {
                                Thread.sleep(5)
                                publishProgress(i)
                            } catch (e: InterruptedException) {
                                e.printStackTrace()
                            }

                        return null
                    }

                    override fun onProgressUpdate(vararg values: Int?) {
                        super.onProgressUpdate(*values)
                        holder.tv.text = "progress:" + values[0]
                    }

                    override fun onPostExecute(aVoid: Void?) {
                        super.onPostExecute(aVoid)
                        mTasks.remove(holder.task)
                        holder.tv.text = "done:" + position
                        holder.task = null
                    }
                }.execute()
            }

            override fun getItemCount(): Int {
                return 1000
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        for (task in mTasks)
            task.cancel(true)
    }

    private class MyViewHolder(itemView: View) : ViewHolder(itemView) {
        internal var tv: TextView
        internal var task: AsyncTask<Void, Int, Void>? = null

        init {
            tv = itemView.findViewById(android.R.id.text1)
        }
    }
}

【问题讨论】:

  • 关于问题 #1:在 RxJava2 中,对于每个订阅,您都会得到一个 Disposable 接口,该接口具有 dispose() 函数。我相信这相当于AsyncTaskcancel()
  • 关于问题#2:我通常将那些Disposables 的引用保存在集合中,dispose() 每个都保存在onDestroy() 中,或者onDestroyView() 中的片段中。如果这个评论没有意义,那么我显然不明白这个的前提。
  • @HendraAnggrian “处置”不仅仅忽略结果吗?它是否允许也中断线程?您能否发布一个与我所做的大致相同的示例代码? AsyncTask 还支持发布进度。 Rx 也支持吗?
  • 我对 RxJava 的体验只包含 3 种流类型:ObservableSingleCompletable。如果要发布进度,则需要Observable,它的onNext() 相当于AsyncTaskpublishProgress()。我会编写一个示例 RxJava+RxAndroid 代码来模仿您的 AsyncTask 代码,但我发现您的代码中没有 publishProgress()。在这种情况下,SingleCompletable 可能更可取,因为它们更简单。编辑:我目前在外面,将在一个小时左右发布代码示例。
  • 我可以轻松添加它。只是我现在想起来了,所以我也添加了它。我现在在问题中更新了 AsyncTask 示例的代码。

标签: android android-asynctask rx-android asynctaskloader


【解决方案1】:

让我提前告诉你,我在 RxJava 方面的经验并不像某些人所说的那样提前。我只知道使用 RxJava + RxAndroid 替换 AsyncTask。例如,让我们使用 Observable 将您的 AsyncTask 代码转换为 RxJava2 等价物,这可以说是最流行的流类型。

Observable
    .create <Int> { emitter ->
        try { 
            for (i in 0..100) {
                Thread.sleep(5) 
                emitter.onNext(i) 
            }
        } catch (e: InterruptedException) {
            emitter.onError(e)
        }
        emitter.onComplete()
    ‎}
    ‎.subscribeOn(Schedulers.io())
    ‎.observeOn(AndroidSchedulers.mainThread())
    ‎.subscribe({ i ->
    ‎    holder.tv.text = "progress:" + i
     }, { e ->
     ‎   e.printStackTrace()
     ‎}, {
     ‎   holder.tv.text = "done:" + position
     ‎})

现在让我们逐行浏览代码:

  1. create 允许您自定义发射器行为。请注意,这不是创建流的唯一方法,还有justfromArrayfromListdefer 等。如果您仍有兴趣,我们可以讨论更多。
  2. subscribeOn(Schedulers.io()) 告诉 RxJava #1 将在 I/O 线程中执行。如果这是后台操作,Schedulers.newThread() 也可以工作(至少据我所知)。
  3. observeOn(AndroidSchedulers.mainThread()) 告诉 RxJava #4 将在 Android 的主线程中执行。这也是 RxAndroid 填补空白的地方,因为 RxJava 没有任何 Android 参考。
  4. subscribe()
    • onNext(i : Int) - 相当于publishProgress()。在较新的 RxJava 中,发出的值不能为空。
    • onError(e : Throwable) - 长期以来被 RxJava 的程序员推崇的错误处理。在较新的 RxJava 中,必须提供 onError(),如果它为空则无关紧要。
    • onComplete() - 相当于onPostExecute()

同样,我的回答可能有很多错误信息。我希望有更好理解的人来纠正它或提供比这个更好的答案。

【讨论】:

  • 我是否在创建 AsyncTask 的新实例的同一位置创建它?我怎样才能让它停止它的任务?调用 dispose() ?我也可以这样中断线程吗?此外,在编写您编写的代码时,对于您编写的代码块的每个结尾,我都有这种类型的错误“期望一个元素”。怎么会?
  • 我会创建一个基础活动来收集这些 Disposables,然后在 onDestroy() 上将它们全部处理掉,类似于 this。关于该代码sn-p,我无法重现您提到的错误:imgur.com/a/ejzDt
  • 它有这个问题。这里:i.stack.imgur.com/ne9qq.png。我不知道它到底想要什么。无论如何,您还将如何处理 AsyncTaskLoader?和 AsyncTask 类似,但更多的是与 Activity 绑定,所以如果配置发生变化,新的 Activity 仍然可以到达它,知道它的状态和结果,包括在它完成时有一个回调。我认为解决方案可能类似于您对“rx-activity”库所做的。
  • 他们怎么能期待一个元素?所有 3 个(onNext、onError 和 onComplete)都有 void 返回类型。尝试添加Unit 作为它们每个的最后一行。
【解决方案2】:

似乎没有办法以更短或更简单的形式用RxJava 实现AsyncTask 功能。使用RxJava 可以简化任务链接,因此如果您只有一个任务AsyncTask 可以是更好的解决方案,但是当您有多个连续任务要执行时-RxJava 的界面更方便。因此,例如,您应该为每个任务编写onCancelled,而不是实现一些通用逻辑。

【讨论】:

  • 我不明白。你是说 Rx 不应该用来替代 AsyncTaskLoader 和 AsyncTask 吗? AsyncTaskLoader 通常在每个 Activity 中使用一次(因为它会在 Activity 配置更改时保存其状态),而 AsyncTask 通常会创建多次,因为它通常用于 RecyclerView 和 ListView 等小任务。
  • 我只建议在更方便时使用每种方法。对于几个连续的异步操作(例如,加载 -> 转换 -> 加载另一部分 -> 加入)RxJava 代码更加透明。这里的中心点是连续性。
  • 在这些情况下是否可以使用 Rx?当然,示例不是最好的用例,但它只是一个示例。我想编写尽可能短的代码,以演示如何以一般方式完成。
  • 是的,这是可能的。您只需要手动编写进度跟踪代码,调用dispose后进行一些清理等。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多