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 Flow

🔥 Kotlin Flow核心机制

冷流(Cold Stream)本质剖析

fun buildFlow(): Flow<Int> = flow {
    println("开始发射")
    emit(1)
    emit(2)
}
// 每次collect都会触发全新执行
buildFlow().collect { println(it) }  // 输出:开始发射 1 2
buildFlow().collect { println(it) }  // 再次输出:开始发射 1 2

Flow如同未通电的流水线,仅当收集者接入时(调用collect)才会启动生产。这种设计带来两大特性:

  1. 按需启动:无收集者时不消耗资源
  2. 独立上下文:每次收集创建全新执行环境

热流(Hot Stream)运作原理

val _state = MutableStateFlow(0)
val state: StateFlow<Int> = _state.asStateFlow()

// 发射端独立运行
launch {
    repeat(5) {
        _state.value = it
        delay(100)
    }
}

// 收集端随时接入
launch { state.collect { println("收集器A: $it") } }
delay(200)
launch { state.collect { println("收集器B: $it") } }

StateFlow/SharedFlow如同持续运转的传送带:

  • 数据发射与收集者生命周期解耦
  • 新收集者默认获取最新状态(StateFlow)或配置范围内的历史数据(SharedFlow)

🚀 高阶操作符实战技巧

事件合并策略对比

// 场景:快速点击防抖
view.clicks()
    .debounce(300) // 300ms内仅接收最后一次点击
    .collect { processClick() }

// 场景:实时搜索建议
searchInputFlow
    .filter { it.length > 2 }
    .distinctUntilChanged() // 内容未变更时忽略
    .flatMapLatest { query -> 
        api.search(query).catch { emit(emptyList()) }
    }
    .collect { showSuggestions(it) }

背压(Backpressure)处理三剑客

// 策略1:缓冲(默认64条)
flow.buffer().collect { /* 生产消费解耦 */ }

// 策略2:丢弃旧值
flow.conflate().collect { /* 仅处理最新值 */ }

// 策略3:手动控制(复杂场景)
channelFlow {
    send(1)
    offer(2) // 队列满时返回false
}

🧩 StateFlow与SharedFlow架构应用

状态容器最佳实践

class AuthViewModel : ViewModel() {
    private val _state = MutableStateFlow(AuthState.IDLE)
    val state = _state.asStateFlow()

    fun login(username: String, password: String) {
        _state.value = AuthState.LOADING
        viewModelScope.launch {
            try {
                authRepo.login(username, password)
                _state.value = AuthState.SUCCESS
            } catch (e: Exception) {
                _state.value = AuthState.ERROR(e)
            }
        }
    }
}

// UI层安全收集
fun observeState() {
    lifecycleScope.launchWhenStarted {
        viewModel.state.collect { renderUI(it) }
    }
}

事件总线替代方案

object EventBus {
    private val _events = MutableSharedFlow<Event>(
        extraBufferCapacity = 10, // 缓存最近10个事件
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    val events = _events.asSharedFlow()

    suspend fun post(event: Event) {
        _events.emit(event) // 挂起直到有收集者接收
    }
}

// 订阅方
EventBus.events
    .filterIsInstance<RefreshEvent>()
    .collect { updateData() }

🛡️ 异常处理与资源释放

安全收集模式

flow {
    emit(data)
}.catch { e -> 
    // 捕获上游异常
    logError(e)
}.onCompletion { 
    // 无论成功失败都会执行
    releaseResources()
}.collect { ... }

超时控制策略

flow {
    longRunningProcess()
}.timeout(5.seconds) // 超时抛出TimeCancellationException
    .retry(3) { cause -> 
        cause is TimeoutCancellationException
    }
    .collect { ... }

⚙️ Flow测试全攻略

虚拟时间测试

@Test
fun testTimerFlow() = runTest {
    val flow = tickerFlow(interval = 1.seconds)
    
    val result = mutableListOf<Int>()
    val job = launch {
        flow.take(3).collect { result.add(it) }
    }
    
    advanceTimeBy(3000) // 虚拟时间推进3秒
    job.cancel()
    
    assertEquals(listOf(0,1,2), result)
}

状态机验证

@Test
fun testStateTransition() = runTest {
    val vm = AuthViewModel(mockRepo)
    
    val states = mutableListOf<AuthState>()
    val job = launch { vm.state.collect(states::add) }
    
    vm.login("test", "pass")
    advanceUntilIdle() // 等待协程完成
    
    assertEquals(AuthState.LOADING, states[0])
    assertTrue(states.last() is AuthState.SUCCESS)
    
    job.cancel()
}

💡 Flow性能优化秘籍

避免常见陷阱

// 反模式:在flow中启动新协程
flow {
    launch { // 错误!违反结构化并发
        emit(1) 
    }
}

// 正确方式:使用callbackFlow
callbackFlow {
    callbackBasedApi.registerCallback { result ->
        trySend(result) // 线程安全发送
    }
    awaitClose { api.unregister() }
}

调度器选择策略

flow {
    // CPU密集型计算
    withContext(Dispatchers.Default) {
        heavyComputation().forEach { emit(it) }
    }
}
.flowOn(Dispatchers.IO) // 影响上游执行上下文
.collect { 
    // 默认在collect调用方上下文执行
    updateUI(it) // 需切换主线程
}

🌐 Flow与响应式编程生态整合

Retrofit适配方案

interface ApiService {
    @GET("users")
    fun getUsers(): Flow<List<User>> // 自动包装为Flow
}

// 使用方式
api.getUsers()
    .catch { emit(emptyList()) }
    .collect { showUsers(it) }

Room数据库实时监听

@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun observeUsers(): Flow<List<User>>
}

// ViewModel中
val users: Flow<List<User>> = userDao.observeUsers()
    .map { it.filter { user -> user.isActive } }

🧪 高级调试技巧

事件日志追踪

flow {
    emit(1)
}.onEach { println("发射值: $it") }
    .catch { println("捕获异常: $it") }
    .launchIn(scope)

调试工具集成

// 添加调试信息
flow {
    emit(1)
}.flowWithLifecycle(lifecycle)
    .onStart { println("Flow started") }
    .onCompletion { println("Flow completed") }

总结

核心

  1. 冷热流本质:Cold Flow按需生产,Hot Flow持续广播
  2. 状态管理:StateFlow适合UI状态,SharedFlow处理事件
  3. 背压策略:buffer/conflate/collectLatest应对不同场景
  4. 安全收集:lifecycleScope+repeatOnLifecycle避免泄漏
  5. 测试方案:TestCoroutineScheduler控制虚拟时间轴

💼 高频面试题

  1. StateFlow与LiveData的核心差异?
    答:StateFlow必须初始值,LiveData自动解绑,StateFlow支持更丰富的操作符

  2. 如何避免Flow内存泄漏?
    答:使用lifecycleScope.launchWhenStarted配合repeatOnLifecycle,或采用flowWithLifecycle扩展

  3. SharedFlow的replay参数作用?
    答:配置新订阅者接收的历史数据数量,实现"最近N个事件"的重放

  4. Flow如何实现线程切换?
    答:上游用flowOn,下游用withContext,collect默认在调用方上下文

  5. 解释flowOn与withContext差异?
    答:flowOn影响上游执行上下文,withContext仅改变代码块内上下文

最后更新: 2025/9/29 08:41
Prev
Android面试(五):深入Kotlin协程
Next
Android面试(七):Jetpack Compose 深度解析与高频考点