Preface
Kotlin Coroutines is a new asynchronous API launched by Kotlin. Not the best solution to all problems, but hopefully in many cases it will make things a little easier. Here I will simply show the specific usage plan of this library in Android. I won’t say much below, let’s take a look at the detailed introduction together.
Introducing Coroutines
//Add the following code in the android node in the build.gradle file of the application kotlin { experimental { coroutines 'enable' }}//Add the following two lines to the dependency implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.20" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.20"The first Coroutines example
Usually we load an image into ImageView, and the asynchronous loading task is as follows:
fun loadBitmapFromMediaStore(imageId: Int, imagesBaseUri: Uri): Bitmap { val uri = Uri.withAppendedPath(imagesBaseUri, imageId.toString()) return MediaStore.Images.Media.getBitmap(contentResolver, uri)}This method must be executed in the background thread because it belongs to an IO operation, which means we have many solutions to start the background task, and once the method returns a bitmap, we need to display it in the Imageview immediately.
imageView.setImageBitmap(bitmap)
This line of code must be executed in the main thread, otherwise it will crash.
If the above three lines of code are written together, the program will be stuck or crashed, which depends on the reasonable selection of threads. Next, let's take a look at how Coroutines using kotlin solves this problem:
val job = launch(Background) { val uri = Uri.withAppendedPath(imagesBaseUri, imageId.toString()) val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, launch(UI) { imageView.setImageBitmap(bitmap) }}The most important thing here is launch() and parameters Background and UI. Launch() means to create and start a Coroutine. The Background parameter CoroutineContext is used to ensure execution in the background thread, so as to ensure that the application will not be stuck or crashed. You can declare a CoroutineContext as shown below.
internal val Background = newFixedThreadPoolContext(2, "bg")
This creates a new context and uses two regular threads when executing its tasks.
Next, launch(UI), which will trigger another coroutine, which will be executed on Android
main thread.
Cancelable
The next challenge is to deal with things related to the Activity declaration cycle. When you load a task and leave the Activity before it has finished executing, it will cause crash when calling imageView.setImageBitmap(bitmap) , so we need to cancel the task before leaving the activity. Here we use the return value of the launch() method. When the activity calls the onStop method, we need to use the job to cancel the task.
job.cancel()
It's like calling dispose when you use Rxjava and calling cancel function when you use AsyncTask.
LifecycleObserver
Android Architecture Components provides Android developers with many powerful libraries, one of which is the Lifecycle API. It provides us with an easy way to listen to the lifecycle of activities and fragments in real time. Let's define the code to use with coroutines.
class CoroutineLifecycleListener(val deferred: Deferred<*>) : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun cancelCoroutine() { if (!deferred.isCancelled) { deferred.cancel() } }}We create a LifecycleOwner extension function:
fun <T> LifecycleOwner.load(loader: () -> T): Deferred<T> { val deferred = async(context = Background, start = CoroutineStart.LAZY) { loader() } lifecycle.addObserver(CoroutineLifecycleListener(deferred)) return deferred} There are too many new things in this method, and I will explain them one by one:
Now we can call load() in an activity or fragment and access the lifecycle members from that function and add our CoroutineLifecycleListener as observer.
The load method requires a loader as a parameter, returning a general type T. In the load method, we call another Coroutine creator async() function, which will use the Background coroutine context to execute tasks in the background thread. Note that this method has another parameter start = CoroutineStart.LAZY, which means that the coroutine will not be executed immediately, until it is called.
coroutine will then return a Defered<T> object to the caller, which is similar to our previous job, but it can also carry a delay value, such as JavaScript Promise or Future <T> in a regular Java API, and even better, it has an await method.
Next we define another extension function then() , this time we define it above Deferen<T> , which is the type returned by our load method above. It also takes a lambda as a parameter, named block, which takes a single object of type T as its parameter.
infix fun <T> Deferred<T>.then(block: (T) -> Unit): Job { return launch(context = UI) { block([email protected]()) }} This function will create another Coroutine using launch() function, which will run on the main thread this time. The lambda (named block) passed to this Coroutine takes the value of the completed Deferred object as its parameter. We call await() to suspend execution of this Coroutine until the Deferred object returns a value.
Here is where coroutine becomes so impressive. The call to await() is done on the main thread, but does not block further execution of that thread. It will simply pause the execution of the function until it is ready, when it resumes and passes the delayed value to the lambda. When the coroutine is suspended, the main thread can continue to perform other things. The await function is a core concept in coroutine, what creates the whole thing so magical.
The lifecycle observer added in load() function will cancel the first coroutine after calling onDestroy() on our activity. This will also cause the second coroutine to be cancelled, preventing block() from being called.
Kotlin Coroutine DSL
Now that we have two extension functions and a class that handles the cancellation of coroutine, let's see how to use it:
load { loadBitmapFromMediaStore(imageId, imagesBaseUri)} then { imageView.setImageBitmap(it)} In the above code, we pass the lambda method to the load function, which calls the loadBitmapFromMediaStore method, which must be executed on the background thread until the method returns a Bitmap, and the return value of the load method is Deferred<Bitmap> .
As an extension function, then() method uses infix declaration. Although the load method returns Deferred<Bitmap> , it will be passed to the then method a bitmap return value, so we can directly call imageView.setImageBitmap(it) in the then method.
The above code can be used for any asynchronous calls that need to occur on the background thread, and where the return value should be returned to the main thread, like in the above example. It doesn't make multiple calls as RxJava does, but it's easier to read and may cover a lot of the most common cases. Now you can do something like this safely without worrying about causing context leaks or processing threads in every call;
load { restApi.fetchData(query) } then { adapter.display(it) }Then() and load() methods are just the tip of the iceberg of this new library, but I do hope something similar appears in future Kotlin-based Android libraries once the coroutine version reaches a stable version. Before this you can use or modify the above code, or check out Anko Coroutines. I also released a more complete version on GitHub. (https://github.com/ErikHellman/KotlinAsyncWithCoroutines (local download)).
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.