AsyncTask 作为 kotlin 协程

AsyncTask as kotlin coroutine

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

但是这些 类 非常丑陋,所以我想摆脱它们。 我想用科特林协程来做这件事。我尝试了一些变体,但其中 none 似乎有效。我最有可能怀疑工作的是这个:

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

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

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

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

其次,要得到onPostExecute的效果,就得用

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

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

在你的 override fun onCreate() 方法中,

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

可以初始化,

private var jobStart: Job? = null

In Kotlin, var declaration means the property is mutable. If you declare it as val, it is immutable, read-only & cannot be reassigned.

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

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

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

An additional tip is to check that your coroutine is indeed running on another thread, eg. DefaultDispatcher-worker-1,

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

希望这对您有所帮助。

要使用协程,您需要做一些事情:

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

通常我为此使用单独的 class,例如“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"


.

另一种方法是在 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"

这不使用协程,但这是一个快速的解决方案,可以在后台执行任务 运行,然后在 UI 上执行某些操作。

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

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

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

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

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

            }.invokeOnCompletion {
                createNotification()
            }
private val TAG = MainActivity::class.simpleName.toString()
    private var job = Job()

    //coroutine Exception
    val handler = CoroutineExceptionHandler { _, exception ->
        Log.d(TAG, "$exception handled !")
    }

    //coroutine context
    val coroutineContext: CoroutineContext get() = Dispatchers.Main + job + handler

    //coroutine scope
    private val coroutineScope = CoroutineScope(coroutineContext)




    fun execute() = coroutineScope.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(5000) // simulate async work
            loadFileFromStorage()
            return@withContext "SomeResult"
        }

    // Runs on the Main(UI) Thread
    private fun onPreExecute() {
        LoadingScreen.displayLoadingWithText(this,"Loading Files",false)

    }

    // Runs on the Main(UI) Thread
    private fun onPostExecute(result: String) {
        //progressDialogDialog?.dismiss()
        LoadingScreen.hideLoading()
        // hide progress
    }

我开始将我 Android 项目中的 AsyncTask 内容迁移到使用协同程序...如果你真的需要在完成异步任务后在 UI 上做一些事情(即,您只是在 AsyncTask 中覆盖 doInBackGround 和 onPostExecute)...可以完成这样的事情(我自己尝试过并且有效):

val job = CoroutineScope(Dispatchers.IO).async {
    val rc = ...
    return@async rc
}

CoroutineScope(Dispatchers.Main).launch {
    val job_rc = job.await() // whatever job returns is fed to job_rc

    // do UI updates here
}

您的工作不需要使用 I/O 调度程序...如果不是 I/O 密集型,您可以使用默认值。

然而,等待作业完成的协程需要在 Main/UI 线程中,以便您可以更新 UI。

是的,有一些语法糖可以用来使上面的代码看起来更酷,但是当一个人刚开始迁移到使用协程时,这至少更容易掌握。