Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
374 views
in Technique[技术] by (71.8m points)

android - AsyncTask as kotlin coroutine

Typical use for AsyncTask: I want to run a task in another thread and after that task is done, I want to perform some operation in my UI thread, namely hiding a progress bar.

The task is to be started in TextureView.SurfaceTextureListener.onSurfaceTextureAvailable and after it finished I want to hide the progress bar. Doing this synchronously does not work because it would block the thread building the UI, leaving the screen black, not even showing the progress bar I want to hide afterwards.

So far I use this:

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

But these classes are beyond ugly so I'd like to get rid of them. I'd like to do this with kotlin coroutines. I've tried some variants but none of them seem to work. The one I would most likely suspect to work is this:

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

But this does not work properly. As I understand it, the runBlockingdoes not start a new thread, as AsyncTask would, which is what I need it to do. But using the thread coroutine, I don't see a reasonable way to get notified when it finished. Also, I can't put progressBar.visibility = View.GONE in a new thread either, because only the UI thread is allowed to make such operations.

I'm new to coroutines so I don't quite understand what I'm missing here.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

To use a coroutine you need a couple of things:

  • Implement CoroutineScope interface.
  • References to Job and CoroutineContext instances.
  • Use suspend function modifier to suspend a coroutine without blocking the Main Thread when calling function that runs code in Background Thread.
  • Use withContext(Dispatchers.IO) function to run code in background thread and launch function to start a coroutine.

Usually I use a separate class for that, e.g. "Presenter" or "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
    }
}

With ViewModel the code is more concise using 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
    }
}

To use viewModelScope add next line to dependencies of the app's build.gradle file:

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

At the time of writing final LIFECYCLE_VERSION = "2.3.0-alpha04"


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...