簡體   English   中英

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

但是這些課程非常丑陋,所以我想擺脫它們。 我想用 kotlin 協程來做到這一點。 我嘗試了一些變體,但它們似乎都不起作用。 我最有可能懷疑的工作是這樣的:

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

但這不能正常工作。 據我了解, runBlocking不會像AsyncTask那樣啟動一個新線程,而這正是我需要它做的。 但是使用thread協程,我沒有看到在完成時得到通知的合理方法。 另外,我也不progressBar.visibility = View.GONE放在新線程中,因為只允許 UI 線程進行此類操作。

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

要使用協程,您需要做幾件事:

  • 實現CoroutineScope接口。
  • JobCoroutineContext實例的引用。
  • 當調用在后台線程中運行代碼的函數時,使用掛起函數修飾符掛起協程而不阻塞線程
  • 使用withContext(Dispatchers.IO)函數在后台線程中運行代碼並使用啟動函數來啟動協程。

通常我為此使用一個單獨的類,例如"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下一行(S)添加到應用程序的文件的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"

您可以讓 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}")

希望這是有幫助的。

首先,您必須使用launch(context)運行協程,而不是runBlockinghttps : runBlocking

其次,要獲得onPostExecute的效果,必須使用

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

這不使用協程,但這是在后台運行任務並在此之后在 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()
            }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM