Kotlin coroutine study guide

Kotlin since 2016 released version 1.0 to the current development version 1.3 has also been for three years, which is recognized in 2017 by Google Android official support for the development of language, I believe the vast majority of Android developers have been concerned about the use and kotlin it is, I am probably in the beginning with version 1.2, and even before contact with some, but just think out of things may not be stable, it is not too concerned, as the consummate update Kotlin language and I use it in-depth , the more that used to go back (of course, refers back to java), especially after contact with coroutines, so to sum up the spirit of knowledge and accumulation, so there is this article today about coroutines ~

If you have to Kotlin or coroutine not familiar with can refer to the following three-part official document based learning, this is the order I listed after learning reference may be easier to understand and learn.

Part I: asynchronous programming techniques, your first collaborative program

                  - You will know what is coroutines

Part II: coroutine Guide

                  - basic and basic use coroutine

Part III: using coordinated program UI programming

                  - coroutine in the Android

The following is a combination of extension of the third part, you need to use a small coroutine partners in Android programming can take a look at:

Button countdown implemented using coroutine

Examples of the countdown is often used in the scene we get the message authentication code, then this scene can how to achieve it?

1) by sending a message Handler one second delay, and 60 cycles

2) (or essentially CountDownTimer Handler)

3) by ObjectAnimator

4) coroutine

Today talk about how to implement this scenario by coroutine. Read the official tutorial above, you should know the coroutine is dependent on the threads and will not block the thread, so based on these two points, we can open a coroutine operate the controls on the UI thread.

tv_countdown.setOnClickListener {
            job = launch {
                it.isClickable = false
                for (i in 10 downTo 1) {
                    tv_countdown.text = "$i"
                    delay(1000)
                }
                tv_countdown.text = "reStart"
                it.isClickable = true
            }
        }

Is not very intuitive, the countdown of the code involved in a simple for loop, no callbacks, no going around, do not believe a programming foundation of people can understand what the code is doing.

If you want to cancel the countdown, it is very simple, just call to launch operations job function returns.

job.cancel()

Control the number of concurrent tasks using channel

When look at the second part of the document, you should note that the process of co-channel, used RxJava might think this stuff is very similar to the back pressure which is equivalent to a task queue, FIFO, support cushion, filter condition, pre-operational, merger, etc., and can be done almost Rxjava support.

Then the countdown of the above example, I open my disabled a countdown Button clicks until the end of the countdown, and prevent multiple clicks while multiple concurrent countdown on a Button, in addition to this method, in official documents also mention to use the channel to achieve this purpose, and mentioned a good solution is to use an  actor  to perform the task should not be concurrent .

Add extension methods to View

fun View.onClick(action: suspend (View) -> Unit) {
    // 启动一个 actor
    val eventActor = GlobalScope.actor<View>(Dispatchers.Main) {
        for (event in channel) action(event)
    }
    // 设置一个监听器来启用 actor
    setOnClickListener { 
        eventActor.offer(it)
    }
}

Applied to our example:

tv_countdown.onClick {
            for (i in 10 downTo 1) {
                tv_countdown.text = "$i"
                delay(1000)
            }
            tv_countdown.text = "reStart"
        }

So what is this actor? Why actor will be able to solve tasks and not complicated by it?

public fun <E> CoroutineScope.actor(
    context: CoroutineContext = EmptyCoroutineContext,
    capacity: Int = 0,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    onCompletion: CompletionHandler? = null,
    block: suspend ActorScope<E>.() -> Unit
): SendChannel<E>{
....
}

coroutine actor is an extension method, parameters coroutine context, capacity, boot options, optional actor coroutine completion handler coroutine codes, these parameters are set to default values, and encapsulates some code returns the results a SendChannel object. Regardless of other parameters, we look at the second parameter capacity, this parameter is used to generate a capacity of Channel 0 of the target.

val channel = Channel<E>(capacity)

In fact, this capacity is three enumerated values, and returns a different Channel based on different values.

Channel.RENDEZVOUS
When capacityit is 0 - it creates RendezvousChannel. The channel did not have any buffer. Only when transmitting and receiving the call timely meet (set point), the element will be transmitted from the sender to the receiver, thus transmission pause until another coroutine calls receive and receive, until another coroutine call Send .
CONFLATED
当capacityIs Channel.CONFLATED  - it creates ConflatedChannel. This buffer up to a channel element and all subsequent mixing sendand offercalls, so that the receiver element always obtain the most recently transmitted. Returns sent has sent elements merge  - receive only recently sent elements and elements previously sent will be lost . Sender this channel will never be suspended and provided always returns true.
UNLIMITED
When capacitythe Channel.UNLIMITED  - it creates LinkedListChannel. This is a channel list having infinite capacity buffer (only limited by available memory). Sender this channel will never be suspended and provided always returns true.

Why set up a buffer of 0 to channel it? Here we must lead send () method and the offer () method of. These two methods are added to the channel element.

