前言
Kotlin
是一种在Java
虚拟机上运行的静态类型编程语言,被称之为Android
世界的Swift
,在Google
I/O2017中,Google
宣布Kotlin
成为Android
官方开发语言
delay
delay
是一个顶级函数,由于它被suspend
修饰,所以只能用在协程或者被其他suspend
函数修饰,它的功能为
将当前协程延迟一个给定时间,但是不会阻塞当前线程 并且它也是可以被取消的
public suspend fun delay(timeMillis: Long) {
if (timeMillis <= 0) return // don't delay
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation ->
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
我们再看下另一个会延迟的函数Thread.sleep()
它会导致 当前线程进行休眠
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds plus the specified
* number of nanoseconds, subject to the precision and accuracy of system
* timers and schedulers. The thread does not lose ownership of any
* monitors.
*/
public static void sleep(long millis, int nanos)
在协程中使用它们,会有不同的效果,如果我们的协程都在同一个线程
fun test() {
MainScope().launch {
Log.e("Mike","Coroutine1 start")
delay(2000)
Log.e("Mike","Coroutine1 end")
}
MainScope().launch {
Log.e("Mike","Coroutine2 start")
delay(2000)
Log.e("Mike","Coroutine2 end")
}
}
上述的代码在使用delay
时打印,当前线程没有阻塞会执行Coroutine2
Coroutine1 start
Coroutine2 start
//两秒后
Coroutine1 end
Coroutine2 end
上述的代码在使用Thread.sleep()
时打印
Coroutine1 start
//两秒后
Coroutine1 end
Coroutine2 start
//两秒后
Coroutine2 end
很容易看到这两种用法的区别,并且当我们在协程中时如果使用了阻塞线程的Thread.sleep()
也会有警告提示Inappropriate blocking method call
提示你使用了不适当的阻塞方法,因为线程阻塞会导致其他协程无法执行,会影响其他协程,delay
表示的是非阻塞调用,不会阻塞当前线程
非阻塞式挂起
我们很容易理解阻塞与非阻塞的区别,从行为上来讲,就是是否挡住了你这条线程的后续执行,如果挡住了就是阻塞,没有挡住就是非阻塞,那么挂起是什么,挂起的行为其实就是切换了线程的工作
MainScope().launch {
delay(2000)
}
MainScope().launch {
delay(2000)
}
在上述例子中,第一个协程创建完成之后就会被挂起,主线程的执行由当前协程切换到了下一个协程,当挂起两秒之后再将主线程切换回了第一个协程继续工作,挂起其实也就是一种协程的暂停行为,不会线程中的其他单元
suspend
suspend
是协程中很重的关键字,它用来修饰函数,表示此函数是一个会挂起的函数,并且 挂起函数只有在协程中使用或者被另一个挂起函数调用,可以暂停和进行恢复,什么情况下需要用到挂起函数
- 线程切换,挂起本身是线程切换不同的协程去工作,所以当需要进行线程切换时可以使用挂起函数
- 延时,暂停往往代表在等待一些结果,当我们在等待一些返回结果时,协程可以通过挂起的方式等待,而不阻塞线程
suspend
只是对函数的一个标识别,它不像inline,refied
等关键字一样会对代码造成影响,而是提醒使用者这是一个挂起函数,具体的挂起业务还是需要函数内部自己实现
withContext
withContext
是一个挂起函数,表明它只能在协程或者其他suspend
函数调用
/**
* Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns
* the result.
*
* The resulting context for the [block] is derived by merging the current [coroutineContext] with the
* specified [context] using `coroutineContext + context` (see [CoroutineContext.plus]).
* This suspending function is cancellable. It immediately checks for cancellation of
* the resulting context and throws [CancellationException] if it is not [active][CoroutineContext.isActive].
*
* This function uses dispatcher from the new context, shifting execution of the [block] into the
* different thread if a new dispatcher is specified, and back to the original dispatcher
* when it completes. Note that the result of `withContext` invocation is
* dispatched into the original context in a cancellable way, which means that if the original [coroutineContext],
* in which `withContext` was invoked, is cancelled by the time its dispatcher starts to execute the code,
* it discards the result of `withContext` and throws [CancellationException].
*/
public suspend fun withContext
需要传入一个suspending
代码块,并且基于合并后的Context
执行环境,并且可以被取消,会返回代码块的执行结果,在suspending
代码块执行完毕之后有切换回来
fun test() {
MainScope().launch {
Log.e("Mike", "Coroutine start")
val result = withContext(Dispatchers.IO) {
delay(2000)
"resposne data"
}
Log.e("Mike", "Coroutine end $result")
}
}
打印结果
Coroutine start 主线程
两秒后
Coroutine end resposne data 主线程
可以提取出suspend
函数,这样我们的代码看起来是同步单线程执行的,但是实际却在不同的线程
fun test() {
MainScope().launch {
Log.e("Mike", "Coroutine start") //主线程
val result = getData() //getData在IO线程
Log.e("Mike", "Coroutine end $result") //主线程
}
}
suspend fun getData() = withContext(Dispatchers.IO) {
delay(2000)
"resposne data"
}
suspend使用案例
文件读取
private val scope = CoroutineScope(Dispatchers.Main)
scope.launch {
Toast.makeText(this@MainActivity, "start...", Toast.LENGTH_SHORT).show()
val result = readFile()
Toast.makeText(this@MainActivity, "end..." + result, Toast.LENGTH_LONG).show()
}
suspend fun readFile() = withContext(Dispatchers.IO) {
val inp = assets.open("a.txt")
val isr = InputStreamReader(inp)
isr.readText().also {
inp.close()
isr.close()
}
}
网络请求
引入Okhttp
implementation("com.squareup.okhttp3:okhttp:4.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.0")
封装OkhttpClient
object OkHttpManager {
fun getOkhttpClient() = OkHttpClient.Builder()
.addNetworkInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.build()
}
封装Sevice
class RequestService(val okhttpClient: OkHttpClient) {
suspend fun getInfo() = withContext(Dispatchers.IO) {
val url = "https://www.baidu.com".toHttpUrl().newBuilder().build()
val request = Request.Builder().url(url).build()
okhttpClient.newCall(request).execute().body?.string()
}
}
发起调用
scope.launch {
Toast.makeText(this@MainActivity, "start...", Toast.LENGTH_SHORT).show()
val result = RequestService(OkHttpManager.getOkhttpClient()).getInfo()
Toast.makeText(this@MainActivity, "end..." + result, Toast.LENGTH_LONG).show()
}
欢迎关注Mike的
Android 知识整理