Kotlin協程實戰對話?
?真題1:協程與線程的本質區別是什么?為什么說協程是輕量級的???
?面試官?:
“我看你項目中用協程替代了線程池,能說說協程和線程的核心區別嗎?為什么協程更適合高并發?”
?候選人?:
“協程和線程的區別有點像‘快遞員’和‘卡車’的關系。線程是操作系統直接管理的‘大卡車’,每輛卡車得自己帶一整個貨箱(1MB的棧內存),啟動和換路線得經過調度中心(內核態),成本高還慢。而協程更像是快遞員,他們騎電動車(用戶態調度),一輛卡車能裝幾千個快遞員,換任務時只需要記下當前位置,輕裝上陣,內存開銷只有幾十KB。
比如我們做電商秒殺,10萬用戶同時搶購。如果用線程池,開500個線程就得吃掉500MB內存,線程切換還得排隊等調度,CPU都忙不過來。換成協程,一個線程就能跑幾萬個請求,內存省了90%,QPS直接翻倍——這就像用電動車送快遞,不堵車還省油。”
?真題2:GlobalScope為什么會導致內存泄漏?如何正確使用作用域???
?面試官?:
“你們項目里把GlobalScope全換成了lifecycleScope,是踩過坑吧?”
?候選人?:
“沒錯!之前做視頻彈幕功能時,用GlobalScope啟動了一個無限循環的彈幕請求。結果用戶退出了頁面,協程還在后臺瘋狂拉數據,Fragment像僵尸一樣賴在內存里,直接導致OOM崩潰。后來發現GlobalScope是‘長生不老’的,它的生命周期和整個App綁定,根本不管Activity的死活。
現在我們用lifecycleScope,相當于給協程裝了‘智能開關’。Activity銷毀時,自動觸發onDestroy
里的取消邏輯。就像給電器裝了個漏電保護器——頁面一關,所有后臺任務立馬斷電,內存泄漏風險直接清零。
比如在ViewModel里這么寫:
viewModelScope.launch { val data = withContext(Dispatchers.IO) { fetchData() } _liveData.value = data //自動綁定到ViewModel生命周期
}
就算用戶瘋狂滑動頁面,舊的請求也會被及時取消,再也不用擔心后臺跑‘幽靈任務’了。”
?真題3:如何處理協程中的并發任務?async和launch有什么區別???
?面試官?:
“如果要同時調三個接口,等結果全到了再更新UI,用協程怎么搞?如果有一個接口掛了怎么辦?”
?候選人?:
“這得請出‘協程三劍客’——async
、await
和coroutineScope
。比如用戶主頁需要同時拉取用戶信息、訂單列表和消息通知,可以這么寫:
lifecycleScope.launch {try {val (user, orders, messages) = coroutineScope {val userDeferred = async { api.getUser() } // 并行啟動val ordersDeferred = async { api.getOrders() }val messagesDeferred = async { api.getMessages() }Triple(userDeferred.await(), ordersDeferred.await(), messagesDeferred.await())}updateUI(user, orders, messages) // 三個結果都到了才更新} catch (e: Exception) {showErrorToast("有一個接口掛了:${e.message}") // 任一失敗都會跳到這里}
}
這里的關鍵是coroutineScope
會‘一損俱損’——只要有一個子協程拋異常,整個作用域里的任務全取消。而async和launch的核心區別在于‘帶不帶回執’——async返回Deferred對象(類似快遞單號),需要用await獲取結果,適合需要聚合數據的場景;launch則像‘寄平郵’,適合日志上報等無需返回值的任務
?真題4:協程的掛起函數底層是如何實現的???
?面試官?:
“你說delay(1000)不阻塞線程,那協程怎么做到‘暫停而不卡死’的?”
?候選人?:
“這得看Kotlin編譯器的‘魔法’——CPS變換和狀態機。比如這個掛起函數:
suspend fun fetchData(): String {delay(1000) // 掛起點1return "Data" // 掛起點2
}
編譯后會變成‘代碼樂高’:
Object fetchData(Continuation $completion) {switch (label) {case 0: delay(1000, $completion); // 記錄位置1label = 1;return COROUTINE_SUSPENDED; // 掛起case 1: return "Data"; // 從位置1恢復}
}
Continuation
就像書簽,記錄執行到哪里了。當delay
觸發時,協程把書簽交給線程:‘你先去忙別的,1秒后喊我’。這時候線程騰出手來處理UI點擊或者別的請求,完全不卡頓。時間一到,線程通過resume()
把書簽插回去,繼續執行——整個過程像接力賽,而不是傻站著等。”
?真題5:如何用協程優化RecyclerView的圖片加載???
?面試官?:
“列表快速滑動時圖片加載卡頓,你們怎么用協程解決的?”
?候選人?:
“傳統方案在onBindViewHolder
里直接開線程,用戶一滑到底,幾百個請求把線程池擠爆。我們給每個Item綁定獨立的Job,滑動時自動取消不可見的請求:
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {private var loadJob: Job? = nullfun bind(url: String) {loadJob?.cancel() // 先取消之前的任務loadJob = lifecycleScope.launch {val bitmap = withContext(Dispatchers.IO) { loadImage(url) // 耗時操作}if (isActive) { // 檢查是否已被取消itemView.image.setImageBitmap(bitmap)}}}fun unbind() {loadJob?.cancel() // 視圖滾出屏幕時取消}
}
對比Glide,這套方案更靈活——比如先加載縮略圖,再用協程組合高清圖加載,還能統一處理異常。之前列表滑動FPS只有30,優化后穩定60,內存波動減少70%。”
面試追問擴展
?面試官?:
“協程和線程的本質區別是什么?為什么說協程更適合高并發場景?”
?候選人?:
“您可以想象協程就像快遞站里的一群快遞員,而線程是送貨的大卡車。卡車每次出發都要裝貨、申請路線、排隊等調度,一趟只能送一個包裹,成本高還慢。而快遞員們共用幾輛電動車,一個卡車能塞下幾百個快遞員,每個人記下自己的送貨路線,到了路口靈活切換——協程就這么干的。它不用等操作系統調度,自己管理任務切換,一個線程能跑幾萬個協程,內存開銷只有幾十KB。比如我們做秒殺活動,10萬人同時搶購,用線程池開500個線程內存就爆了,但換成協程,一個線程輕松扛住,還能自動取消沒必要的請求,這就是輕量級的威力。”
?面試官?:
“聽說GlobalScope容易導致內存泄漏,你們項目里是怎么解決的?”
?候選人?:
“這真是血淚教訓!之前做視頻彈幕功能,用GlobalScope啟動了一個無限循環的彈幕請求,結果用戶退出頁面后,協程還在后臺瘋狂拉數據,Fragment像‘僵尸’一樣賴在內存里,最后直接OOM崩潰。后來才明白,GlobalScope是‘長生不老’的,它的生命周期和整個App綁定,根本不管Activity的死活。現在我們全員改用lifecycleScope——相當于給協程裝了智能開關,頁面銷毀時自動斷電。比如在Fragment里發起網絡請求,只要用lifecycleScope.launch,用戶一返回,請求立刻取消,再也不會出現后臺偷偷耗流量的問題了。”
?面試官?:
“用戶快速滑動商品列表,每個Item都要加載圖片,用協程怎么防止卡頓和錯亂?”
候選人?:
“這個問題我們優化了三個月!首先,?給每個圖片加載任務打標簽。比如商品ID是123,加載完成后對比ImageView當前綁定的ID,如果不一樣就直接丟棄,防止圖片錯位。其次,?用協程作用域控制生命周期。在RecyclerView的ViewHolder里,每次綁定新數據時,先取消前一個協程任務,像這樣——”
候選人用手比劃著空氣代碼:
“在onBindViewHolder里啟動協程前,先檢查job是否活躍,如果還在跑就立刻cancel。最后,?給IO操作加限流。比如用Dispatchers.IO的limitedParallelism限制同時加載的圖片數,避免100張圖同時開線程,線程池直接被打爆。現在我們的列表滑動FPS穩定在60幀,內存占用降了60%。”
?面試官?:
“如果協程嵌套了三層異步任務,突然父協程被取消,會發生什么?”
?候選人?:
“這就好比拆炸彈時剪錯了線——子協程會連鎖爆炸!結構化并發的核心思想就是‘要活一起活,要死一起死’。比如父協程負責訂單支付,內部啟動了子協程A查庫存、子協程B扣積分。如果用戶突然退出了頁面,父協程取消,A和B會立刻收到取消信號,哪怕B已經完成了90%,也會立刻回滾。這雖然殘酷,但保證了資源不會部分提交(比如積分扣了但庫存沒鎖)。如果想讓某個子協程‘茍活’,比如日志上報任務,可以用SupervisorJob把它包起來,這樣即使父協程掛了,它還能在后臺默默執行完。”
Android學習總結之Kotlin 協程_android kotlin協程-CSDN博客https://blog.csdn.net/2301_80329517/article/details/146909197Android學習總結之協程對比優缺點(協程一)_android 協程和線程-CSDN博客
https://blog.csdn.net/2301_80329517/article/details/147256220