【问题标题】:AsyncTask as kotlin coroutineAsyncTask 作为 kotlin 协程
【发布时间】:2019-08-08 01:50:23
【问题描述】:

AsyncTask 的典型用途:我想在另一个线程中运行一个任务,完成该任务后,我想在我的 UI 线程中执行一些操作,即隐藏一个进度条。

任务将在TextureView.SurfaceTextureListener.onSurfaceTextureAvailable 开始,完成后我想隐藏进度条。同步执行此操作不起作用,因为它会阻止构建 UI 的线程,使屏幕变黑,甚至不显示我之后想要隐藏的进度条。

到目前为止,我使用这个:

inner class MyTask : AsyncTask<ProgressBar, Void, ProgressBar>() {
    override fun doInBackground(vararg params: ProgressBar?) : ProgressBar {
        // do async
        return params[0]!!
    }

    override fun onPostExecute(result: ProgressBar?) {
        super.onPostExecute(result)
        result?.visibility = View.GONE
    }
}

但是这些课程非常丑陋,所以我想摆脱它们。 我想用 kotlin 协程来做到这一点。我尝试了一些变体,但它们似乎都不起作用。我最有可能怀疑的工作是这样的:

runBlocking {
        // do async
}
progressBar.visibility = View.GONE

但这不能正常工作。据我了解,runBlocking 不会像AsyncTask 那样启动新线程,这就是我需要它做的事情。但是使用thread 协程,我看不到一种合理的方式来在它完成时得到通知。另外,我也不能把progressBar.visibility = View.GONE放到一个新线程中,因为只有UI线程可以做这样的操作。

我是协程的新手,所以我不太明白我在这里缺少什么。

【问题讨论】:

  • Benjamin Basmaci,您仍然认为接受的答案是正确的吗?也许您会重新考虑您的选择,因为这个问题似乎很受欢迎,而寻求答案的用户可能会感到困惑。
  • @Sergey 非常感谢您的回答和您的 cmets。我相信你帮助了很多人,它显然显示在赞成票中。我更改了接受的答案,但我没有选择您的任何一个答案。原因是,我特别想要一个苗条、快速的解决方案,它不涉及为我拥有的每个用例编写新类。出于这个原因,我做出了自己的回答并接受了这个答案,因为这就是我目前正在做的事情。不确定这两种方法的优缺点,但这就是我正在寻找的这个问题。

标签: android multithreading kotlin android-asynctask kotlin-coroutines


【解决方案1】:

要使用协程,您需要做以下几件事:

  • 实现CoroutineScope接口。
  • JobCoroutineContext 实例的引用。
  • 在调用在后台线程中运行代码的函数时,使用 suspend 函数修饰符挂起协程而不阻塞主线程
  • 使用 withContext(Dispatchers.IO) 函数在后台线程中运行代码并使用 launch 函数启动协程。

通常我为此使用一个单独的类,例如“Presenter”或“ViewModel”

class Presenter : CoroutineScope {
    private var job: Job = Job()
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job // to run code in Main(UI) Thread

    // call this method to cancel a coroutine when you don't need it anymore,
    // e.g. when user closes the screen
    fun cancel() {
        job.cancel()
    }

    fun execute() = launch {
        onPreExecute()
        val result = doInBackground() // runs in background thread without blocking the Main Thread
        onPostExecute(result)
    }

    private suspend fun doInBackground(): String = withContext(Dispatchers.IO) { // to run code in Background Thread
        // do async work
        delay(1000) // simulate async work
        return@withContext "SomeResult"
    }

    // Runs on the Main(UI) Thread
    private fun onPreExecute() {
        // show progress
    }

    // Runs on the Main(UI) Thread
    private fun onPostExecute(result: String) {
        // hide progress
    }
}

使用ViewModel,代码使用viewModelScope更简洁:

class MyViewModel : ViewModel() {
    
    fun execute() = viewModelScope.launch {
        onPreExecute()
        val result = doInBackground() // runs in background thread without blocking the Main Thread
        onPostExecute(result)
    }

    private suspend fun doInBackground(): String = withContext(Dispatchers.IO) { // to run code in Background Thread
        // do async work
        delay(1000) // simulate async work
        return@withContext "SomeResult"
    }

    // Runs on the Main(UI) Thread
    private fun onPreExecute() {
        // show progress
    }

    // Runs on the Main(UI) Thread
    private fun onPostExecute(result: String) {
        // hide progress
    }
}

要使用viewModelScope,请将下一行添加到应用的 build.gradle 文件的依赖项:

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION"

在撰写本文时final LIFECYCLE_VERSION = "2.3.0-alpha04"