So what difference does it send () method and offer () method?

 /**
  * Adds [element] into to this channel, suspending the caller while this channel [isFull],
  * or throws exception if the channel [isClosedForSend] (see [close] for details).
  * .....
  */
public suspend fun send(element: E)

Defined by the first method can be seen, send () method is a suspended, which means it can only suspend or co-process method call. This adds element channel, when the caller hangs this channel buffer is full or is not present.

/**
 * Adds [element] into this queue if it is possible to do so immediately without violating capacity restrictions
 * and returns `true`. Otherwise, it returns `false` immediately
 * or throws exception if the channel [isClosedForSend] (see [close] for details).
 */
public fun offer(element: E): Boolean

The offer () is a common method, you can call anywhere, do so immediately without violating capacity restrictions would add an element to this queue and return true,otherwise false.

Back to the original question, is not as long as the channel buffer, concurrent tasks to perform only one task can become yet? These words are not entirely right, in order to achieve with this transformation also need to offer (), why not send (), because send () method is a suspended, not as offer () the same will be executed immediately. For example countdown above, this means that when the countdown animation, click the action will be ignored. The reason this happens is the actor is busy performing and the channel is not cached, then you see the offer () returns a Boolean value will be false. 

 

Retrofit2 + coroutine using a network request

Retrofit2 + RxJava you should use a lot, but now have coroutine, with Retrofit2 how to use it, after all coroutine too convenient. Do not worry, Retrofit2 Association has supported the process, its author has updated with Retrofit2 use coroutine adapter .

Android GCA process will be the trend, by which point it can be a little collection of rxJava get one or two.

Let's look at an example of a Retrofit2 with Rxjava, the request to load a news list.

ApiManager.getApiManager().getApiService().
                  getNews(param1 ?: "top", "75c088a1daa9e51b558a74e2049c1aa0").
                  subscribeOn(Schedulers.newThread()).
                  observeOn(AndroidSchedulers.mainThread()).
                  subscribe { t ->
                    initRecycler(t.result.data)
                }

In fact, it does not seem complicated is not it, of course, this is not the case callback package before.

Then, using coroutines request this list is what?

launch{
        val newsBean = ApiManager.getInstance().getApiService().getNews("top", "75c088a1daa9e51b558a74e2049c1aa0").await()
        initRecycler(newsBean.result.data)
}

Less callback, is not pleasing to the eye looks better than Rxjava a lot, because the co-use process more in line with our logical thinking, top to bottom, the order of execution, prepared using a synchronous manner asynchronous request.

Perhaps the above example does not allow you to fully appreciate the difference is, this time we do a complex network request, or the above example, the current scene is so (just to better illustrate, not the actual scene ) - we need to be logged account get userId, and then go get token by userId, and finally took the token to the request list, a total of three network requests. As usual, Rxjava first to:

val apiService = ApiManager.getApiManager("").getApiService()
apiService.
         login("15700000001", "asdf1234")
         .subscribeOn(Schedulers.newThread())
         .observeOn(Schedulers.newThread())
         .flatMap { apiService.getToken(it) }
         .observeOn(Schedulers.newThread())
         .flatMap { apiService.getNews("top", it) }
         .observeOn(AndroidSchedulers.mainThread())
         .subscribe{ t ->
                initRecycler(t.result.data)
          }

You have not read the faint? Among other things, it switches the thread I see enough vertigo, while flatMap operator unfamiliar people may still need to take the time to learn the use of this operator, and moreover these are just a small amount of code to streamline, in flatMap you may also need to do some processing for the response to the request, even if you feel these little problems, once the request of a chain link in the chain exceptions, but even that log information than the code you just took over obscure, troubleshoot it may be enough for you to drink a pot.

so, using coroutines to write what would be sub-sub it? It will be good than Rxjava?

val apiService = ApiManager.getInstance().getApiService()
launch {
    val userId = apiService.login("15700000001", "asdf1234").await()
    val token = apiService.getToken(userId).await()
    val newsBean = apiService.getNews("top", token).await()
    initRecycler(newsBean.result.data)
}

wow,awesome~~

Compared to the previous version of just more than two lines of code, the code overall logic is very simple, token waits userId is completed, the list will wait for the completion token, whether the execution process on the code or logic is presented to us in order to synchronize way to complete the asynchronous request.

Coroutine exception caught

Examples of multiple request above, the exception is not easy to capture using Rxjava, suberic loss coroutine not so, want to catch exceptions very simple coroutine.

Only you need to add the parameters in launch

CoroutineExceptionHandler { _, throwable -> Log.e("kkk", throwable.message) }

Now we run the above example, try to throw an exception

throw IndexOutOfBoundsException("test exception")

You can see, we are indeed abnormal captured

 E/kkk: test exception

end

After reading official documents coroutines and this Part Bowen, co-routines in the end it delicious in your mind should have the answer. Anyway, I decided to start the next project on the use of the coroutine! ! !

Published 45 original articles · won praise 18 · views 30000 +

Guess you like

Origin blog.csdn.net/Ever69/article/details/97786560