Jetpack Compose之“副作用”的讲解

Compose附带效应的核心概念

在 Jetpack Compose 中,"附带效应"(Side Effects)是一个核心概念,用于处理 "在 Compose 的声明式 UI 之外发生的、与 UI 状态同步但不直接参与 UI 渲染的操作"。

📌 一句话定义:

附带效应 = 在 Composable 函数中执行的、会影响外部世界(如启动协程、修改非 Compose 状态、调用系统 API)的操作。

为什么需要"附带效应"?

Compose 的核心是纯函数 + 响应式状态驱动 UI 。理想情况下,@Composable 函数应该是:

  • 无副作用(Pure):只根据输入状态生成 UI,不修改外部状态。
  • 可重入(Re-entrant):可能被多次、任意顺序调用。

但现实开发中,我们经常需要:

  • 启动网络请求
  • 记录埋点日志
  • 修改 ViewModel 中的状态
  • 请求权限
  • 播放音效
  • 跳转页面

这些操作不能随意执行 (比如每次重组都调一次API就炸了),必须 精确控制执行时机和次数

Jetpack Compose 提供的主要附带效应 API

🧪 常见使用场景详解

1️⃣ LaunchedEffect ------ 最常用!

在协程中执行逻辑,仅当 key 变化时重新启动。

✅ 场景:页面加载数据

kotlin 复制代码
@Composable
fun UserProfileScreen(userId: String, viewModel: UserViewModel) {
    // 当 userId 变化时,重新加载用户数据
    LaunchedEffect(userId) {
        viewModel.loadUser(userId)
    }

    val user by viewModel.userState.collectAsState()
    // ... 显示 UI
}

✅ 场景:收集 Flow(替代 LiveData)

kotlin 复制代码
LaunchedEffect(Unit) {
    viewModel.uiState.collect { state ->
        when (state) {
            is Success -> showSuccess(state.data)
            is Error -> showError(state.message)
        }
    }
}

⚠️ 注意:不要写 LaunchedEffect(true),应使用 Unit 或具体 key。

2️⃣ DisposableEffect ------ 需要清理的资源

类似 android 中Activity的生命周期 onResume / onDestroy,进入时执行,离开时清理。

✅ 场景:注册广播接收器

kotlin 复制代码
@Composable
fun BatteryStatusScreen() {
    var batteryLevel by remember { mutableStateOf(0f) }

    DisposableEffect(Unit) {
        val receiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0).toFloat()
            }
        }

        context.registerReceiver(receiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED))

        // 返回清理逻辑
        onDispose {
            context.unregisterReceiver(receiver)
        }
    }

    Text("Battery: ${batteryLevel}%")
}

✅ 场景:连接蓝牙设备、开启摄像头等。

3️⃣ rememberUpdatedState ------ 解决"闭包捕获旧值"问题

kotlin 复制代码
@Composable
fun TimerScreen(onTick: () -> Unit) {
    val currentOnTick by rememberUpdatedState(onTick)

    LaunchedEffect(Unit) {
        while (true) {
            delay(1000)
            currentOnTick() // ← 总是最新版本
        }
    }
}

4️⃣ SideEffect ------ 每次重组都执行(谨慎!)

用于 将 Compose 状态同步到非 Compose 系统。

✅ 场景:设置非 Compose 组件的状态(如 Fragment)

kotlin 复制代码
@Composable
fun SyncWithFragment(title: String) {
    SideEffect {
        // 每次 title 变化,都更新 Activity 标题
        (LocalContext.current as? AppCompatActivity)?.supportActionBar?.title = title
    }
}

⚠️ 不要在这里做耗时操作或修改 Compose State!会导致无限重组。

5️⃣ produceState ------ 封装异步数据加载

将任意异步操作(如 Retrofit 调用)转换为 State。

kotlin 复制代码
@Composable
fun loadUserProfile(userId: String): State<Resource<User>> {
    return produceState(initialValue = Resource.Loading()) {
        try {
            val user = api.getUser(userId)
            value = Resource.Success(user)
        } catch (e: Exception) {
            value = Resource.Error(e.message ?: "Unknown error")
        }
    }
}

// 使用
val userState by loadUserProfile("123")

🚫 常见错误 & 最佳实践

✅ 总结:什么时候用附带效应?

💡 核心思想:

附带效应 = "受控的副作用"。

它让 Compose 在保持声明式的同时,安全地与命令式世界交互。

相关推荐
ujainu1 小时前
告别杂乱!Flutter + OpenHarmony 鸿蒙记事本的标签与分类管理(三)
android·flutter·openharmony
常利兵2 小时前
Android内存泄漏:成因剖析与高效排查实战指南
android
·云扬·2 小时前
MySQL 8.0 Redo Log 归档与禁用实战指南
android·数据库·mysql
野生技术架构师2 小时前
SQL语句性能优化分析及解决方案
android·sql·性能优化
doupoa3 小时前
内存指针是什么?为什么指针还要有偏移量?
android·c++
非凡ghost4 小时前
PowerDirector安卓版(威力导演安卓版)
android·windows·学习·软件需求
独行soc4 小时前
2026年渗透测试面试题总结-19(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
爱装代码的小瓶子6 小时前
【C++与Linux基础】进程间通讯方式:匿名管道
android·c++·后端
兴趣使然HX6 小时前
Android绘帧流程解析
android
JMchen1237 小时前
Android UDP编程:实现高效实时通信的全面指南
android·经验分享·网络协议·udp·kotlin