【讨论】:

    【解决方案2】:

    另一种方法是在CoroutineScope上创建通用扩展函数:

    fun <R> CoroutineScope.executeAsyncTask(
            onPreExecute: () -> Unit,
            doInBackground: () -> R,
            onPostExecute: (R) -> Unit
    ) = launch {
        onPreExecute()
        val result = withContext(Dispatchers.IO) { // runs in background thread without blocking the Main Thread
            doInBackground()
        }
        onPostExecute(result)
    }
    

    现在我们可以将它与任何CoroutineScope 一起使用:

    • ViewModel:

        class MyViewModel : ViewModel() {
      
            fun someFun() {
                viewModelScope.executeAsyncTask(onPreExecute = {
                    // ...
                }, doInBackground = {
                    // ...
                    "Result" // send data to "onPostExecute"
                }, onPostExecute = {
                    // ... here "it" is a data returned from "doInBackground"
                })
            }
        }
      
    • ActivityFragment

        lifecycleScope.executeAsyncTask(onPreExecute = {
            // ...
        }, doInBackground = {
            // ...
            "Result" // send data to "onPostExecute"
        }, onPostExecute = {
            // ... here "it" is a data returned from "doInBackground"
        })
      

    要使用viewModelScopelifecycleScope,将下一行添加到应用的build.gradle 文件的依赖项:

    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION" // for viewModelScope
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:$LIFECYCLE_VERSION" // for lifecycleScope
    

    在撰写本文时final LIFECYCLE_VERSION = "2.3.0-alpha05"

    【讨论】:

    • 保持熟悉度的最佳解决方案。你可以为此添加一个java版本吗?我试图从 kotlin 反编译它,但生成的代码看起来很垃圾。
    • @chitgoks 这个实现是基于 Coroutines 的,Java 没有 Coroutines,所以在 Java 中很难做到。
    • 我爱你!我想我只有在阅读了这篇 sn-p 之后才真正理解了协程
    【解决方案3】:

    您可以让 ProgressBar 在 UI 主线程上运行,同时使用协程异步运行您的任务。

    在你的重写 fun onCreate() 方法中,

    GlobalScope.launch(Dispatchers.Main) { // Coroutine Dispatcher confined to Main UI Thread
        yourTask() // your task implementation
    }
    

    你可以初始化,

    private var jobStart: Job? = null
    

    在 Kotlin 中,var 声明意味着属性是可变的。如果你 将其声明为 val,它是不可变的、只读的且不能重新分配。

    在 onCreate() 方法之外,yourTask() 可以实现为一个挂起函数,它不会阻塞主调用者线程。

    当函数在等待返回结果的过程中被挂起时,它正在运行的线程被解除阻塞,以供其他函数执行。

    private suspend fun yourTask() = withContext(Dispatchers.Default){ // with a given coroutine context
        jobStart = launch {
           try{
            // your task implementation
           } catch (e: Exception) {
                 throw RuntimeException("To catch any exception thrown for yourTask", e)
          }
        }
      }
    

    对于您的进度条,您可以创建一个按钮以在单击按钮时显示进度条。

    buttonRecognize!!.setOnClickListener {
        trackProgress(false)
    }
    

    在 onCreate() 之外,

    private fun trackProgress(isCompleted:Boolean) {
        buttonRecognize?.isEnabled = isCompleted // ?. safe call
        buttonRecognize!!.isEnabled // !! non-null asserted call
    
        if(isCompleted) {
            loading_progress_bar.visibility = View.GONE
        } else {
            loading_progress_bar.visibility = View.VISIBLE
        }
    }
    

    另一个提示是检查您的协程是否确实在运行 另一个线程,例如。 DefaultDispatcher-worker-1,

    Log.e("yourTask", "Running on thread ${Thread.currentThread().name}")
    

    希望这有帮助。

    【讨论】:

      【解决方案4】:

      首先,您必须使用launch(context) 运行协程,而不是使用runBlockinghttps://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html

      其次,要得到onPostExecute的效果,必须使用

      Activity.runOnUiThread(Runnable)View.post(Runnable)

      【讨论】:

      • 没有必要使用Activity.runOnUiThread(Runnable) or View.post(Runnable).。使用适当的Dispatchers,您可以在 UI 线程中调用函数。看我的回答stackoverflow.com/a/58900195/1731626
      【解决方案5】:

      这不使用协程,但它是让任务在后台运行并在之后在 UI 上执行某些操作的快速解决方案。

      我不确定这种方法与其他方法相比的优缺点,但它很有效并且非常容易理解:

      Thread {
          // do the async Stuff
          runOnUIThread {
              // do the UI stuff
          }
          // maybe do some more stuff
      }.start()
      

      使用此解决方案,您可以轻松地在两个实体之间传递值和对象。你也可以无限嵌套。

      【讨论】:

      • 这不是一个好的解决方案。你可能有后台任务执行很长时间,同时你的活动可能会被破坏,调用runOnUiThread 方法时可能会出现异常。顺便说一句,这个解决方案没有使用协程。
      • @Sergey 虽然我不同意这种方法的风险推理,但我回顾了问题和答案。虽然问题是针对像我这样的解决方案,但现在就是这样写的。原因是我当时缺乏对 kotlin、android 和协程的理解。这种缺乏经验导致我专门要求协程。正如你所说,这个问题很受欢迎,我的解决方案不涉及协程。我最初的意图和问题的确切措辞非常不同。然而,措辞才是最重要的。所以我将重新分配接受的解决方案。谢谢!
      【解决方案6】:

      以下方法可能能够满足您的需求。它需要更少的样板代码,并且适用于 100% 的用例

      GlobalScope.launch {
                      bitmap = BitmapFactory.decodeStream(url.openStream())
      
                  }.invokeOnCompletion {
                      createNotification()
                  }
      

      【讨论】:

      • 不建议使用GlobalScope。来自文档:“...在 GlobalScope 的实例上使用异步或启动是非常不鼓励的。”
      猜你喜欢
      • 1970-01-01
      • 2020-10-07
      • 2019-07-16
      • 2019-02-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多