前言
Kotlin是一種在Java虛擬機上運行的靜態(tài)類型編程語言,被稱之為Android世界的Swift,在GoogleI/O2017中,Google宣布Kotlin成為Android官方開發(fā)語言

delay
delay是一個頂級函數(shù),由于它被suspend修飾,所以只能用在協(xié)程或者被其他suspend函數(shù)修飾,它的功能為
將當前協(xié)程延遲一個給定時間,但是不會阻塞當前線程 并且它也是可以被取消的
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)
}
}
我們再看下另一個會延遲的函數(shù)Thread.sleep()它會導(dǎo)致 當前線程進行休眠
/**
* 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é)程中使用它們,會有不同的效果,如果我們的協(xié)程都在同一個線程
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時打印,當前線程沒有阻塞會執(zhí)行Coroutine2
Coroutine1 start
Coroutine2 start
//兩秒后
Coroutine1 end
Coroutine2 end
上述的代碼在使用Thread.sleep()時打印
Coroutine1 start
//兩秒后
Coroutine1 end
Coroutine2 start
//兩秒后
Coroutine2 end
很容易看到這兩種用法的區(qū)別,并且當我們在協(xié)程中時如果使用了阻塞線程的Thread.sleep()也會有警告提示Inappropriate blocking method call 提示你使用了不適當?shù)淖枞椒?,因為線程阻塞會導(dǎo)致其他協(xié)程無法執(zhí)行,會影響其他協(xié)程,delay表示的是非阻塞調(diào)用,不會阻塞當前線程
非阻塞式掛起
我們很容易理解阻塞與非阻塞的區(qū)別,從行為上來講,就是是否擋住了你這條線程的后續(xù)執(zhí)行,如果擋住了就是阻塞,沒有擋住就是非阻塞,那么掛起是什么,掛起的行為其實就是切換了線程的工作
MainScope().launch {
delay(2000)
}
MainScope().launch {
delay(2000)
}
在上述例子中,第一個協(xié)程創(chuàng)建完成之后就會被掛起,主線程的執(zhí)行由當前協(xié)程切換到了下一個協(xié)程,當掛起兩秒之后再將主線程切換回了第一個協(xié)程繼續(xù)工作,掛起其實也就是一種協(xié)程的暫停行為,不會線程中的其他單元
suspend
suspend是協(xié)程中很重的關(guān)鍵字,它用來修飾函數(shù),表示此函數(shù)是一個會掛起的函數(shù),并且 掛起函數(shù)只有在協(xié)程中使用或者被另一個掛起函數(shù)調(diào)用,可以暫停和進行恢復(fù),什么情況下需要用到掛起函數(shù)
- 線程切換,掛起本身是線程切換不同的協(xié)程去工作,所以當需要進行線程切換時可以使用掛起函數(shù)
- 延時,暫停往往代表在等待一些結(jié)果,當我們在等待一些返回結(jié)果時,協(xié)程可以通過掛起的方式等待,而不阻塞線程
suspend只是對函數(shù)的一個標識別,它不像inline,refied等關(guān)鍵字一樣會對代碼造成影響,而是提醒使用者這是一個掛起函數(shù),具體的掛起業(yè)務(wù)還是需要函數(shù)內(nèi)部自己實現(xiàn)
withContext
withContext是一個掛起函數(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
需要傳入一個suspending 代碼塊,并且基于合并后的Context執(zhí)行環(huán)境,并且可以被取消,會返回代碼塊的執(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í)行的,但是實際卻在不同的線程
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 知識整理