该技能面向 Kotlin 协程开发,解决“多个独立条件共同决定一个子系统是否运行”的问题。典型场景是:一个昂贵子系统(网络连接、后台发送器、轮询循环)需要同时满足多个条件,例如用户设置已开启且所需凭据存在。若分别监听这些条件,会得到两个各知一半答案的收集器,常见故障是仅凭设置就启动子系统,随后因无法认证而无限失败循环。
主要能力:用一个派生布尔值同时处理启动与拆除两个方向。示例代码使用 combine 合并 settings.featureEnabled 与 settings.credential,通过 enabled == TRUE && credential.isNotBlank() 得到 shouldRun,再经 distinctUntilChanged 去重后交给 collectLatest。启动分支中,连接与发送器各自做独立幂等检查(if (connection == null)、if (senderJob?.isActive != true)),以应对 on→off→on 的竞态;拆除分支在 withContext(NonCancellable) 中取消发送器、关闭连接并清空保留快照。同一组操作符也可用于更小的场景:从两个状态流派生一个对外暴露的值,且完全无副作用,此时使用普通 collect 即可。
使用方式与陷阱:combine 在所有源至少发射一次之前不会发射任何值,因此“开关从不触发”通常是因为某个输入仍为空(无初始值的流、返回空的查询、从未写入的偏好设置),且这种失败是静默的,应给每个源一个确定的起始值(StateFlow 或 onStart { emit(default) })。若不加 distinctUntilChanged,分支会在每次上游 tick 时重跑,因为偏好存储常在任何写入时重复发射相同值,应对派生值去重而非对源去重。collectLatest 会中途取消上一个动作:这对启动分支有利(翻转为关时应放弃连接尝试),但对拆除分支危险(翻回开时会半途放弃清理,留下已关闭连接配活跃发送器或反之),因此拆除需放在 withContext(NonCancellable) 中。幂等性应放在每个子资源上,而非整个分支上;分支级标志会在快速 off→on 时误报“已在运行”而跳过重建。拆除时需清空保留值,否则重启的消费者会重放它们;但单调递增的计数器不应随之重置。在分支内重新读取源值可能与触发开关时的值不一致,必要时应让 combine 的转换直接发射数据而非仅布尔值。当分支体是纯赋值时用 collect 即可,涉及启动、等待或拆除时才用 collectLatest。不要用来自其他线程的 player.isPlaying 式实时查询作为门控条件,应读取状态流的 .value。
验证方法:独立切换每个输入,观察开关自身的日志行(经 distinctUntilChanged 后每次发射一行)。凭据缺失时打开设置不应产生任何输出;随后提供凭据应恰好产生一次 true;撤回任一条件应恰好产生一次 false,且子系统活动必须在该次发射内停止,而非“最终”停止。随后在不到一秒内快速切换多次,并逐个检查资源而非只看开关:连接非空、发送器任务活跃,二者应同时成立或同时不成立。该模式要防止的正是“一个已设置而另一个未设置”的状态,而这种状态从开关日志中不可见。