xDocxDoc
AI
前端
后端
iOS
Android
Flutter
AI
前端
后端
iOS
Android
Flutter
  • Android 面试合集

    • Android面试基础篇(一):基础架构与核心组件深度剖析
    • Android中高级面试(二):架构组件与协程实战精要
    • Android中高级面试(三):架构组件与性能优化实战
    • Android 面试(四): Kotlin 基础
    • Android面试(五):深入Kotlin协程
    • Android面试(六):深入Kotlin Flow
    • Android面试(七):Jetpack Compose 深度解析与高频考点
    • Android架构面试(八):从Jetpack到模块化设计
    • Android架构面试(九):Clean Architecture 终极指南
    • Android面试全栈指南:从Kotlin到Jetpack Compose

Android面试(五):深入Kotlin协程

一、协程本质解析:轻量级线程的真相

协程并非传统意义上的线程,而是线程框架之上的任务调度单元。其轻量性体现在:

// 启动10万个协程对比线程
fun main() = runBlocking {
    repeat(100_000) { // 协程版本
        launch {
            delay(1000L)
            print(".")
        }
    }
    
    // 对比线程版本(将导致OOM)
    // repeat(100_000) {
    //    thread {
    //        Thread.sleep(1000)
    //        print(".")
    //    }
    // }
}

✅ 执行分析:

  • 协程仅消耗KB级内存,线程需要MB级
  • 协程切换在用户态完成,无需内核介入
  • 挂起函数通过CPS(Continuation Passing Style)实现状态保存

二、结构化并发:协程的骨架系统

2.1 协程作用域层级

2.2 生命周期绑定实践

class MyActivity : AppCompatActivity(), CoroutineScope {
    private lateinit var job: Job
    
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate()
        job = Job()
        
        // 启动网络请求协程
        launch {
            val data = withContext(Dispatchers.IO) {
                apiService.fetchData()
            }
            updateUI(data)
        }
    }
    
    override fun onDestroy() {
        super.onDestroy()
        job.cancel() // 自动取消所有子协程
    }
}

⚠️ 典型错误:在ViewModel中使用GlobalScope将导致:

  • 无法自动取消任务
  • 内存泄漏风险增加
  • 失去异常传播能力

三、协程上下文:控制并发的DNA

3.1 关键元素组合

val customContext = Dispatchers.Default + 
                    CoroutineName("BackgroundTask") + 
                    CoroutineExceptionHandler { _, e ->
                        Log.e("Coroutine", "Caught $e")
                    }

launch(customContext) {
    // 在此执行后台计算
}

3.2 调度器选择策略

调度器类型适用场景线程池特征
Dispatchers.MainUI更新、轻量任务单线程(主线程)
Dispatchers.IO网络/文件操作动态扩容(64线程)
Dispatchers.DefaultCPU密集型计算固定线程(CPU核数)

四、Flow:响应式编程的协程实现

4.1 冷流与热流对比

// 冷流示例(按需生产)
val coldFlow = flow {
    repeat(3) {
        emit(it)
        delay(100)
    }
}

// 热流示例(独立生产)
val stateFlow = MutableStateFlow(0)
fun startProducer() {
    viewModelScope.launch {
        repeat(Int.MAX_VALUE) {
            stateFlow.emit(it)
            delay(1000)
        }
    }
}

4.2 背压处理策略

flow {
    for (i in 1..1000) {
        emit(i)
    }
}
.buffer(50) // 设置缓冲区大小
.conflate() // 保留最新值
.collectLatest { value -> // 取消慢速收集器
    // 处理最新值
}

五、协程取消的陷阱与解决方案

5.1 不可取消的代码块

suspend fun criticalTask() = withContext(NonCancellable) {
    // 即使父协程取消,也会执行完成
    writeToDatabase()
    sendLogToServer()
}

5.2 资源清理规范

val job = launch {
    try {
        val stream = openFileStream()
        stream.use { // 使用use函数自动关闭资源
            while (isActive) { // 检查取消状态
                val data = it.read()
                process(data)
            }
        }
    } finally {
        withContext(NonCancellable) {
            releaseExternalResource() // 确保清理执行
        }
    }
}

六、协程调试进阶技巧

6.1 协程ID追踪

# 启用协程调试模式
System.setProperty("kotlinx.coroutines.debug", "on")

