Most mobile apps have to perform long-running or intensive operations — such as network calls or database operations — at some point. At any one time, your app might be playing a video, buffering the next section of video, and monitoring the network for possible interruptions, all while staying responsive to user input.
This kind of multi-tasking might be standard behavior for Android apps, but it isn't easy to implement. Android executes all its tasks by default on a single main UI thread, one task at a time. If this thread ever becomes blocked, your application is going to freeze, and may even crash.
If your application will ever be capable of performing one or more tasks in the background, you'll have to deal with multiple threads. Typically, this involves creating a background thread, performing some work on this thread, and posting the results back to Android's main UI thread. However, juggling multiple threads is a complex process that can quickly result in verbose code that's difficult to understand and prone to errors. Creating a thread is also an expensive process.
Several solutions aim to simplify multi-threading on Android, such as the RxJava library and AsyncTask, providing ready-made worker threads. Even with the help of third party libraries and helper classes, multi-threading on Android is still a challenge.
Let's take a look at coroutines, an experimental feature of the Kotlin programming language that promises to take the pain out of asynchronous programming on Android. You can use coroutines to quickly and easily create threads, assign work to different threads, and perform long-running tasks on any thread (even Android's main UI thread) without causing freezing or crashing your app.
Why should I use coroutines?
Learning any new technology takes time and effort, so before taking the plunge you'll want to know what's in it for you.
Despite still being classed as experimental, there's several reasons why coroutines are one of Kotlin's most talked-about features.
They're a lightweight alternative to threads
Think of coroutines as a lightweight alternative to threads. You can run thousands of them without any noticeable performance issues. Here we're launching 200,000 coroutines and telling them to print "Hello World":
fun main(args: Array<String>) = runBlocking<Unit> { //Launch 200,000 coroutines// val jobs = List(200_000) { launch { delay(1000L) print("Hello world") } } jobs.forEach { it.join() } } }
While the above code will run without any issues, spawning 200,000 threads will likely result in your application crashing with an OutOfMemory error.
Even though coroutines are commonly referred to as an alternative to threads, they don't necessarily replace them entirely. Threads still exist in an app based on coroutines. The key difference is that a single thread can run many coroutines, which helps keep your app's thread count under control.
Write your code sequentially, and let coroutines do the hard work!
Asynchronous code can quickly become complicated, but coroutines let you express the logic of your asynchronous code sequentially. Simply write your lines of code, one after the other, and the kotlinx-coroutines-core library will figure out the asynchrony for you.
Using coroutines, you can write asynchronous code as simply as if it were sequentially executed — even when it's performing dozens of operations in the background.
Avoid callback hell
Handling asynchronous code execution usually requires some form of callback. If you're performing a network call you'd typically implement onSuccess and onFailure callbacks. As callbacks increase, your code becomes more complex and difficult to read. Many developers refer to that problem as callback hell. Even if you've been dealing with asynchronous operations using the RxJava library, each RxJava call set usually ends with a few callbacks.
With coroutines you don't have to provide a callback for long-running operations. this results in more compact and less error-prone code. Your code will also be easier to read and maintain, as you won't have to follow a trail of callbacks to figure out what's actually going on.
It's flexible
Coroutines provide much more flexibility than plain reactive programming. They give you have the freedom to write your code in a sequential way when reactive programming isn't required. You can also write your code in a reactive programming style, using Kotlin's set of operators on collections.
Getting your project coroutine-ready
Android Studio 3.0 and higher comes bundled with the Kotlin plugin. To create a project that supports Kotlin, you simply need to select the "Include Kotlin support" checkbox in Android Studio's project creation wizard.
This checkbox adds basic Kotlin support to your project, but since coroutines are currently stored in a separate kotlin.coroutines.experimental package, you'll need to add a few additional dependencies:
dependencies { //Add Kotlin-Coroutines-Core// implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.22.5" //Add Kotlin-Coroutines-Android// implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.22.5"
Once coroutines are no longer considered experimental, they'll be relocated to the kotlin.coroutines package.
While coroutines still have experimental status, using any coroutine-related features will cause the Kotlin compiler to issue a warning. You can suppress this warning by opening your project's gradle.properties file and adding the following:
kotlin { experimental { coroutines "enable" } }
Creating your first coroutines
You can create a coroutine using either of the following coroutine builders:
Launch
The launch() function is one of the simplest ways to create a coroutine, so this is the method we'll be using throughout this tutorial. The launch() function creates a new coroutine and returns a Job object without an associated result value. Since you can't return a value from launch(), it's roughly equivalent to creating a new thread with a Runnable object.
In the following code, we're creating a coroutine, instructing it to delay for 10 seconds, and printing "Hello World" to Android Studio's Logcat.
import android.support.v7.app.AppCompatActivity import android.os.Bundle import kotlinx.coroutines.experimental.delay import kotlinx.coroutines.experimental.launch class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) launch { delay(10000) println("Hello world") } } }
This gives you the following output:
Async
Async() executes the code inside its block asynchronously, and returns a result via Deferred<T>, a non-blocking future that promises to provide a result later. You can get a Deferred's result using the await() function, which allows you to suspend the execution of the coroutine until the asynchronous operation completes.
Even if you call await() on the main UI thread, it won't freeze or crash your app because only the coroutine is suspended, not the whole thread (we'll be exploring this more in the following section). Once the asynchronous operation inside async() completes, the coroutine is resumed and can continue as normal.
fun myAsyncCoroutine() { launch { //We'll be looking at CommonPool later, so ignore this for now// val result = async(CommonPool) { //Do something asynchronous// }.await() myMethod(result) } }
Here, myMethod(result) is executed with the result of the asynchronous operation (the result returned by the block of code inside async) without having to implement any callbacks.
Replace thread blocking with coroutine suspension
Many long-running operations, such as network I/O, require the caller to block until they complete. When a thread is blocked it's unable to do anything else, which can make your app feel sluggish. At worst it may even result in your application throwing an Application Not Responding (ANR) error.
Coroutines introduce suspension of a coroutine as an alternative to thread blocking. While a coroutine is suspended, the thread is free to continue doing other things. You could even suspend a coroutine on Android's main UI thread without causing your UI to become unresponsive.
The catch is that you can only suspend a coroutine's execution at special suspension points, which occur when you invoke a suspending function. A suspending function can only be called from coroutines and other suspending functions — if you try to call one from your "regular" code, you'll encounter a compilation error.
Every coroutine must have at least one suspending function that you pass to the coroutine builder. For the sake of simplicity, throughout this article I'll be using Delay() as our suspending function, which intentionally delays the program's execution for the specified amount of time, without blocking the thread.
Let's look at an example of how you can use the Delay() suspending function to print "Hello world" in a slightly different way. In the following code we're using Delay() to suspend the coroutine's execution for two seconds, and then printing "World." While the coroutine is suspended, the thread is free to continue executing the rest of our code.
import android.support.v7.app.AppCompatActivity import android.os.Bundle import kotlinx.coroutines.experimental.delay import kotlinx.coroutines.experimental.launch class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) launch { //Wait for 2 seconds/// delay(2000L) //After the delay, print the following// println("world") } //The thread continues while the coroutine is suspended// println("Hello") Thread.sleep(2000L) } }
The end result, is an app that prints "Hello" to Android Studio's Logcat, waits two seconds, and then prints "world."
In addition to Delay(), the kotlinx.coroutines library defines a number of suspending functions that you can use in your projects.
Under the hood, a suspending function is simply a regular function that's marked with the "suspend" modifier. In the following example, we're creating a sayWorld suspending function:
import android.support.v7.app.AppCompatActivity import android.os.Bundle import kotlinx.coroutines.experimental.launch class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) launch { sayWorld() } println("Hello") } suspend fun sayWorld() { println("world!") } }
Switching threads with coroutines
Apps based on coroutines still use threads, so you'll want to specify which thread a coroutine should use for its execution.
You can restrict a coroutine to Android's main UI thread, create a new thread, or dispatch a coroutine to a thread pool using coroutine context, a persistent set of objects you can attach to a coroutine. If you imagine coroutines as lightweight threads, then the coroutine context is like a collection of thread-local variables.
All coroutine builders accept a CoroutineDispatcher parameter, which lets you control the thread a coroutine should use for its execution. You can pass any of the following CoroutineDispatcher implementations to a coroutine builder.
CommonPool
The CommonPool context confines the coroutine to a separate thread, which is taken from a pool of shared background threads.
import android.support.v7.app.AppCompatActivity import android.os.Bundle import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.launch class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) launch(CommonPool) { println("Hello from thread ${Thread.currentThread().name}") } } }
Run this app on an Android Virtual Device (AVD) or physical Android smartphone or tablet. Then look at Android Studio's Logcat and you should see the following message:
I/System.out: Hello from thread ForkJoinPool.commonPool-worker-1
If you don't specify a CoroutineDispatcher, the coroutine will use CommonPool by default. To see this in action, remove the CommonPool reference from your app:
import android.support.v7.app.AppCompatActivity import android.os.Bundle import kotlinx.coroutines.experimental.launch class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) launch { println("Hello from thread ${Thread.currentThread().name}") } } }
Re-run this project, and Android Studio's Logcat will display the exact same greeting:
I/System.out: Hello from thread ForkJoinPool.commonPool-worker-1
Currently, if you want to execute a coroutine off the main thread you don't have to specify the context, as coroutines run in CommonPool by default. There's always a chance default behavior may change, so you should still be explicit about where you want a coroutine to run.
newSingleThreadContext
The newSingleThreadContext function creates a thread where the coroutine will run:
import android.support.v7.app.AppCompatActivity import android.os.Bundle import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.newSingleThreadContext class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) launch(newSingleThreadContext("MyThread")) { println("Hello from thread ${Thread.currentThread().name}") } } }
If you use newSingleThreadContext, make sure your app doesn't consume unnecessary resources by releasing this thread as soon as it's no longer needed.
UI
You can only access Android's view hierarchy from the main UI thread. Coroutines run on CommonPool by default, but if you try to modify the UI from a coroutine running on one of these background threads, you'll get a runtime error.
To run code on the main thread, you need to pass the "UI'"object to the coroutine builder. In the following code, we're performing some work on a separate thread using launch(CommonPool), and then calling launch() to trigger another coroutine, which will run on Android's main UI thread.
import android.support.v7.app.AppCompatActivity import android.os.Bundle import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.experimental.launch class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) launch(CommonPool){ //Perform some work on a background thread// println("Hello from thread ${Thread.currentThread().name}") } //Switch to the main UI thread// launch(UI){ println("Hello from thread ${Thread.currentThread().name}") } } }
Check Android Studio's Logcat output, and you should see the following:
Cancelling a coroutine
Although coroutines have lots of positive to offer, memory leaks and crashes can still be a problem if you fail to stop long-running background tasks when the associated Activity or Fragment is stopped or destroyed. To cancel a coroutine, you need to call the cancel() method on the Job object returned from the coroutine builder (job.cancel). If you just want to cancel the acronymous operation inside a coroutine, you should call cancel() on the Deferred object instead.
Wrapping up
So that's what you need to know to start using Kotlin's coroutines in your Android projects. I showed you how to create a range of simple coroutines, specify the thread where each of these coroutines should execute, and how to suspend coroutines without blocking the thread.
Do you think coroutines have the potential to make asynchronous programming in Android easier? Do you already have a tried-and-true method for giving your apps the ability to multi-task? Let us know in the comments below!
from Android Authority https://ift.tt/2jr98Og
via IFTTT
Aucun commentaire:
Enregistrer un commentaire