I have some experience and accumulation in using coroutines in my projects. Here are some common cases to illustrate the benefits of using coroutines

Official Activity Case

abstract class BaseCoroutineActivity : Activity(), CoroutineScope {

    protected lateinit var job: Job

    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.Main

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        job = Job()
    }

    override fun onBeforeDestroy(a) {
        super.onBeforeDestroy()
        job.cancel()
    }

}
Copy the code

All activities inherit this class to obtain the Activity lifecycle management coroutine Scope, which has several features:

  • The Activity itself is a CoroutineScope, which makes it easy to invoke launch/async code and so on
  • The code running inside the coroutine is cancelled when the Activity is closed without causing unnecessary problems
  • The code for coroutines by default runs on the Main thread and can update the UI as determined by dispatchers.main

Retrofit2

Retrofit2 directly supports declaring the suspend method in the new version, which is easier to use than callback and RxJava

Simple Api

Declare an Api with the suspend method:

interface UserApi {
    @GET("/user/list/{page}")
    suspend fun fetchPage(page: Int): List<User>
}
Copy the code

Call this Api in the Activity:

class ExampleActivity : BaseCoroutineActivity() {

    override fun onCreate(savedInstanceState: Bundle? , persistentState:PersistableBundle?). {
        super.onCreate(savedInstanceState, persistentState)

        launch {
            val api: UserApi = Retrofit.create(UserApi::class.java)
            val userList = api.fetchPage(0)
            someAdapter.setData(userList)
            someAdapter.notifyDataSetChanged()
        }

    }

}
Copy the code

FetchPage is a suspend method, that is, it has a suspend start, and the Activity finishes before it returns a result. Because the coroutine is canceled, the suspended code is no longer executed, causing no problems such as NPE. In addition, Retrofit is supposed to monitor the job running status of the coroutine corresponding to the current suspend method and cancel the Call to save network resource consumption

This is much better than callback and RxJava

Paging pull

Using callback and RxJava causes some ugly code when doing paging logic, but using coroutines doesn’t, and it’s easy to see

The paging server will typically deliver data along with a hasMore to indicate whether there is a next page

data class UserPage(
    val hasMore: Boolean.val list: List<User>
)

interface UserApi {
    @GET("/user/list/{page}")
    suspend fun fetchPage(page: Int): UserPage
}
Copy the code
launch {
    val api: UserApi = Retrofit.create(UserApi::class.java)
    var page = 0
    val allUsers = mutableListOf<User>()
    do {
        val userPage = api.fetchPage(page)
        allUsers.addAll(userPage.list)
        page ++
    } while(userPage.hasMore)
    someAdapter.setData(allUsers)
    someAdapter.notifyDataSetChanged()
}
Copy the code

Such code can be understood without comments at all

The asynchronous method goes to suspend

Mimicking the official delay function

Suppose we declare these methods in an Activity. After all, we need a threaded environment, where we use the Activity’s UI thread to understand the method itself

private val mHandler: Handler = Handler()

suspend fun delay(timeMills: Long) {
    suspendCancellableCoroutine<Unit> { continuation ->
        // use Handler to do a delay
        val callback: () -> Unit = {
            continuation.resumeWith(Result.success(Unit))
        }
        mHandler.postDelayed(callback, timeMills)

        // Note the cancellation logic of coroutines, which can be cancelled externally at any time
        continuation.invokeOnCancellation {
            mHandler.removeCallbacks(callback)
        }
    }
}
Copy the code

Essentially, the above code converts the postDelayed callback form of the Handler to the suspend method, which can then be used in the coroutine

OkHttp instance

class OkHttpUtil(private val httpClient: OkHttpClient) {

    private suspend fun download(url: String): InputStream {
        // Suspend the coroutine
        return suspendCancellableCoroutine {
            val call = download(url) { error, stream ->
                // We have the result
                if(error ! =null) {
                    it.resumeWith(Result.failure(error))
                } else{ it.resumeWith(Result.success(stream!!) ) } } it.invokeOnCancellation { call.safeCancel()// This is a security cancel}}}// Here is the usual callback request code
    private fun download(url: String, callback: (error: Throwable? , stream:InputStream?). -> Unit) : Call {
        val request = Request.Builder().url(url)
        val call = httpClient.newCall(request.build())
        call.enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                callback(e, null)}override fun onResponse(call: Call, response: Response) {
                if(! response.isSuccessful) { callback(IOException("Response with ${response.code()} ${response.message()}"), null)
                    return
                }
                try {
                    callback(null, response.body().byteStream())
                } catch (e: Throwable) {
                    callback(e, null)}}})return call
    }
    
}
Copy the code

Use code:


launch {
    val okHttpClient = ...
    val util = OkHttpUtil(okHttpClient)
    val stream = util.download("... some url")
    val file = File("... some file path")
    FileUtils.streamToFile(stream, file)
}
Copy the code

summary

It is using hang coroutines (suspendCancellableCoroutine) and restore coroutines (resumeWith) can realize arbitrary asynchronous code into suspend method, is better for us to write the external logic

Decompile to Java

There is no suspend keyword in Java, so what is Kotlin’s suspend decomcompiled

Here we use the tools of AS to explore

First, write a suspend function from any new Kotlin file

suspend fun test(arg: Any): Any {
    return Any()
}
Copy the code

Use the following steps to get the decompiled Java code

The following

public final class Test2Kt {
   @Nullable
   public static final Object test(@NotNull Object arg, @NotNull Continuation $completion) {
      return newObject(); }}Copy the code

As you can see, the Java method takes one more parameter than Kotlin’s method: Continuation $completion

So Retrofit uses this parameter to determine whether a method is suspend and then returns the result with the resume of $completion when it creates a dynamic proxy intercept method execution. If the $completion is a CancellableContinuation, you can also use the invokeOnCancellation function to cancel the current Call

The last

Coroutines are designed to simplify asynchronous code into synchronous code to optimize streamline code and improve readability. Of course, coroutines also help save thread resource consumption.