Android-kotlin协程学习总结
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