前言
Kotlin
是一種在Java
虛擬機(jī)上運(yùn)行的靜態(tài)類型編程語言势腮,被稱之為Android
世界的Swift
丹壕,在Google
I/O2017中抵赢,Google
宣布Kotlin
成為Android
官方開發(fā)語言
delay
delay
是一個(gè)頂級函數(shù)粗井,由于它被suspend
修飾,所以只能用在協(xié)程或者被其他suspend
函數(shù)修飾肄梨,它的功能為
將當(dāng)前協(xié)程延遲一個(gè)給定時(shí)間,但是不會(huì)阻塞當(dāng)前線程 并且它也是可以被取消的
public suspend fun delay(timeMillis: Long) {
if (timeMillis <= 0) return // don't delay
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
我們再看下另一個(gè)會(huì)延遲的函數(shù)Thread.sleep()
它會(huì)導(dǎo)致 當(dāng)前線程進(jìn)行休眠
/**
* 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)
在協(xié)程中使用它們膘格,會(huì)有不同的效果峭范,如果我們的協(xié)程都在同一個(gè)線程
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
時(shí)打印财松,當(dāng)前線程沒有阻塞會(huì)執(zhí)行Coroutine2
Coroutine1 start
Coroutine2 start
//兩秒后
Coroutine1 end
Coroutine2 end
上述的代碼在使用Thread.sleep()
時(shí)打印
Coroutine1 start
//兩秒后
Coroutine1 end
Coroutine2 start
//兩秒后
Coroutine2 end
很容易看到這兩種用法的區(qū)別瘪贱,并且當(dāng)我們在協(xié)程中時(shí)如果使用了阻塞線程的Thread.sleep()
也會(huì)有警告提示Inappropriate blocking method call
提示你使用了不適當(dāng)?shù)淖枞椒ǎ驗(yàn)榫€程阻塞會(huì)導(dǎo)致其他協(xié)程無法執(zhí)行辆毡,會(huì)影響其他協(xié)程菜秦,delay
表示的是非阻塞調(diào)用,不會(huì)阻塞當(dāng)前線程
非阻塞式掛起
我們很容易理解阻塞與非阻塞的區(qū)別舶掖,從行為上來講球昨,就是是否擋住了你這條線程的后續(xù)執(zhí)行,如果擋住了就是阻塞眨攘,沒有擋住就是非阻塞主慰,那么掛起是什么,掛起的行為其實(shí)就是切換了線程的工作
MainScope().launch {
delay(2000)
}
MainScope().launch {
delay(2000)
}
在上述例子中鲫售,第一個(gè)協(xié)程創(chuàng)建完成之后就會(huì)被掛起共螺,主線程的執(zhí)行由當(dāng)前協(xié)程切換到了下一個(gè)協(xié)程,當(dāng)掛起兩秒之后再將主線程切換回了第一個(gè)協(xié)程繼續(xù)工作情竹,掛起其實(shí)也就是一種協(xié)程的暫停行為藐不,不會(huì)線程中的其他單元
suspend
suspend
是協(xié)程中很重的關(guān)鍵字,它用來修飾函數(shù)秦效,表示此函數(shù)是一個(gè)會(huì)掛起的函數(shù)雏蛮,并且 掛起函數(shù)只有在協(xié)程中使用或者被另一個(gè)掛起函數(shù)調(diào)用,可以暫停和進(jìn)行恢復(fù)阱州,什么情況下需要用到掛起函數(shù)
- 線程切換挑秉,掛起本身是線程切換不同的協(xié)程去工作,所以當(dāng)需要進(jìn)行線程切換時(shí)可以使用掛起函數(shù)
- 延時(shí)苔货,暫停往往代表在等待一些結(jié)果犀概,當(dāng)我們在等待一些返回結(jié)果時(shí),協(xié)程可以通過掛起的方式等待蒲赂,而不阻塞線程
suspend
只是對函數(shù)的一個(gè)標(biāo)識(shí)別阱冶,它不像inline,refied
等關(guān)鍵字一樣會(huì)對代碼造成影響,而是提醒使用者這是一個(gè)掛起函數(shù)滥嘴,具體的掛起業(yè)務(wù)還是需要函數(shù)內(nèi)部自己實(shí)現(xiàn)
withContext
withContext
是一個(gè)掛起函數(shù)木蹬,表明它只能在協(xié)程或者其他suspend
函數(shù)調(diào)用
/**
* 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 <T> withContext
需要傳入一個(gè)suspending
代碼塊,并且基于合并后的Context
執(zhí)行環(huán)境,并且可以被取消镊叁,會(huì)返回代碼塊的執(zhí)行結(jié)果尘颓,在suspending
代碼塊執(zhí)行完畢之后有切換回來
fun test() {
MainScope().launch {
Log.e("Mike", "Coroutine start")
val result = withContext(Dispatchers.IO) {
delay(2000)
"resposne data"
}
Log.e("Mike", "Coroutine end $result")
}
}
打印結(jié)果
Coroutine start 主線程
兩秒后
Coroutine end resposne data 主線程
可以提取出suspend
函數(shù),這樣我們的代碼看起來是同步單線程執(zhí)行的晦譬,但是實(shí)際卻在不同的線程
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()
}
}
網(wǎng)絡(luò)請求
引入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()
}
}
發(fā)起調(diào)用
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()
}
歡迎關(guān)注Mike的簡書
Android 知識(shí)整理