What is a coroutine
The coroutine in Kotlin can be understood either as a lightweight thread or as a threading framework that allows developers to implement concurrent requirements in a non-concurrent coding manner. To simplify asynchronous programming, the asynchronous logic of the program can be expressed sequentially with the help of coroutines.
Coroutines and threads
- Threads are scheduled by the system, and the overhead of thread switching or blocking is high. Coroutines depend on threads, but they can be suspended without blocking threads.
- Thread execution and termination are scheduled by the operating system, but coroutines can manually control their execution and termination
How do I use coroutines
Add the dependent
Kotlin does not put coroutines into their standard library API, so if we want to use the coroutine functionality, we need to manually add the following dependencies. The second is required for Android development, and we will add them here as well.
implementation "Org. Jetbrains. Kotlinx: kotlinx coroutines -- core: 1.5.1." "
implementation "Org. Jetbrains. Kotlinx: kotlinx coroutines - android: 1.5.1." "
Copy the code
Create coroutines
Coroutines can be created in the following ways, which are described below:
1. GlobalScope.launch
Each time you create a top-level coroutine, which ends when the application finishes running.
fun testGlobalScope (a) {
GlobalScope.launch {
println("hello in coroutines")}//Thread.sleep(1000)
}
Copy the code
The above code has no output because the print statement has no chance to run. If you sleep the current thread for 1 second, a printout will be displayed.
Take a look at the following code:
fun testGlobalScope (a) {
GlobalScope.launch {
println("hello in coroutines")
The /** * delay() function is a non-blocking suspend function that suspends only the current coroutine */
delay(1000)
println("world in coroutines")}}Copy the code
Also, this part of the code will have no output, despite delay, because it is inside the coroutine. The actual running situation is that the coroutine never gets a chance to run.
2. runBlocking
The runBlocking function also creates a coroutine scope, but it guarantees that the current thread will block until all code and child coroutines in the coroutine scope have been executed. Note that the runBlocking function should normally only be used in a test environment, as using it in a formal environment can cause performance problems.
3. launch
Launch must run in a coroutine environment, which creates child coroutines under the scope of the current coroutine. The characteristic of subcoroutines is that if the coroutine in the outer scope terminates, all subcoroutines in that scope also terminate.
runBlocking {
launch {
println("hello")
delay(1000)
println("word")
}
launch {
println("hello2")
delay(1000)
println("word2")}}Copy the code
4.sync
The async function must be called from the coroutine scope. It creates a new child coroutine and returns a Deferred object. If we want to get the execution result of the async function block, we simply call the await() method of the Deferred object.
Once async is called, the code in the block immediately starts executing. When an await() method is called, if the code in the block is not finished executing, the await() method blocks the current coroutine until the result of the async function can be obtained
runBlocking {
var start = System.currentTimeMillis();
var result1 = async {
delay(1000)
5 + 5
}.await()
var result2 = async {
delay(1000)
5 + 5
}.await()
println("result is ${result1 + result2}.")
var end = System.currentTimeMillis();
println("cost ${end - start} ms.")}Copy the code
5. coroutineScope
The coroutineScope function is also a suspend function, so it can be called in any other suspend function. It inherits the scope of an external coroutine and creates a child coroutine. With this feature, we can provide coroutine scope to any pending function.
suspend fun printHello2(a)= coroutineScope {
launch {
println("Hello")
delay(1000)}}Copy the code
The coroutineScope function is also similar to the runBlocking function in that it guarantees that all code and child coroutines in its scope will be suspended until all code and child coroutines have been executed. But the coroutineScope function blocks only the current coroutine and does not affect other coroutines or any threads, so it does not cause any performance problems. As runBlocking suspends external threads, if you call it from the main thread, it may cause the interface to block, so it is not recommended to use it in real projects.
withContext
Threads can be cut with withContext as follows:
runBlocking {
var result = withContext(Dispatchers.Default) {
5 + 5
}
println(result)
}
Copy the code
WithContext must run in a coroutine scope or in a suspend function modified by suspend. In Android, the typical application scenario is to process time-consuming operations in child threads and then cut to the main thread to update the UI
withContext(Dispatchers.IO) {
// Switch the IO thread
}
/ /.. Execute in the UI thread
withContext(Dispatchers.IO) {
// Switch the IO thread
}
/ /.. Execute in the UI thread
withContext(Dispatchers.IO) {
// Switch the IO thread
}
/ /.. Execute in the UI thread
Copy the code
Hang up function
As the code logic in the coroutine scope becomes more complex, the code needs to be extracted into a separate function. A function declared by the suspend keyword is a suspend function, which indicates that the function body has coroutine scope. A method that is not decorated by suspend cannot participate in a coroutine task. A method decorated by suspend can only communicate with another method decorated by suspend in a coroutine.
- Suspended functions can only be called inside a coroutine, not outside it
- The essential difference between suspended functions and other functions is that suspended functions return asynchronously.
Termination of coroutines
If you want to terminate a coroutine after starting it, you can call the Cancel method.
var job = launch { for (i in 1.. 10) { println(i) delay(1000) } } job.cancel()Copy the code
When the launch function executes, it returns a Job object and can call the Cancel method if canceled. When async is used, we return a Deferred, which inherits from Job and can call Cancel directly.
var deferred = async {
delay(1000)
5 + 5
}
deferred.cancel()
//defered.await()
Copy the code
Calling the await method after calling the cancel method returns an error.
Coroutines and OKHttp
When we use coroutines and OKHttp for network communication, our code can also be very concise, as follows:
class WanAndroidFetcher {
suspend fun getBanner(a): NetResult<List<Banner>> {
val request = Request.Builder()
.url("https://www.wanandroid.com/banner/json")
.build()
val response = OkHttpClient().newCall(request).await()
// If the network request wasn't successful, throw an exception
if(! response.isSuccessful)throw HttpException(response)
returnwithContext(Dispatchers.IO) { response.body!! .use { body -> Gson().fromJson(body.string(),object: TypeToken<NetResult<List<Banner? >? >? >() {}.type) } } } }Copy the code
We define an okHttpExtensioins.kt file that extends the Call class by adding an await method with the following code:
suspend fun Call.await(a): Response = suspendCancellableCoroutine { continuation ->
enqueue(
object : Callback {
override fun onResponse(call: Call, response: Response) {
continuation.resume(response) {
// If we have a response but we're cancelled while resuming, we need to
// close() the unused response
if(response.body ! =null) {
response.closeQuietly()
}
}
}
override fun onFailure(call: Call, e: IOException) {
continuation.resumeWithException(e)
}
}
)
continuation.invokeOnCancellation {
try {
cancel()
} catch (t: Throwable) {
// Ignore cancel exception}}}Copy the code
When called:
private fun sendRequestWithOkHttp(a) {
runBlocking {
var data = WanAndroidFetcher().getBanner()
println("data: $data"); }}Copy the code
Coroutines and retrofit
The main thread cannot make network requests (coroutines are no exception)
We know that after Android 6.0, network requests are no longer allowed in the main thread. If you do, will encounter a android.os.Net workOnMainThreadException anomalies. We define the following interface:
interface NetApi {
@GET("banner/json")
suspend fun getBannerInfo(a):NetResult<List<Banner>>
@GET("banner/json")
fun getBannerNoSuspend(a): Call<NetResult<List<Banner>>>
companion object {
private const val BASE_URL = "https://www.wanandroid.com/"
private var service: NetApi? = null
fun getApi(a): NetApi {
if (null == service) {
val client = OkHttpClient.Builder().build()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
service = retrofit.create(NetApi::class.java)
}
returnservice!! }}}Copy the code
We call it in the main thread
runBlocking { NetApi.getApi().getBannerNoSuspend().execute() }
Copy the code
Will find the program error, throws android.os.Net workOnMainThreadException, although in runBlocking created a collaborators cheng environment, but getBannerNoSuspend method of the return value is a Call object, Calling the execute method on this instance does not involve a thread switch, so it still runs on the main thread, and the result is still an error.
If we call in the main thread is
runBlocking {
var data = NetApi.getApi().getBannerInfo()
println("data: ---> " + data)}Copy the code
The network request was sent successfully and returned correctly because Retrofit switched threads for us at the bottom.
Can not locate call Adapter for class java.lang.Object
Coroutine + Retrofit + LiveData combination
The current Android development official advocates using MVVM architecture, here we use jetpack ViewModel to write a Demo, here we omit the Repository layer, directly in the ViewModel to initiate network requests. The code is as follows:
class ResourceViewModel: ViewModel() {
private var bannerInfo = MutableLiveData<Result<NetResult<List<Banner>>>>()
fun getBannerInfo( bannerType: Int) {
viewModelScope.launch{
var result = try {
Result.success(NetApi.getApi().getBannerInfo())
} catch (e: Exception) {
e.printStackTrace()
Result.failure(e)
}
bannerInfo.postValue(result)
}
}
}
Copy the code
To create a coroutine environment in a ViewModel, use viewModelScope.launch.
Network requests are processed using coroutine + RetroFIT
Because I want to understand the best practice of using coroutines to handle network requests, I have recently looked at many implementations of this area in open source projects. Generally speaking, they fall into the following categories:
Suspend function
Based on Retrofit 2.6
This is how we used the example above
@GET("banner/json")
suspend fun getBannerInfo(a):NetResult<List<Banner>>
Copy the code
In use, directly with the following code, very convenient
NetApi.getApi().getBannerInfo()
Copy the code
This approach requires our Retrofit version 2.6 and above
async + Deferred
Based on Retrofit version 2.4
@GET("banner/json")
fun getBanner(a): Deferred<NetResult<List<Banner>>>
Copy the code
When used (Retrofit helps us cut threads) :
var data = async {
NetApi.getApi().getBannerAsync().await()
}
println("data: ---> " + data.await().data)
Copy the code
When creating a retrofit instance, you need to specify the CoroutineCallAdapterFactory this way, the only know, is now rarely used
val client = OkHttpClient.Builder().build()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
service = retrofit.create(NetApi::class.java)
Copy the code
retrofit + flow
Flows in Kotlin can replace the functionality of RXJava, and flow is very simple to use. Our repository layer might be written like this (directly sending network requests).
class UserRepository(private val service: WanAndroidService) {
suspend fun loginFlow(userName: String, password: String) = service.login(userName, password)
}
Copy the code
If we want to use the network request flow package, convenient later data processing, we can write like this
class UserRepository(private val service: WanAndroidService) {
suspend fun loginFlow(userName: String, password: String) = flow {
var data = service.login(userName, password)
emit(data)}}Copy the code
Of course, if there is no complex data flow operation in the project, it is possible to use coroutine + LiveData directly to handle business requirements without introducing flow.
reference
- Proandroiddev.com/kotlin-coro…
- www.jianshu.com/p/76d2f47b9…
- Github.com/guolindev/S…
- Mp.weixin.qq.com/s?__biz=MzU…
- Flow is officially recommended to replace LiveData, is it necessary juejin.cn/post/698626…
- Juejin. Cn/post / 686035…