观察者回调

回调

定义:接收初始形参和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 classequals 在这里发挥作用。

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 里的形态。它干了两件事:

  1. 创建一个 mutableStateOf,作为 Compose 能感知的"盒子"
  2. 创建一个 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 更新 → 文字重新显示。

posted @ 2026-05-04 12:41  气温骤降  阅读(10)  评论(0)    收藏  举报