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

相关推荐
TeleostNaCl15 小时前
Android | 启用 TextView 跑马灯效果的方法
android·经验分享·android runtime
TheNextByte116 小时前
Android USB文件传输无法使用?5种解决方法
android
quanyechacsdn17 小时前
Android Studio创建库文件用jitpack构建后使用implementation方式引用
android·ide·kotlin·android studio·implementation·android 库文件·使用jitpack
程序员陆业聪18 小时前
聊聊2026年Android开发会是什么样
android
编程大师哥18 小时前
Android分层
android
极客小云20 小时前
【深入理解 Android 中的 build.gradle 文件】
android·安卓·安全架构·安全性测试
Juskey iii20 小时前
Android Studio Electric Eel | 2022.1.1 Patch 2 版本下载
android·ide·android studio
Android技术之家20 小时前
2025年度Android行业总结:AI驱动生态重构,跨端融合开启新篇
android·人工智能·重构
洞见前行20 小时前
Android第二代加固技术原理详解(附源码)
android
风清云淡_A20 小时前
【JetCompose】入门教程实战基础案例01之显隐动画
android