jetpack compose 副作用 produceState

一、produceState 是 Compose Runtime 提供的一个 Composable 函数,它的核心作用是把非 Compose 的外部数据源(如 Flow、LiveData、回调、轮询等)转换成 Compose 可观察的 State。

kotlin 复制代码
@Composable
fun <T> produceState(
    initialValue: T,
    vararg keys: Any?,
    producer: suspend ProduceStateScope<T>.() -> Unit
): State<T>

关键特性 1、自动生命周期管理:producer 协程在 produceState 进入组合时启动,离开组合时自动取消 2、Key 驱动重启:当 keys 参数发生变化时,正在运行的 producer 会被取消并重新启动 3、值合并(Conflation):如果连续设置的新值与旧值 equals 相等,不会触发重组

例子

kotlin 复制代码
sealed class Result<out T> {
    object Loading : Result<Nothing>()
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Throwable) : Result<Nothing>()
}

// 加载网络图片
@Composable
fun loadNetworkImage(url: String): State<Result<Bitmap>> {
    return produceState<Result<Bitmap>>(
        initialValue = Result.Loading,
        key1 = url          // url 变化时重新加载
    ) {
        value = try {
            Result.Success(loadImage(url))
        } catch (e: Exception) {
            Result.Error(e)
        }
    }
}


// 在 Composable 中使用
@Composable
fun ImageScreen(imageUrl: String) {
    // 调用 produceState,拿到 State<Result<ImageBitmap>>
    val imageState by loadNetworkImage(imageUrl)

    // 根据状态渲染不同 UI
    when (val result = imageState) {
        is Result.Loading -> {
            Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                CircularProgressIndicator()   // 加载中,显示转圈
            }
        }
        is Result.Success -> {
            Image(
                bitmap = result.data,
                contentDescription = "网络图片",
                modifier = Modifier.fillMaxWidth()
            )
        }
        is Result.Error -> {
            Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                Text("加载失败: ${result.exception.message}")
            }
        }
    }
}


// 监听网络状态
@Composable
fun observeConnectivity(): State<Boolean> {
    val context = LocalContext.current
    return produceState(initialValue = false) {
        val manager = context.getSystemService<ConnectivityManager>()!!
        val callback = object : ConnectivityManager.NetworkCallback() {
            override fun onAvailable(network: Network) { value = true }
            override fun onLost(network: Network)      { value = false }
        }
        manager.registerDefaultNetworkCallback(callback)
        // 离开组合时自动清理
        awaitDispose { manager.unregisterNetworkCallback(callback) }
    }
}

一句话总结:把任何异步/回调式的数据源"桥接"进 Compose 的响应式世界。

二、produceState 实现与在ViewModel实现对比 方式一:produceState 写在 Composable(轻量场景)

kotlin 复制代码
@Composable
fun ImageScreen(imageUrl: String) {
    val imageState by loadNetworkImage(imageUrl)  // produceState 在这里

    when (val result = imageState) {
        is Result.Loading -> CircularProgressIndicator()
        is Result.Success -> Image(bitmap = result.data, ...)
        is Result.Error -> Text("加载失败")
    }
}

优点:代码短、零样板、逻辑和 UI 在一个文件里,适合简单页面或原型。

缺点: 无法被多个 Composable 共享同一份加载状态 配置变更(旋转屏幕)时协程会被取消重建,数据可能丢失 不方便做单元测试(耦合在 Composable 里)

方式二:使用 ViewModel

kotlin 复制代码
class ImageViewModel : ViewModel() {
    private val _imageState = MutableStateFlow<Result<ImageBitmap>>(Result.Loading)
    val imageState: StateFlow<Result<ImageBitmap>> = _imageState.asStateFlow()

    private var loadJob: Job? = null

