数据流: 数据产生、传递、处理和消费的过程。
数据流包含三个重要概念:
- 生产者:生成数据,添加到数据流中
- 加工者:处于数据流的中间,可以对数据流中的数据进行变换、加工、处理
- 消费者:处于数据流的末尾,对数据流中的数据进行最终消费、使用
Flow 是一种 实现异步数据流的方案
Kotlin Flow / StateFlow / SharedFlow 速记
Kotlin协程已经提供了强大的异步编程能力,但Kotlin Flow提供了额外的抽象层次,专门用于处理异步数据流的,它是作为协程的补充,使得开发者可以更方便地构建复杂的异步数据处理逻辑。
1. 一句话定位
- Flow:描述异步数据流(冷流,收集时才触发)
- StateFlow:保存并暴露最新状态(热流,有初始值,状态容器)
- SharedFlow:向多个订阅者广播数据或事件(热流,可配置缓存)
2. 继承关系(类型树)
#mermaid-svg-dxgsJP2pcxaFDsio{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dxgsJP2pcxaFDsio .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dxgsJP2pcxaFDsio .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dxgsJP2pcxaFDsio .error-icon{fill:#552222;}#mermaid-svg-dxgsJP2pcxaFDsio .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dxgsJP2pcxaFDsio .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dxgsJP2pcxaFDsio .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dxgsJP2pcxaFDsio .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dxgsJP2pcxaFDsio .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dxgsJP2pcxaFDsio .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dxgsJP2pcxaFDsio .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dxgsJP2pcxaFDsio .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dxgsJP2pcxaFDsio .marker.cross{stroke:#333333;}#mermaid-svg-dxgsJP2pcxaFDsio svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dxgsJP2pcxaFDsio p{margin:0;}#mermaid-svg-dxgsJP2pcxaFDsio g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-dxgsJP2pcxaFDsio g.classGroup text .title{font-weight:bolder;}#mermaid-svg-dxgsJP2pcxaFDsio .cluster-label text{fill:#333;}#mermaid-svg-dxgsJP2pcxaFDsio .cluster-label span{color:#333;}#mermaid-svg-dxgsJP2pcxaFDsio .cluster-label span p{background-color:transparent;}#mermaid-svg-dxgsJP2pcxaFDsio .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dxgsJP2pcxaFDsio .cluster text{fill:#333;}#mermaid-svg-dxgsJP2pcxaFDsio .cluster span{color:#333;}#mermaid-svg-dxgsJP2pcxaFDsio .nodeLabel,#mermaid-svg-dxgsJP2pcxaFDsio .edgeLabel{color:#131300;}#mermaid-svg-dxgsJP2pcxaFDsio .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-dxgsJP2pcxaFDsio .label text{fill:#131300;}#mermaid-svg-dxgsJP2pcxaFDsio .labelBkg{background:#ECECFF;}#mermaid-svg-dxgsJP2pcxaFDsio .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-dxgsJP2pcxaFDsio .classTitle{font-weight:bolder;}#mermaid-svg-dxgsJP2pcxaFDsio .node rect,#mermaid-svg-dxgsJP2pcxaFDsio .node circle,#mermaid-svg-dxgsJP2pcxaFDsio .node ellipse,#mermaid-svg-dxgsJP2pcxaFDsio .node polygon,#mermaid-svg-dxgsJP2pcxaFDsio .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dxgsJP2pcxaFDsio .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-dxgsJP2pcxaFDsio g.clickable{cursor:pointer;}#mermaid-svg-dxgsJP2pcxaFDsio g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-dxgsJP2pcxaFDsio g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-dxgsJP2pcxaFDsio .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-dxgsJP2pcxaFDsio .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-dxgsJP2pcxaFDsio .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-dxgsJP2pcxaFDsio .dashed-line{stroke-dasharray:3;}#mermaid-svg-dxgsJP2pcxaFDsio .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-dxgsJP2pcxaFDsio #compositionStart,#mermaid-svg-dxgsJP2pcxaFDsio .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dxgsJP2pcxaFDsio #compositionEnd,#mermaid-svg-dxgsJP2pcxaFDsio .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dxgsJP2pcxaFDsio #dependencyStart,#mermaid-svg-dxgsJP2pcxaFDsio .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dxgsJP2pcxaFDsio #dependencyStart,#mermaid-svg-dxgsJP2pcxaFDsio .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dxgsJP2pcxaFDsio #extensionStart,#mermaid-svg-dxgsJP2pcxaFDsio .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dxgsJP2pcxaFDsio #extensionEnd,#mermaid-svg-dxgsJP2pcxaFDsio .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dxgsJP2pcxaFDsio #aggregationStart,#mermaid-svg-dxgsJP2pcxaFDsio .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dxgsJP2pcxaFDsio #aggregationEnd,#mermaid-svg-dxgsJP2pcxaFDsio .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dxgsJP2pcxaFDsio #lollipopStart,#mermaid-svg-dxgsJP2pcxaFDsio .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dxgsJP2pcxaFDsio #lollipopEnd,#mermaid-svg-dxgsJP2pcxaFDsio .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dxgsJP2pcxaFDsio .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-dxgsJP2pcxaFDsio .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dxgsJP2pcxaFDsio .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dxgsJP2pcxaFDsio .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dxgsJP2pcxaFDsio :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} <<interface>>
Flow
SharedFlow
+replay
+extraBufferCapacity
+onBufferOverflow
+emit()
+tryEmit()
StateFlow
+value
+initial value required
+update()
+distinctUntilChanged by equals()
面试可讲 :StateFlow 是一种特殊的 SharedFlow,SharedFlow 又是 Flow 的扩展。
3. 冷流 vs 热流(核心区分)
| 冷流 (Cold) | 热流 (Hot) | |
|---|---|---|
| 代表 | flow { } 构建的 Flow |
StateFlow, SharedFlow |
| 数据生产 | 每次 collect 都重新执行上游 |
独立于收集者,生产者持续存在 |
| 收集者 | 一对一(每个收集者独立执行) | 一对多(广播,共享同一份上游) |
冷流是:有收集者 collect 时才开始执行,每个收集者都会独立执行一次上游代码。
热流是:独立于收集者存在,多个收集者共享同一份数据源。
流程图:冷热对比
收集者2 收集者1 生产者 收集者2 收集者1 生产者 #mermaid-svg-gBJ0XJ4MFw0taaM7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gBJ0XJ4MFw0taaM7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .error-icon{fill:#552222;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .marker.cross{stroke:#333333;}#mermaid-svg-gBJ0XJ4MFw0taaM7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gBJ0XJ4MFw0taaM7 p{margin:0;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-gBJ0XJ4MFw0taaM7 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-gBJ0XJ4MFw0taaM7 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-gBJ0XJ4MFw0taaM7 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .sequenceNumber{fill:white;}#mermaid-svg-gBJ0XJ4MFw0taaM7 #sequencenumber{fill:#333;}#mermaid-svg-gBJ0XJ4MFw0taaM7 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .messageText{fill:#333;stroke:none;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .labelText,#mermaid-svg-gBJ0XJ4MFw0taaM7 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .loopText,#mermaid-svg-gBJ0XJ4MFw0taaM7 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-gBJ0XJ4MFw0taaM7 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .noteText,#mermaid-svg-gBJ0XJ4MFw0taaM7 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .actorPopupMenu{position:absolute;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-gBJ0XJ4MFw0taaM7 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-gBJ0XJ4MFw0taaM7 .actor-man circle,#mermaid-svg-gBJ0XJ4MFw0taaM7 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-gBJ0XJ4MFw0taaM7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 冷流:各自独立执行 热流:共享同一份生产 collect 触发 → emit(1),emit(2)collect 触发 → 重新 emit(1),emit(2)emit(1)emit(1)emit(2)emit(2)
4. StateFlow 面试要点
- 必须有初始值 :
MutableStateFlow(initialValue),因为状态任何时候都得有值。 - 始终持有最新值 :通过
.value同步读取。 - 去重 :新值
equals旧值时不会通知收集者,所以不适合发连续相同的事件。 - 新收集者:立刻收到当前最新状态。
- 更新状态 :
_state.value = newValue或_state.update { it.copy(...) }(原子操作)。 - 对外暴露 :
private val _state = MutableStateFlow(...),val state: StateFlow<...> = _state保证单向数据流。 - 收集方式 :View 中用
repeatOnLifecycle(STARTED)包裹,防止后台泄漏。
StateFlow 数据流向图
#mermaid-svg-vbys3oMZfIVhqYA8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vbys3oMZfIVhqYA8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vbys3oMZfIVhqYA8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vbys3oMZfIVhqYA8 .error-icon{fill:#552222;}#mermaid-svg-vbys3oMZfIVhqYA8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vbys3oMZfIVhqYA8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vbys3oMZfIVhqYA8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vbys3oMZfIVhqYA8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vbys3oMZfIVhqYA8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vbys3oMZfIVhqYA8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vbys3oMZfIVhqYA8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vbys3oMZfIVhqYA8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vbys3oMZfIVhqYA8 .marker.cross{stroke:#333333;}#mermaid-svg-vbys3oMZfIVhqYA8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vbys3oMZfIVhqYA8 p{margin:0;}#mermaid-svg-vbys3oMZfIVhqYA8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vbys3oMZfIVhqYA8 .cluster-label text{fill:#333;}#mermaid-svg-vbys3oMZfIVhqYA8 .cluster-label span{color:#333;}#mermaid-svg-vbys3oMZfIVhqYA8 .cluster-label span p{background-color:transparent;}#mermaid-svg-vbys3oMZfIVhqYA8 .label text,#mermaid-svg-vbys3oMZfIVhqYA8 span{fill:#333;color:#333;}#mermaid-svg-vbys3oMZfIVhqYA8 .node rect,#mermaid-svg-vbys3oMZfIVhqYA8 .node circle,#mermaid-svg-vbys3oMZfIVhqYA8 .node ellipse,#mermaid-svg-vbys3oMZfIVhqYA8 .node polygon,#mermaid-svg-vbys3oMZfIVhqYA8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vbys3oMZfIVhqYA8 .rough-node .label text,#mermaid-svg-vbys3oMZfIVhqYA8 .node .label text,#mermaid-svg-vbys3oMZfIVhqYA8 .image-shape .label,#mermaid-svg-vbys3oMZfIVhqYA8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-vbys3oMZfIVhqYA8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vbys3oMZfIVhqYA8 .rough-node .label,#mermaid-svg-vbys3oMZfIVhqYA8 .node .label,#mermaid-svg-vbys3oMZfIVhqYA8 .image-shape .label,#mermaid-svg-vbys3oMZfIVhqYA8 .icon-shape .label{text-align:center;}#mermaid-svg-vbys3oMZfIVhqYA8 .node.clickable{cursor:pointer;}#mermaid-svg-vbys3oMZfIVhqYA8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vbys3oMZfIVhqYA8 .arrowheadPath{fill:#333333;}#mermaid-svg-vbys3oMZfIVhqYA8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vbys3oMZfIVhqYA8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vbys3oMZfIVhqYA8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vbys3oMZfIVhqYA8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vbys3oMZfIVhqYA8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vbys3oMZfIVhqYA8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vbys3oMZfIVhqYA8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vbys3oMZfIVhqYA8 .cluster text{fill:#333;}#mermaid-svg-vbys3oMZfIVhqYA8 .cluster span{color:#333;}#mermaid-svg-vbys3oMZfIVhqYA8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vbys3oMZfIVhqYA8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vbys3oMZfIVhqYA8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-vbys3oMZfIVhqYA8 .icon-shape,#mermaid-svg-vbys3oMZfIVhqYA8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vbys3oMZfIVhqYA8 .icon-shape p,#mermaid-svg-vbys3oMZfIVhqYA8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vbys3oMZfIVhqYA8 .icon-shape .label rect,#mermaid-svg-vbys3oMZfIVhqYA8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vbys3oMZfIVhqYA8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vbys3oMZfIVhqYA8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vbys3oMZfIVhqYA8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Intent
更新
通知新值
渲染
UI 操作
ViewModel
MutableStateFlow
UI 收集者
界面刷新
5. SharedFlow 面试要点
- 不要求初始值。
- 广播:多个收集者同时接收。
- replay :新订阅者能收到最近
replay个历史值。replay = 0:新订阅者收不到历史数据,适合一次性事件(Toast、导航)。replay = 1:新订阅者收到最近一个值。
- 缓冲区 :
extraBufferCapacity+onBufferOverflow(SUSPEND/DROP_OLDEST/DROP_LATEST) 控制背压。 - 发射方法 :
emit:挂起函数,缓冲满时会挂起。tryEmit:非挂起,立即返回布尔值,适合从非协程上下文发事件(但可能丢失)。
- 事件场景 :
sealed interface UiEvent配合MutableSharedFlow(replay=0, extraBufferCapacity=1)发射。
6. StateFlow vs SharedFlow 速查表
| StateFlow | SharedFlow | |
|---|---|---|
| 用途 | 持久状态(UI 状态) | 事件/广播(一次性通知) |
| 初始值 | 必须 | 无 |
| 当前值 | 始终有,.value 直接取 |
不能直接取单个当前值(除非 replay) |
| 新订阅者 | 收到最新值 | 收到 replay 个历史值 |
| 去重 | 自动(equals) | 不自动 |
| 典型场景 | 加载中/成功/失败状态,列表数据 | Toast、导航、Snackbar、刷新信号 |
| 配置 | 固定重放1 | 可自定义 replay、缓冲区、溢出策略 |
选择决策图
#mermaid-svg-154J7qitkb8xbjRW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-154J7qitkb8xbjRW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-154J7qitkb8xbjRW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-154J7qitkb8xbjRW .error-icon{fill:#552222;}#mermaid-svg-154J7qitkb8xbjRW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-154J7qitkb8xbjRW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-154J7qitkb8xbjRW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-154J7qitkb8xbjRW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-154J7qitkb8xbjRW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-154J7qitkb8xbjRW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-154J7qitkb8xbjRW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-154J7qitkb8xbjRW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-154J7qitkb8xbjRW .marker.cross{stroke:#333333;}#mermaid-svg-154J7qitkb8xbjRW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-154J7qitkb8xbjRW p{margin:0;}#mermaid-svg-154J7qitkb8xbjRW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-154J7qitkb8xbjRW .cluster-label text{fill:#333;}#mermaid-svg-154J7qitkb8xbjRW .cluster-label span{color:#333;}#mermaid-svg-154J7qitkb8xbjRW .cluster-label span p{background-color:transparent;}#mermaid-svg-154J7qitkb8xbjRW .label text,#mermaid-svg-154J7qitkb8xbjRW span{fill:#333;color:#333;}#mermaid-svg-154J7qitkb8xbjRW .node rect,#mermaid-svg-154J7qitkb8xbjRW .node circle,#mermaid-svg-154J7qitkb8xbjRW .node ellipse,#mermaid-svg-154J7qitkb8xbjRW .node polygon,#mermaid-svg-154J7qitkb8xbjRW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-154J7qitkb8xbjRW .rough-node .label text,#mermaid-svg-154J7qitkb8xbjRW .node .label text,#mermaid-svg-154J7qitkb8xbjRW .image-shape .label,#mermaid-svg-154J7qitkb8xbjRW .icon-shape .label{text-anchor:middle;}#mermaid-svg-154J7qitkb8xbjRW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-154J7qitkb8xbjRW .rough-node .label,#mermaid-svg-154J7qitkb8xbjRW .node .label,#mermaid-svg-154J7qitkb8xbjRW .image-shape .label,#mermaid-svg-154J7qitkb8xbjRW .icon-shape .label{text-align:center;}#mermaid-svg-154J7qitkb8xbjRW .node.clickable{cursor:pointer;}#mermaid-svg-154J7qitkb8xbjRW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-154J7qitkb8xbjRW .arrowheadPath{fill:#333333;}#mermaid-svg-154J7qitkb8xbjRW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-154J7qitkb8xbjRW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-154J7qitkb8xbjRW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-154J7qitkb8xbjRW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-154J7qitkb8xbjRW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-154J7qitkb8xbjRW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-154J7qitkb8xbjRW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-154J7qitkb8xbjRW .cluster text{fill:#333;}#mermaid-svg-154J7qitkb8xbjRW .cluster span{color:#333;}#mermaid-svg-154J7qitkb8xbjRW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-154J7qitkb8xbjRW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-154J7qitkb8xbjRW rect.text{fill:none;stroke-width:0;}#mermaid-svg-154J7qitkb8xbjRW .icon-shape,#mermaid-svg-154J7qitkb8xbjRW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-154J7qitkb8xbjRW .icon-shape p,#mermaid-svg-154J7qitkb8xbjRW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-154J7qitkb8xbjRW .icon-shape .label rect,#mermaid-svg-154J7qitkb8xbjRW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-154J7qitkb8xbjRW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-154J7qitkb8xbjRW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-154J7qitkb8xbjRW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
是, 需读取当前值
否, 一次性事件
否, 需向新订阅者发缓存数据
需要共享给多个订阅者?
使用冷 Flow
是否表示当前持续状态?
StateFlow
SharedFlow replay=0
SharedFlow replay>0
7. stateIn / shareIn 与启动策略
stateIn:将冷 Flow 转为热StateFlow。
coldFlow.stateIn(scope, SharingStarted.WhileSubscribed(5000), initialValue)shareIn:将冷 Flow 转为热SharedFlow。
coldFlow.shareIn(scope, SharingStarted.WhileSubscribed(5000), replay = 1)
启动策略 SharingStarted:
Eagerly:立即启动,永不停止。Lazily:首个订阅者出现后启动,永不停止。WhileSubscribed(stopTimeoutMillis):Android ViewModel 推荐 。最后一个订阅者离开后,等待5000ms停止上游。避免屏幕旋转等短暂断开造成的重启开销。
8. 正确收集多个流(避免阻塞)
错误写法会阻塞第二个流:
kotlin
repeatOnLifecycle(STARTED) {
viewModel.state.collect { } // 挂起不结束
viewModel.events.collect { } // 永远执行不到
}
正确写法:每个流单独子协程
kotlin
lifecycleScope.launch {
repeatOnLifecycle(STARTED) {
launch { viewModel.uiState.collect { render(it) } }
launch { viewModel.events.collect { handle(it) } }
}
}
9. 常见面试问答速记
Q: StateFlow 为什么需要初始值?
A: 因为它表示当前状态,任何时候用 .value 都应读到有效数据,不能出现"无值"状态。
Q: StateFlow 如何进行数据去重?
A: 内部使用 equals 比较新旧值,相同则不重新发射,所以不适合连续相同的"事件"。
Q: 如何用 SharedFlow 发一次性事件防止丢失?
A: 使用 replay=0, extraBufferCapacity=1 或更大,用 tryEmit;但真正的"确保不丢失"事件建议建模为状态或使用 Channel(点对点)。SharedFlow 在无订阅者时事件会被丢弃。
Q: repeatOnLifecycle 为什么重要?
A: StateFlow/SharedFlow 是热流,不会随生命周期自动停止。在 STOPPED 时继续收集会浪费资源甚至导致崩溃。repeatOnLifecycle(STARTED) 保证只在界面可见时收集,后台自动取消。
Q: stateIn 和 shareIn 的 WhileSubscribed 参数有什么用?
A: 最后一个订阅者离开后延迟一定时间停止上游,既节省资源,又防止配置变更(如旋转)造成的立即重启。
10. 最终记忆口诀
Flow 是数据流管道,collect 才开工;
StateFlow 是状态箱,有初值,去重稳;
SharedFlow 是大喇叭,可缓存,广播发。
状态用 State,事件用 Shared(replay=0),
收集记住 lifecycle,多个流开子协程。
LiveData 和 Flow 的区别
| LiveData | Flow |
|---|---|
| 支持 Java 和 Kotlin,使用简单 | 仅支持在 Kotlin 中使用,Java 中使用起来比较困难 |
| 不需要协程环境来执行 | 需要协程环境来执行 |
| 主要在主线程上运行 | 在协程上运行,不阻塞主线程 |
| 转换运算符在主线程上执行 | 运算符是挂起函数,可以很方便地在不同线程上执行 |
| 默认情况下,能感知生命周期 | 默认情况下,不能感知生命周期 |
除此之外,LiveData 操作符不够强大、而且不支持切换线程。 是为了简化开发才被造出来的,搞的太复杂反而违背了设计初衷
LiveData 主要在 主线程上 执行?
- 说 LiveData 主要在主线程运行,本质上是因为它主要服务于 Android UI,而 UI 更新只能在主线程完成。
- LiveData 的数据更新和观察者通知主要由主线程完成。setValue() 只能在主线程调用,postValue() 可以在后台线程调用,但最终仍会将更新切换到主线程分发。