Kotlin協(xié)程進(jìn)階使用
Kotlin協(xié)程在工作中有用過(guò)嗎? (qq.com)
1.kotlin和java編譯后都是java字節(jié)碼早处,相對(duì)于java有啥優(yōu)勢(shì)和劣勢(shì)捷雕?
(1).kotlin優(yōu)勢(shì)
代碼簡(jiǎn)潔椒丧,開發(fā)效率更高
不用使用findViewById,支減少冗余代碼救巷,支持空安全壶熏,擴(kuò)展函數(shù),屬性浦译,高階函數(shù)和lambda表達(dá)式棒假,內(nèi)聯(lián)函數(shù)。內(nèi)存消耗更少
由于空安全特性和內(nèi)聯(lián)函數(shù)優(yōu)化精盅,它能夠生成更高效的字節(jié)碼帽哑,從而減少內(nèi)存的使用。kotlin協(xié)程提供了一中輕量級(jí)的并發(fā)處理方式渤弛,可以進(jìn)一步降低內(nèi)存的占用祝拯。kotlin是未來(lái)Android開發(fā)主流的語(yǔ)言,一些Android的新特性,新組件都會(huì)優(yōu)先支持kotlin版本佳头。
(2).kotlin劣勢(shì)
kotlin編譯速度相對(duì)java較慢鹰贵,因?yàn)樗枰M(jìn)行額外的類型檢查(空安全),和代碼轉(zhuǎn)換(kotlin-java-java字節(jié)碼康嘉,復(fù)雜碉输,耗性能)
備注:Kotlin與Java在運(yùn)行時(shí)性能方面基本相當(dāng),由于kotlin支持inline函數(shù)亭珍,lambda表達(dá)式敷钾,在某些情況下性能還要由于java.
2.Kotlin內(nèi)置的高階函數(shù)run的原理是什么,與let函數(shù)有啥區(qū)別肄梨?
(1).inline :表示這是一個(gè)內(nèi)聯(lián)函數(shù)阻荒,將函數(shù)代碼直接插入調(diào)用處,避免了調(diào)用普通方法時(shí)众羡,棧幀的創(chuàng)建侨赡,銷毀所帶來(lái)的開銷。
(2).<T,R> T.run :T代表要為T擴(kuò)展出一個(gè)名為run的函數(shù)粱侣,R表示lambda表達(dá)式最后一行返回的類型羊壹。
(3).block:T.()->R:
block:表示lambda的名稱
T.():表示輸入?yún)?shù)是T本身,讓lambda表達(dá)式持有了this表示(T)調(diào)用者本身
R:表示lambda最后一行返回的類型.
(T) :表示輸入?yún)?shù)是T本身齐婴,讓lambda表達(dá)式持有了it表示(T)調(diào)用者本身
public inline fun<T,R> T.let(block:(T)->R){
return block(this)
}
public inline fun<T,R> T.run(block:T.()->R):R{
return block()
}
//with不是泛型的擴(kuò)展方法油猫,而是全局方法
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
public inline fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this
}
public inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
fun main(){
var result1="778".run{
true
length
}
println(result1) //3
var result2="778".let{
999
"$it" //778
}
}
為啥所有的類型都可以使用run或者let,那是因?yàn)楦唠A函數(shù)let和run是對(duì)泛型進(jìn)行擴(kuò)展柠偶,意味著所有類型都等于泛型情妖,所以任何地方都可以使用.run和let的區(qū)別在于lambda表達(dá)式一個(gè)持有this表示調(diào)用者本身,一個(gè)持有it表示調(diào)用者本身嚣州。
public inline <T> T.apply(block:T.()->unit):T
public inline <T> T.also(block:(T)->unit):T
public int <T,R> with(receiver:T,block:T.()->R):R
3.kotlin語(yǔ)言泛型的形變是什么鲫售?【PECS原則 extends out || super in】
- 不變:指的是此泛型既可以是消費(fèi)者共螺,可以是生產(chǎn)者该肴,沒(méi)有任何繼承相關(guān)的概念.
class Student<T> {}
- 協(xié)變 : 指的是此泛型只能是生產(chǎn)者 ,泛型類型只能是T或者T的子類藐不,只能get數(shù)據(jù)匀哄,無(wú)法add數(shù)據(jù).
ArrayList<out T>
- 逆變:指的是此泛型只能是消費(fèi)者,泛型類型只能是T類型雏蛮,或者T的父類涎嚼,只能add數(shù)據(jù),無(wú)法get【如果get數(shù)據(jù)挑秉,數(shù)據(jù)類型只能是Object】
ArrayList<in T>
4.協(xié)程的基本使用.
(1).啟動(dòng)協(xié)程的幾種方式.
- launch{} CoroutineScope接口的擴(kuò)展方法法梯,啟動(dòng)一個(gè)攜程,不阻塞當(dāng)前線程,返回一個(gè)Job對(duì)象協(xié)程.
public fun CoroutineScope.launch(
context:CoroutineScope=EmptyCoroutineContext,
start:CoroutineStart = CoroutineStart.DEFAULT,
block:suspend CoroutineScope.()->Unit
):Job { //Job是返回值立哑, {} 是launch的方法體實(shí)現(xiàn) 【不要搞混淆了】
//launch方法體實(shí)現(xiàn)夜惭,返回Job對(duì)象
val newcontenxt=newCoroutineContext(context)
val coroutine=if(start.isLazy){
LazyStandaloneCoroutine(newContext,block);//實(shí)現(xiàn)了job接口
}else{
StandaloneCoroutine(newContext,active=true) //實(shí)現(xiàn)了job接口
}
coroutine.start(start,coroutine,block) //啟動(dòng)job
return coroutine;//返回實(shí)現(xiàn)了Job接口的協(xié)程對(duì)象
}
Job.cancel()可以用來(lái)取消協(xié)程铛绰,控制協(xié)程的生命周期.
- async{} CoroutienScope接口的擴(kuò)展方法诈茧,啟動(dòng)一個(gè)協(xié)程,不阻塞當(dāng)前線程捂掰,返回一個(gè)Deferred<T>類敢会,可以通過(guò)wait獲取T對(duì)象.
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
val coroutine= if (start.isLazy){
LazyDeferredCoroutine<T>(newContext, block) //實(shí)現(xiàn)了Deferred接口
} else{
DeferredCoroutine<T>(newContext, active = true) //實(shí)現(xiàn)了Deferred接口
}
coroutine.start(start, coroutine, block) //啟動(dòng)協(xié)程
return coroutine //返回實(shí)現(xiàn)了Deferred接口的協(xié)程對(duì)象
}
- runBlocking{} 全局方法,創(chuàng)建并啟動(dòng)協(xié)程,返回值是lambda表達(dá)式block的最后一行的返回值.(Unit或者其他具體類型)
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
val currentThread = Thread.currentThread()
val contextInterceptor = context[ContinuationInterceptor]
val eventLoop: EventLoop?
val newContext: CoroutineContext
if (contextInterceptor == null) {
eventLoop = ThreadLocalEventLoop.eventLoop
newContext = GlobalScope.newCoroutineContext(context + eventLoop)
} else {
eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
?: ThreadLocalEventLoop.currentOrNull()
newContext = GlobalScope.newCoroutineContext(context)
}
val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
return coroutine.joinBlocking()
}
//這一段代碼會(huì)阻塞主線程,直接導(dǎo)致黑屏这嚣。
runBlocking {
delay(10000)
}
//即使指定啟動(dòng)的協(xié)程運(yùn)行在IO線程鸥昏,也會(huì)阻塞主線程,導(dǎo)致黑屏
runBlocking(Dispatchers.IO) {
delay(10000L)
}
//依然會(huì)阻塞主線程導(dǎo)致黑屏
GlobalScope.launch(Dispatchers.Main){
runBlocking {
delay(10000)
}
}
//不會(huì)阻塞主線程
GlobalScope.launch(Dispatchers.IO){
runBlocking {
delay(10000)
}
}
(2).withContext() suspend修飾姐帚,并不會(huì)啟動(dòng)協(xié)程互广,只能在suspend掛起方法或者協(xié)程中調(diào)用,用于切換線程卧土,并掛起協(xié)程惫皱。(暫停協(xié)程,但是執(zhí)行協(xié)程的線程可以繼續(xù)執(zhí)行其他任務(wù)尤莺,不會(huì)阻塞旅敷,等到協(xié)程恢復(fù),該線程可以繼續(xù)執(zhí)行)
public suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T
lifecycleScope.launch(Dispatchers.IO) {
delay(1000)
withContext(Dispatchers.Main){ //返回值是Unit 也就是void //切換到主線程 颤霎,此時(shí)協(xié)程的IO線程可以去執(zhí)行其他任務(wù)
println("Test001:withContext:${Thread.currentThread().name}")
}
}
(3).coroutineScope() suspend修飾媳谁,并不會(huì)啟動(dòng)協(xié)程,只能在suspend掛起方法或者協(xié)程中調(diào)用友酱,創(chuàng)建一個(gè)新的協(xié)程作用域【或者說(shuō)可以在已有的協(xié)程內(nèi)部創(chuàng)建一個(gè)新協(xié)程】
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R =
suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = ScopeCoroutine(uCont.context, uCont)
coroutine.startUndispatchedOrReturn(coroutine, block)
}
coroutineScope { } //報(bào)錯(cuò) 晴音,不能直接用,只能在協(xié)程里面使用
runBlocking { //非suspend全局方法缔杉,直接啟動(dòng)協(xié)程
coroutineScope { } //正常使用锤躁,因?yàn)閞unBlocking創(chuàng)建了協(xié)程//創(chuàng)建一個(gè)新的協(xié)程作用域
delay(1000L)
}
(4).協(xié)程上下文,一般用調(diào)度器Dispatchers來(lái)切換線程或详,它是CoroutineContext接口的實(shí)現(xiàn)類.
- Dispatchers.Main 協(xié)程代碼執(zhí)行在Android主線程
- Dispatchers.Unconfined 不限制協(xié)程代碼執(zhí)行在哪個(gè)線程【主線程或者其他空閑線程】
- Dispatcher.Default JVM共享線程池分配線程執(zhí)行協(xié)程代碼
- Dispatcher.IO IO線程池分配線程執(zhí)行協(xié)程代碼
5.協(xié)程的掛起和阻塞
(1).suspend非阻塞式掛起函數(shù)
fun testCoroutineInActivity() {
GlobalScope.launch(Dispatchers.Main) { //1.啟動(dòng)協(xié)程1
println("Test001:執(zhí)行在協(xié)程中...")
GlobalScope.launch (Dispatchers.IO){//2.啟動(dòng)協(xié)程2
println("Test001:異步執(zhí)行result1")
delay(1000)
println("Test001:result1:1234")
}
GlobalScope.launch(Dispatchers.IO) {//3.啟動(dòng)協(xié)程3
println("Test001:異步執(zhí)行result2.")
delay(1000)
println("Test001:result2:123456")
}
println("Test001:執(zhí)行完畢...")
}
}
注意:協(xié)程2和協(xié)程3中的delay函數(shù)只是掛起了協(xié)程2和協(xié)程3系羞,不會(huì)影響協(xié)程1中的主線程的執(zhí)行.
執(zhí)行結(jié)果如下:
(2).協(xié)程都被掛起了,那么掛起函數(shù)的函數(shù)體由誰(shuí)執(zhí)行呢霸琴?
當(dāng)協(xié)程執(zhí)行到掛起函數(shù)式椒振,協(xié)程的執(zhí)行會(huì)被暫停(即協(xié)程被掛起),但它并不會(huì)阻塞執(zhí)行該掛起函數(shù)的線程梧乘,掛起函數(shù)的函數(shù)體任然由當(dāng)前線程繼續(xù)執(zhí)行澎迎。
為了更清楚的解釋這一點(diǎn),我們可以使用一下步驟:
(1).協(xié)程啟動(dòng):當(dāng)一個(gè)協(xié)程開始運(yùn)行時(shí),它會(huì)在某個(gè)線程(Dispatchers.Main)上執(zhí)行夹供。
(2).遇到掛起函數(shù):當(dāng)協(xié)程遇到掛起函數(shù)時(shí)辑莫,協(xié)程的執(zhí)行會(huì)被暫停。
(3).掛起函數(shù)執(zhí)行:盡管協(xié)程被暫停了罩引,掛起函數(shù)的函數(shù)體任然會(huì)在原始線程上執(zhí)行各吨。(除非該掛起函數(shù)明確指定了其他的執(zhí)行上下文)
(4).掛起函數(shù)完成:一旦掛起函數(shù)完成其工作,它會(huì)通知協(xié)程庫(kù)袁铐,然后協(xié)程會(huì)恢復(fù)執(zhí)行揭蜒。
注意:當(dāng)協(xié)程被掛起時(shí),主線程并沒(méi)有被阻塞剔桨,而是可以執(zhí)行其他的任務(wù)屉更,等到掛起函數(shù)執(zhí)行完畢,協(xié)程恢復(fù)時(shí)洒缀,又可以在主線程中繼續(xù)執(zhí)行瑰谜。
總結(jié)一下:在協(xié)程中使用掛起函數(shù)時(shí),任何可能得"阻塞"操作都會(huì)轉(zhuǎn)移到其他線程上執(zhí)行树绩,這樣啟動(dòng)協(xié)程的原始線程(例如主線程)就不會(huì)被實(shí)際阻塞萨脑,這樣使得協(xié)程特別適合UI線程編程,因?yàn)樗梢源_保UI線程保持響應(yīng)饺饭。
6.kotlin協(xié)程在工作中有用過(guò)嗎渤早?
kotlin協(xié)程是一個(gè)線程框架,提供了一種輕量級(jí)的并發(fā)處理方式瘫俊,通過(guò)非阻塞掛起和恢復(fù)實(shí)現(xiàn)了用同步代碼的方式編寫異步代碼鹊杖,把原本運(yùn)行在不同線程的代碼寫在一個(gè)代碼塊{}里面,看起來(lái)就像是同步代碼扛芽。
協(xié)程的目的是骂蓖,簡(jiǎn)化復(fù)雜的異步代碼邏輯,用同步的代碼編寫方式實(shí)現(xiàn)復(fù)雜異步代碼邏輯川尖。
(1).幾種封裝好的協(xié)程
-
CoroutineScope(Dispatchers.IO)構(gòu)造函數(shù)啟動(dòng)協(xié)程 ,通過(guò)Dispatchers指定運(yùn)行的線程登下。
-
GlobalScope啟動(dòng)協(xié)程(默認(rèn)不是主線程)
-
MainScope啟動(dòng)協(xié)程(默認(rèn)運(yùn)行在主線程)
-
viewModelScope啟動(dòng)協(xié)程(默認(rèn)運(yùn)行在主線程)
-
lifecycleScope啟動(dòng)協(xié)程(默認(rèn)運(yùn)行在主線程)
協(xié)程就是一個(gè)線程框架,是對(duì)線程的封裝空厌,提供了一種輕量并發(fā)的處理方式庐船,通過(guò)非阻塞式掛起和恢復(fù)的方式,用同步代碼的方編寫式實(shí)現(xiàn)復(fù)雜的異步代碼邏輯嘲更,把原本運(yùn)行在不同線程的代碼寫在一個(gè)代碼塊里面,看起來(lái)就像同步代碼揩瞪。
(2).如果一個(gè)頁(yè)面需要同時(shí)并發(fā)請(qǐng)求多個(gè)接口赋朦,當(dāng)所有的接口都請(qǐng)求完成需要做一些合并處理,然后更新UI,如何并發(fā)處理呢宠哄?
方法一:為每個(gè)接口設(shè)置一個(gè)boolean值壹将,每當(dāng)接口請(qǐng)求成功,boolean值設(shè)置為true,當(dāng)最后一個(gè)接口請(qǐng)求成功后毛嫉,在更新UI诽俯,這樣就達(dá)到了并發(fā)的目的 【管理多個(gè)boolean值,不夠優(yōu)雅承粤,太累了】
方法二:RXjava的zip操作符【達(dá)到發(fā)射一次暴区,將結(jié)果合并處理的目的】
fun testRxjavaZip(){
println("-------------")
val observable1: Observable<HttpResult<List<Banner>>> = HttpRetrofit.apiService.getBanners().subscribeOn(Schedulers.io()).observeOn(
AndroidSchedulers.mainThread())
val observable2:Observable<HttpResult<ArrayList<HomeData.DatasBean>>> =HttpRetrofit.apiService.getTopArticles().subscribeOn(Schedulers.io()).observeOn(
AndroidSchedulers.mainThread())
val observable3: Observable<HttpResult<List<KnowledgeData>>> = HttpRetrofit.apiService.getKnowledgeTree().subscribeOn(Schedulers.io()).observeOn(
AndroidSchedulers.mainThread())
var result1: HttpResult<List<Banner>>? =null
var result2: HttpResult<ArrayList<HomeData.DatasBean>>? =null
var result3: HttpResult<List<KnowledgeData>>? =null
Observable.zip(observable1, observable2, observable3,
object: Function3<
HttpResult<List<Banner>>,
HttpResult<ArrayList<HomeData.DatasBean>>,
HttpResult<List<KnowledgeData>>,
Boolean>{
override fun apply(t1: HttpResult<List<Banner>>, t2: HttpResult<ArrayList<HomeData.DatasBean>>, t3: HttpResult<List<KnowledgeData>>): Boolean {
result1=t1
result2=t2
result3=t3
return t1!=null&&t2!=null&&t3!=null
}
}).subscribe(object:Observer<Boolean>{
override fun onSubscribe(d: Disposable) {
}
override fun onError(e: Throwable) {
}
override fun onComplete() {
}
override fun onNext(t: Boolean) {
if(t){//對(duì)結(jié)果進(jìn)行處理
println("成功獲取結(jié)果");
println(Gson().toJson(result1))
println(Gson().toJson(result2))
println(Gson().toJson(result3))
}
}
});
}
- kotlin協(xié)程提供了一種輕量級(jí)的并發(fā)處理方式
fun testCoroutineInActivity() {
//1.launch啟動(dòng)協(xié)程,返回Job對(duì)象辛臊,通過(guò)job.cancel()取消任務(wù)
val job:Job=CoroutineScope(Dispatchers.IO).launch() {
println("Test001:查看運(yùn)行的線程1:"+Thread.currentThread().name)
val start= System.currentTimeMillis()
val result1=async {//運(yùn)行在主線程仙粱,非異步
println("Test001:查看運(yùn)行的線程2:"+Thread.currentThread().name)
delay(1000)
"123"
}
//2.async 啟動(dòng)協(xié)程,得到有返回值的Deferred對(duì)象
val result2:Deferred<String> = async(Dispatchers.IO) { //異步
println("Test001:查看運(yùn)行的線程3:"+Thread.currentThread().name)
delay(2000)
"456"
}
val result3=result1.await()+result2.await();
println("Test001:并發(fā)耗時(shí):"+ (System.currentTimeMillis() - start)+"||result3:"+result3)
withContext(Dispatchers.IO){
println("Test001:查看運(yùn)行的線程4:"+ Thread.currentThread().name)
delay(1000)
}
println("Test001:查看運(yùn)行的線程5:"+ Thread.currentThread().name)
}
}
- kotlin協(xié)程解決地獄回調(diào)問(wèn)題【先調(diào)用接口1獲取數(shù)據(jù)彻舰,然后拿到接口1的結(jié)果作為參數(shù)調(diào)用接口2】
// 注意:在真實(shí)開發(fā)過(guò)程中伐割,MainScope作用域用的非常常用
MainScope().launch(){ // 注意:此協(xié)程塊默認(rèn)是在UI線程中啟動(dòng)協(xié)程
// 下面的代碼看起來(lái)會(huì)以同步的方式一行行執(zhí)行(異步代碼同步獲取結(jié)果)
val token = apiService.getToken() // 網(wǎng)絡(luò)請(qǐng)求:IO線程,獲取用戶token
val user = apiService.getUser(token)// 網(wǎng)絡(luò)請(qǐng)求:IO線程刃唤,獲取用戶信息
nameTv.text = user.name // 更新 UI:主線程隔心,展示用戶名
val articleList = apiService.getArticleList(user.id)// 網(wǎng)絡(luò)請(qǐng)求:IO線程,根據(jù)用戶id獲取用戶的文章集合哦
articleTv.text = "用戶${user.name}的文章頁(yè)數(shù)是:${articleList.size}頁(yè)" // 更新 UI:主線程
}