    fun loadImage(url: String) {
        loadJob?.cancel()
        loadJob = viewModelScope.launch {
            _imageState.value = Result.Loading
            _imageState.value = try {
                Result.Success(loadImageFromUrl(url))
            } catch (e: Exception) {
                Result.Error(e)
            }
        }
    }
}

@Composable
fun ImageScreen(viewModel: ImageViewModel = viewModel()) {
    val imageState by viewModel.imageState.collectAsStateWithLifecycle()

    when (val result = imageState) {
        is Result.Loading -> CircularProgressIndicator()
        is Result.Success -> Image(bitmap = result.data, ...)
        is Result.Error -> Text("加载失败")
    }
}

优点: ViewModel 存活,数据不丢,不用重新请求 多个 Composable 订阅同一个 StateFlow 测试 ViewModel 可以脱离 UI 做单元测试 生命周期 viewModelScope 跟随 ViewModel,不影响 Composable 重组

三、原理 核心源码

kotlin 复制代码
@Composable
fun <T> produceState(
    initialValue: T,
    vararg keys: Any?,
    producer: suspend ProduceStateScope<T>.() -> Unit   
): State<T> {
    // ① remember 持有一个可变状态
    val result = remember { mutableStateOf(initialValue) }

    // ② LaunchedEffect 在协程中执行 producer
    LaunchedEffect(keys = keys) {
        ProduceStateScopeImpl(result, coroutineContext).producer()
    }

    // ③ 返回只读 State
    return result
}

producer 代码块的接收者类型是 ProduceStateScope

kotlin 复制代码
producer: suspend ProduceStateScope<T>.() -> Unit

suspend                          ← 这是个挂起函数
ProduceStateScope<T>.            ← 接收者类型(receiver type)
() -> Unit                       ← 无参数,返回 Unit

含义:producer 是一个挂起函数,它没有显式参数,但它有一个隐式的 this,类型是 ProduceStateScope<T>。
kotlin 复制代码
interface ProduceStateScope<T> : MutableState<T>, CoroutineScope {
    suspend fun awaitDispose(onDispose: () -> Unit): Nothing
}

internal class ProduceStateScopeImpl<T>(
    state: MutableState<T>,
    override val coroutineContext: CoroutineContext
) : ProduceStateScope<T>, MutableState<T> by state {

    override suspend fun awaitDispose(onDispose: () -> Unit): Nothing {
        try {
            // 永久挂起,直到协程被取消
            suspendCancellableCoroutine<Nothing> {}
        } finally {
            onDispose()  // 协程取消时在 finally 中执行清理
        }
    }
}

一句话总结

scss 复制代码
produceState = remember { mutableStateOf() } + LaunchedEffect { producer() } + 一个包装了 State 和 CoroutineScope 的作用域对象。

它没有引入任何新的底层机制,只是把"创建状态 + 协程异步更新 + 生命周期绑定 + 清理"这套常见组合封装成了一个简洁的 API。

相关推荐
hunterandroid2 小时前
Android 后台任务可靠性排查:从 WorkManager 观测到失败重试闭环
android·前端
GitLqr4 小时前
Flutter FocusNode 实战指南:玩转键盘焦点与用户输入体验
android·flutter·ios
zzq77974 小时前
Android 17 升级后 System.load 报错排查与加固兼容指南
android·安全·app加固·apk加固·御盾安全·免费加固·御盾加固
酷在前行5 小时前
【R绘图】Nature Communications 半眼图复刻:分布、区间与多面板排版(保姆级教程)
android·开发语言·r语言
2501_932750265 小时前
Android registerForActivityResult 详解
android
小孔龙7 小时前
VSync 与同步屏障:doFrame() 的优先调度
android·性能优化
kfzjw0088 小时前
Android 的一帧完整渲染流程:从 VSYNC 到 Screen 的全链路解密
android
wWYy.9 小时前
Mysql:覆盖索引
android·数据库·mysql
Kapaseker9 小时前
如果我要从 List 中获取一段元素
android·kotlin