日志输出示例:

[CoroutineId=3] Starting network request
[CoroutineId=3] Received 2048 bytes

6.2 自定义拦截器

class TimingInterceptor : CoroutineInterceptor {
    override fun <T> interceptContinuation(continuation: Continuation<T>) =
        object : Continuation<T> {
            private val startTime = System.currentTimeMillis()
            
            override val context = continuation.context
            
            override fun resumeWith(result: Result<T>) {
                val duration = System.currentTimeMillis() - startTime
                log("Coroutine took ${duration}ms")
                continuation.resumeWith(result)
            }
        }
}

七、工业级协程架构模式

7.1 仓库层封装方案

class UserRepository(
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
    private val scope = CoroutineScope(SupervisorJob() + ioDispatcher)
    
    suspend fun fetchUser(id: String): User = 
        withContext(scope.coroutineContext) {
            // 网络请求与缓存逻辑
        }
    
    fun clear() {
        scope.cancel("Repository cleared")
    }
}

7.2 复杂任务组合

suspend fun loadDashboardData() = coroutineScope {
    val userDeferred = async { getUserProfile() }
    val notificationsDeferred = async { fetchNotifications() }
    
    val user = userDeferred.await() // 挂起点1
    val notifications = notificationsDeferred.await() // 挂起点2
    
    DashboardData(user, notifications).apply {
        preprocess() // 同步处理
    }
}

八、协程性能优化指南

8.1 调度器优化矩阵

场景推荐方案性能提升点
并行网络请求Dispatchers.IO.limitedParallelism(8)避免线程过多竞争
数据库批量操作使用channelFlow缓冲写入减少事务提交次数
图片处理流水线自定义固定大小线程池控制CPU占用峰值

8.2 避免过度切换上下文

// 反模式:频繁切换调度器
suspend fun processData() {
    withContext(Dispatchers.IO) { /* 网络请求 */ }
    withContext(Dispatchers.Default) { /* 数据处理 */ }
    withContext(Dispatchers.Main) { /* UI更新 */ }
}

// 优化方案:批量处理
suspend fun optimizedProcess() = withContext(Dispatchers.Default) {
    val rawData = fetchData() // 在IO调度器执行
    val processed = transformData(rawData) // 同线程继续处理
    withContext(Dispatchers.Main) {
        updateUI(processed) // 单次切换
    }
}

九、协程在跨平台开发中的实践

9.1 KMM架构中的协程使用

// commonMain模块
expect val backgroundDispatcher: CoroutineDispatcher

class SharedRepository {
    suspend fun getData(): Data = 
        withContext(backgroundDispatcher) {
            // 跨平台业务逻辑
        }
}

// androidMain模块
actual val backgroundDispatcher: CoroutineDispatcher = Dispatchers.IO

// iosMain模块
actual val backgroundDispatcher: CoroutineDispatcher = 
    Dispatchers.Default.limitedParallelism(1)

十、协程面试真题解析

10.1 问题:如何避免协程内存泄漏?

解决方案:

  1. 使用viewModelScope/lifecycleScope自动绑定生命周期
  2. 避免在全局作用域启动长任务
  3. 使用WeakReference传递上下文
  4. 定期使用LeakCanary进行检测

10.2 问题:协程挂起函数是否阻塞线程?

深度解析:

suspend fun nonBlockingCall() {
    delay(1000) // 挂起协程但不阻塞线程
    // 等同于:
    // handler.postDelayed({ continuation.resume() }, 1000)
}

fun blockingCall() {
    Thread.sleep(1000) // 实际阻塞当前线程
}

✅ 核心区别:

  • 挂起函数通过回调机制释放线程资源
  • 阻塞调用会持续占用线程直至完成

总结

关键知识点

  1. 结构化并发体系:通过作用域层级实现协程树管理,确保资源自动释放
  2. 上下文控制艺术:合理组合调度器、异常处理器等元素构建健壮任务
  3. 响应式数据流:掌握Flow的冷热流特性及背压处理策略
  4. 取消传播机制:理解协程取消的双向传播路径及资源清理规范
  5. 跨平台适配:在KMM中实现协程调度器的平台差异化配置

高频面试题思维导图

最后更新: 2025/9/29 08:41
Prev
Android 面试(四): Kotlin 基础
Next
Android面试(六):深入Kotlin Flow