AsyncTask as kotlin coroutine

后端 未结 5 473
长发绾君心
长发绾君心 2020-12-06 02:50

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

相关标签:
5条回答
  • 2020-12-06 02:58

    You can get ProgressBar to run on the UI Main Thread, while using coroutine to run your task asynchronously.

    Inside your override fun onCreate() method,

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

    You can initialize,

    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.

    Outside the onCreate() method, yourTask() can be implemented as a suspending function, which does not block main caller thread.

    When the function is suspended while waiting for the result to be returned, its running thread is unblocked for other functions to execute.

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

    For your progress bar, you can create a button to show the progress bar when the button is clicked.

    buttonRecognize!!.setOnClickListener {
        trackProgress(false)
    }
    

    Outside of 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}")
    

    Hope this is helpful.

    0 讨论(0)
  • 2020-12-06 03:04

    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"

    0 讨论(0)
  • 2020-12-06 03:17

    First, you have to run coroutine with launch(context), not with runBlocking: https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html

    Second, to get the effect of onPostExecute, you have to use

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

    0 讨论(0)
  • 2020-12-06 03:18

    This does not use coroutines, but it's a quick solution to have a task run in background and do something on UI after that.

    I'm not sure about the pros and cons of this approach compared to the others, but it works and is super easy to understand:

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

    With this solution, you can easily pass values and objects between the two entities. You can also nest this indefinitely.

    0 讨论(0)
  • 2020-12-06 03:21

    Another approach is to create generic extension function on 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)
    }
    

    Now we can use it with any CoroutineScope:

    • In ViewModel:

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

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

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

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

    At the time of writing final LIFECYCLE_VERSION = "2.3.0-alpha05".

    0 讨论(0)
提交回复
热议问题