观察者回调
回调
定义:接收初始形参和Lambda
假设有个下载功能,需要两个参数:一个是文件名(初始形参),另一个是下载完成后的处理逻辑(Lambda函数)。
// file是常规参数,onDone就是那个接回调的形参
fun download(file: String, onDone: (String) -> Unit) {
println("开始搞: $file")
// 产生了一个结果,直接在onDone里执行
val res = "$file.apk"
onDone(res)
}
就地使用
fun main(){
dowload("SukiSuUltra"){callback->
println("下载完成$callback")
}
}
观察者
执行当初注册时塞进来的回调函数(Lambda)
建立一个list作为映射
变化后立即遍历整个数组
// 观察者模式的核心骨架
class SimpleObservable {
// 用 Map 存所有注册进来的回调,key 是观察者的身份标识
private val listeners = mutableMapOf<Any, (String) -> Unit>()
// 注册:把 Lambda 塞进 Map
fun register(key: Any, onChanged: (String) -> Unit) {
listeners[key] = onChanged
}
// 注销:从 Map 里移除
fun unregister(key: Any) {
listeners.remove(key)
}
// 变化发生时,遍历所有注册的回调,挨个执行
fun notifyAll(newValue: String) {
listeners.values.forEach { callback ->
callback(newValue) // 把新数据"喂"给每个观察者
}
}
}
fun main() {
val observable = SimpleObservable()
// 两个观察者各自注册自己的处理逻辑
observable.register("A") { value -> println("观察者A收到: $value") }
observable.register("B") { value -> println("观察者B收到: $value") }
// 状态变化,所有人都会被通知
observable.notifyAll("hello")
// 输出:
// 观察者A收到: hello
// 观察者B收到: hello
}
关键点:谁注册、谁收到。notifyAll 本身不关心有多少观察者,也不关心它们各自怎么处理——只管把新值传过去,剩下的由当初塞进来的 Lambda 自己决定。
具体例子解释
//让ai把这段vico(一个支持compose的绘制数据图形(笛卡尔坐标系)的库)的源代码总结了一下
package org.primftpd.ui
import androidx.compose.runtime.*
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/**
* 极简版 Vico 回调机制
*
* 核心原理:观察者模式 + Compose State
* 1. 外部调用 runTransaction() 更新数据
* 2. Producer 通知所有已注册的 Receiver(回调)
* 3. Receiver 更新 Compose State → 触发重组
* 4. Canvas 自动重绘
*/
// ==================== 1. 数据模型 ====================
data class SimpleChartModel(val data: List<Float>)
// ==================== 2. Producer(被观察者) ====================
class SimpleChartModelProducer {
// 所有观察者的列表(key 通常是 Composable 的 identity)
private val receivers = mutableMapOf<Any, UpdateReceiver>()
// 缓存上一次的模型,用于 diff
private var cachedModel: SimpleChartModel? = null
// 并发锁
private val mutex = Mutex()
// 外部调用:更新数据
suspend fun runTransaction(block: Transaction.() -> Unit) {
val transaction = Transaction().apply(block)
commit(transaction.newModel)
}
inner class Transaction {
var newModel: SimpleChartModel? = null
}
// 提交更新 + 通知所有观察者
private suspend fun commit(model: SimpleChartModel?) = coroutineScope {
mutex.withLock {
// 只有数据变化才通知
if (model == cachedModel) return@coroutineScope
cachedModel = model
// 🔑 核心:并发通知所有观察者
//**这里就相当于上一个例子的类当中的第三个函数,用于通知观察者数据的变化**
//**从而让数据变化,传给变化的数据给观察者,让compose触发重绘**
receivers.values.map { receiver ->
launch {
// 回调:传新数据给观察者
receiver.onUpdate(model)
}
}.joinAll() // 等待全部通知完成
}
}
// Composable 调用:注册观察者,返回 State
fun collectAsState(key: Any): State<SimpleChartModel?> {
// Compose State(观察目标)
val state = mutableStateOf<SimpleChartModel?>(null)
// 创建 Receiver(观察者)
val receiver = UpdateReceiver { model ->
// 在后台线程接收数据
CoroutineScope(Dispatchers.Main).launch {
// 更新 Compose State → 触发重组
state.value = model
}
}
// 注册到 Producer
GlobalScope.launch(Dispatchers.Default) {
mutex.withLock {
receivers[key] = receiver
// 立即发送一次当前缓存值
receiver.onUpdate(cachedModel)
}
}
return state
}
// 观察者结构
private class UpdateReceiver(
val onUpdate: (SimpleChartModel?) -> Unit
)
}
// ==================== 3. Composable 使用 ====================
@Composable
fun SimpleChartDemo() {
val producer = remember { SimpleChartModelProducer() }
// 订阅:返回 State,数据变 → 重组
val model by producer.collectAsState(key = "demo")
// 模拟:每秒更新一次
LaunchedEffect(Unit) {
var i = 0
while (true) {
delay(1000)
producer.runTransaction {
newModel = SimpleChartModel(listOf(i++.toFloat(), i++.toFloat(), i++.toFloat()))
}
}
}
// model 变化时,这里会重组
Text("当前数据: ${model?.data}")
}
上面那段代码就是把这个"注册→通知→处理"的模式搬进了 Compose 的世界,稍微复杂一点,但骨架完全一样
这段代码是ai简化的,似乎会出现死锁问题,但是理解就行了
很大的一个区别就是这里的真实例子是有并发的
forEach:先更新图表A,完成→更新图表B,完成→更新图表C
map + launch:同时更新ABC,全部完成才继续
然后joinAll
整体流程
runTransaction() 更新数据
→ commit() 加锁,检测数据是否变化
→ 遍历 receivers,并发调用每个 onUpdate()
→ Receiver 把新数据推给 Compose State
→ State 变化触发重组 → Canvas 自动重绘
(尝试在代码示例当中加了一些注释,和上面的例子对比, 用**** 标出)
逐块拆解
SimpleChartModel — 纯数据包,只是把 List<Float> 包了一层,方便做 diff 比较(data class 自动实现了 equals)。
SimpleChartModelProducer — 被观察者,等价于上面例子里的 SimpleObservable。
private val receivers = mutableMapOf<Any, UpdateReceiver>()
和上面的 listeners 是一回事,key 是 Composable 的身份(通常传 this 或者字符串)。
suspend fun runTransaction(block: Transaction.() -> Unit) {
val transaction = Transaction().apply(block)
commit(transaction.newModel)
}
这是对外暴露的"写入入口"。Transaction 就是个临时容器,让调用方用 DSL 风格填数据,填完就交给 commit 去处理。
if (model == cachedModel) return@coroutineScope
diff 优化:数据没变就直接跳过,不通知任何人,避免无意义的重组。data class 的 equals 在这里发挥作用。
receivers.values.map { receiver ->
launch { receiver.onUpdate(model) }
}.joinAll()
和 forEach 的区别:这里是并发通知,每个观察者在独立协程里被调用,互不等待,全部完成才继续。如果有十个图表同时订阅,它们会同时收到更新而不是排队等。
fun collectAsState(key: Any): State<SimpleChartModel?> {
val state = mutableStateOf<SimpleChartModel?>(null)
val receiver = UpdateReceiver { model ->
CoroutineScope(Dispatchers.Main).launch {
state.value = model // 切回主线程更新 State
}
}
// ...注册到 receivers
return state
}
这是"注册"动作在 Compose 里的形态。它干了两件事:
- 创建一个
mutableStateOf,作为 Compose 能感知的"盒子" - 创建一个 Receiver,当数据来了就把值塞进这个盒子
(我懒了,反正嫩看懂了,这里用ai续写了一下()
state.value = model 这一行是触发重组的关键——Compose 会自动盯着所有读取过这个 State 的 Composable,一旦值变了就重新执行它们。
在 Composable 里的用法
val model by producer.collectAsState(key = "demo")
by 是 Kotlin 的委托语法,等价于每次访问 model 都自动调 .value。Compose 在这里悄悄记录了"这个 Composable 读了这个 State",之后 State 变化时就知道该重组谁。
LaunchedEffect(Unit) 里的死循环每秒调一次 runTransaction,整个链路就跑起来了:写数据 → 通知 → State 更新 → 文字重新显示。

浙公网安备 33010602011771号