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 在保持声明式的同时,安全地与命令式世界交互。

相关推荐
右手吉他32 分钟前
高通平台IA APN问题分析之二
android·物联网
夜雪一千1 小时前
MySQL 自增 ID 的底层原理与坑点
android·mysql·adb
不浪费一个字节1 小时前
一个纯 Python 实现的 APK 解析与设备操控工具箱
android
itmigrate2 小时前
PHP 导出大 CSV 时内存溢出的一次排查
android·开发语言·php
hai_android2 小时前
LiveEventBusCore 跨 Activity 通讯源码解析
android·kotlin·android jetpack
维克兜率天2 小时前
【维克】统计套利建模:从因子到信号的完整流程
android·开发语言·python·算法·kotlin
hunterandroid2 小时前
Room 数据库迁移与多表关联查询实战
android
吐了啊取名字太难2 小时前
Android 相机 USB 连接别写在 Activity 里:锁屏、切页和后台传输怎么设计?
android·数码相机
hunterandroid3 小时前
Android ViewModel + LiveData 工程实践:配置变更、一次性事件与状态恢复
android