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。

相关推荐
一笑的小酒馆6 小时前
Android12Launcher3实现应用裁剪
android
FinelyYang12 小时前
UniApp + LiveKit:Android 端为什么要用原生做屏幕共享,以及我们怎么落地的
android·uni-app
蓝速科技12 小时前
蓝速科技信创落地:平衡安全合规与项目成本的实战方案
android·运维·人工智能·科技·安全·电脑·鸿蒙
又见情义14 小时前
RK3568 Android 13 SDK 开机自动开启共享热点实践
android
mmsx15 小时前
return false 不是拦截:一次按键触发两个动作的事件冒泡陷阱
android·bug
DU_YULIN16 小时前
Camera HAL ThreadManager概述
android·camera hal
淡淡的香烟16 小时前
Android中的MQTT通信原理解析
android·物联网
淡淡的香烟16 小时前
Android Camera开发详解
android·音视频
mmsx17 小时前
基于 Android 的校园信息管理系统源码
android·java·okhttp
YF021118 小时前
如何进入Android工程模式
android