Summary of Kotlin coroutines and their use in Android (3. Rewrite callbacks and RxJava calls into suspend functions)

Insert picture description here
This article mainly introduces the following, for the interface callback logic in the existing project, and the subscription logic code in Rxjava, modified into the form of suspend suspend function.

1 Rewriting of interface callback

In the general interface callback scenario, we can all change to the form of suspending functions, such as the success and failure callbacks of the network interface, the application callback for dynamic permissions, or other asynchronous callback interfaces.

(1) Use suspendCoroutine

Taking the network request Call in OkHttp as an example, refer to the Kotlin Coroutines (Coroutines) Complete Analysis (3), encapsulating asynchronous callbacks, the relationship between coroutines and the cancellation of coroutines , we can add an extension function to the Call object await(), the code is as follows:

suspend fun <T> Call<T>.await(): T = suspendCoroutine { cont ->
    enqueue(object : Callback<T> {
        override fun onResponse(call: Call<T>, response: Response<T>) { 
            if (response.isSuccessful) {
                cont.Continuation(response.body()!!)
            } else {
                cont.resumeWithException(ErrorResponse(response))
            }
        }
        override fun onFailure(call: Call<T>, t: Throwable) { 
            cont.resumeWithException(t)
        } 
    })
}

I.e. by suspendCoroutineor suspendCancellableCoroutine, using its internal Continuationor CancellableContinuationthe resumemethod of recovery to normal coroutine, or by resumeWithExceptionabnormal error to achieve the principles of the callback.

public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T =
    suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
        val safe = SafeContinuation(c.intercepted())
        block(safe)
        safe.getOrThrow()
    }

public suspend inline fun <T> suspendCancellableCoroutine(
    crossinline block: (CancellableContinuation<T>) -> Unit
): T =
    suspendCoroutineUninterceptedOrReturn { uCont ->
        val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
        // 和 suspendCoroutine 的区别就在这里,如果协程已经被取消或者已完成,就会抛出 CancellationException 异常
        cancellable.initCancellability()
        block(cancellable)
        cancellable.getResult()
    }

The idea of ​​transforming into a suspend function is also very simple, that is, a layer is encapsulated on the original interface success and error callback, so that the caller can use the form of coroutine to reduce the nesting of interfaces.

(2)使用suspendCancellableCoroutine

suspendCoroutineThe use introduced above , now introduce suspendCancellableCoroutinethe use, the difference between the two is that suspendCancellableCoroutineyou can Job.cancel()cancel (will throw CancellationException), the suspendCancellableCoroutinereturned CancellableContinuationobject, we can call it resume、resumeWithException和抛出CancellationExceptionto deal with the coroutine logic.

MainScope().launch {
  try {
    val user = fetchUser()
    //由于fetchUser中产生来异常,所以下面的updateUser不会被调用到
    updateUser(user)
  } catch (exception: Exception) {
    // Use try-catch or CoroutinesExceptionHandler to handle exceptions.
    Log.d("demo", "$exception") // Prints "java.io.IOException".
  }
  
  // 如果上面使用了try-catch来捕获异常,那么下面的代码仍然可以执行到
  doSomething()
}

// Fetches the user data from server.
private suspend fun fetchUser(): User = suspendCancellableCoroutine { 
cancellableContinuation ->
  fetchUserFromNetwork(object : Callback {
    override fun onSuccess(user: User) {
      cancellableContinuation.resume(user)
    }

    override fun onFailure(exception: Exception) {
      // Invokes this line since the callback onFailure() is invoked.
      cancellableContinuation.resumeWithException(exception)
    }
  })
}

private fun fetchUserFromNetwork(callback: Callback) {
  Thread {
    Thread.sleep(3_000)
    
    //callback.onSuccess(User())
    // Invokes onFailure() callback with "IOException()".
    callback.onFailure(IOException())
  }.start()
}

private fun updateUser(user: User) {
  // Updates UI with [User] data.
}

interface Callback {
  fun onSuccess(user: User)
  fun onFailure(exception: Exception)
}

class User

2 RxJava's subscription callback is converted into a suspend function

Most projects now use RxJava. Although many texts promote the use of coroutines to replace RxJava, the coroutine API is still evolving. On the other hand, the current RxJava code is still acceptable. The total replacement cost And the risk is high, so using the two together is an optimal choice.

In order to facilitate the conversion of RxJava calls to the suspend function form of coroutines , jetbrains officially gave an implementation, that is, use kotlinx-coroutines-rx2:

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.3.2"

All coroutine constructors
Taking the use of Single as an example, calling Single.await () can help us convert RxJava into one suspendCancellableCoroutine. The process is actually the same as the conversion above. The source code is as follows:
Insert picture description here
use example:

MainScope().launch {
  CoroutineScope(Dispatchers.Main).launch {
     try {
    	val user = fetchUserFromServer().await()
    	updateUser(user)
  	} catch (e: Exception) {
    	Log.d("demo", "(4) {$e}, ${Thread.currentThread()}")
  	}
  }
}

private fun fetchUserFromServer(): Single<User> =
  Single.create<User> {
    Log.d("demo", "(1) fetchUserFromServer start, ${Thread.currentThread()}")
    Thread.sleep(3_000)
    it.onSuccess(User())
    Log.d("demo", "(2) fetchUserFromServer onSuccess, ${Thread.currentThread()}")
  }.subscribeOn(Schedulers.io())
      .observeOn(AndroidSchedulers.mainThread())

private fun updateUser(user: User) {
  Log.d("demo", "(3) updateUser, ${Thread.currentThread()}")
}

class User

That is, our original method does not need to be modified, just use kotlinx-coroutines-rx2the await function defined in the call , it will suspend the execution of the current coroutine, and then resume the execution of the coroutine after RxJava returns the result. Of course, don't forget to add exception try-catch when calling.

For Maybe and Observable in RxJava, kotlinx-coroutines-rx2corresponding extension functions are provided for us to use. Hurry up and try it in your code!

Insert picture description here

to sum up:

The callback function or calls into RxJava into coroutine suspend function call, the principle is quite simple, on suspendCoroutineand suspendCancellableCoroutineprinciples,
they are the key to call the suspendCoroutineUninterceptedOrReturn()function, its role is to obtain an instance of the current coroutine, and hang Start the current coroutine or return the result without suspending it.

There are also two common suspend functions used in coroutines suspendCoroutineUninterceptedOrReturn(), namely delay()and yield().
delay()Everyone is already familiar with it, yield()that is, to give up the execution power of the current coroutine and give it to other coroutines for execution, which can achieve an effect similar to alternating execution. I have not yet thought of the applicable scenario.

Specific source code analysis can refer to the first article at the end of the article.

When calling other suspend functions in a Scope, we still need to add try-catch(注意应该加在协程体内部,在整个Scope上包try-catch是无法捕获异常的)or use CoroutinesExceptionHandlerprocessing logic. Although Kotlin claims that there are no checked exceptions , but for the logic of the process and the normal operation of the code, we still Capture processing at the call.

Reference:
Kotlin Coroutines (Coroutines) Complete analysis (3), encapsulating asynchronous callbacks, relations between coroutines and cancellation of
coroutines Kotlin Coroutines in Android — Suspending Functions

Published 82 original articles · Like 86 · Visit 110,000+

Guess you like

Origin blog.csdn.net/unicorn97/article/details/105163021