Appearance
kotlin conroutines
请说明 Kotlin 协程的核心优势,以及 launch 和 async 的区别?同时解释 Dispatchers 常见的调度器类型及适用场景?
一、Kotlin协程的核心优势
协程是 Kotlin 提供的轻量级并发框架,核心优势体现在:
轻量级:协程基于线程池实现,但创建成本远低于线程(单个JVM可创建上万协程),不会因资源消耗导致OOM,适合高并发场景(如批量网络请求)。
挂起与恢复:通过suspend函数实现非阻塞式挂起(无需阻塞线程),挂起时会释放线程资源给其他协程使用,恢复时回到原逻辑执行,相比线程阻塞更高效。
结构化并发:协程通过CoroutineScope管理生命周期,子协程依赖父协程,父协程取消时子协程自动取消,避免内存泄漏和僵尸协程。
简化异步代码:用同步代码的写法实现异步逻辑(无需回调嵌套),可读性和可维护性大幅提升(如withContext(Dispatchers.IO)替代AsyncTask)。
灵活的调度:支持指定调度器切换执行线程,轻松实现“后台线程处理任务+主线程更新UI”。
二、launch 和 async 的核心区别
两者均用于启动协程,但设计目标和使用场景不同:
| 对比维度 | launch | async |
|---|---|---|
| 返回值 | Job对象(仅用于管理协程生命周期,如取消、等待) | Deferred<T<对象(继承自Job,可获取异步结果) |
| 核心用途 | 启动“不关心结果”的协程(如UI更新、日志打印) | 启动“需要获取结果”的协程(如计算、网络请求返回数据) |
| 获取结果方式 | 无返回结果,仅能通过回调或共享状态传递 | 调用await()方法(挂起函数)获取结果,await()会阻塞当前协程直到结果返回 |
| 异常处理 | 异常会直接抛出(需通过CoroutineExceptionHandler捕获) | 异常默认被捕获到Deferred中,调用await()时才会抛出 |
示例场景:
launch:点击按钮后启动协程刷新UI、后台日志上报;
async:并行请求多个接口并合并结果(如同时获取用户信息和订单列表,用awaitAll()等待所有结果)。
三、Dispatchers 常见调度器类型及适用场景
Dispatchers 用于指定协程运行的线程池,核心类型包括:
1. Dispatchers.Main
运行在主线程(UI线程),适用于UI操作(如更新TextView、RecyclerView数据)、调用Android SDK的UI相关API。
注意:不能执行耗时操作(会阻塞主线程导致ANR)。
2. Dispatchers.IO
专为IO密集型任务设计的线程池(如网络请求、数据库操作、文件读写),内部会根据任务动态调整线程数。
是耗时IO操作的首选调度器,避免阻塞主线程。
3. Dispatchers.Default
专为CPU密集型任务设计(如数据解析、复杂计算、列表排序),线程数默认等于CPU核心数(保证CPU利用率)。
若用Dispatchers.IO处理CPU密集任务,会因线程过多导致上下文切换开销增大。
4. Dispatchers.Unconfined
“无限制”调度器,协程在调用者线程启动,挂起恢复后可能在任意线程继续执行(如由IO操作的回调线程恢复)。
适用场景极少,仅建议在“不依赖特定线程”的简单场景使用(如纯计算且无需线程限制),否则易导致线程安全问题。
补充:还可通过newSingleThreadContext()或newFixedThreadPoolContext()创建自定义调度器,适用于需专属线程的场景(用完需手动关闭,避免内存泄漏)。
接下来是第四道面试题,聚焦Android性能优化,请说明ANR的产生原因及常见类型,以及从编码层面如何避免ANR?