Compose @Immutable注解

@Immutable 是Jetpack Compose中的一个重要注解,它比 @Stable 更严格,专门用于标记不可变类型。

@Immutable的定义

概念

@Immutable 表示被标记的类型是完全不可变的,即一旦创建就永远不会改变。

kotlin 复制代码
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class Immutable

不可变性的严格要求

要被标记为 @Immutable,类型必须满足:

  1. 所有公共属性都是不可变的:只读属性,且引用的对象也不可变
  2. 所有公共方法都是纯函数:相同输入总是产生相同输出,无副作用
  3. 类实例一旦创建就永不改变:没有任何可变状态

@Immutable vs @Stable 对比

kotlin 复制代码
// @Stable:可以变化,但变化时会通知Compose
@Stable
class StableCounter {
    private var _value by mutableStateOf(0)
    val value: Int get() = _value
    
    fun increment() { _value++ } // 可以改变,但会通知
}

// @Immutable:完全不可变
@Immutable
data class ImmutablePoint(
    val x: Int,
    val y: Int
) {
    // 不能有任何可变状态
    // 所有操作都返回新实例
    fun move(dx: Int, dy: Int) = ImmutablePoint(x + dx, y + dy)
}

@Immutable解决的核心问题

问题1:深度比较的性能开销

kotlin 复制代码
// ❌ 没有@Immutable:需要深度比较
data class ComplexData(
    val items: List<Item>,
    val metadata: Map<String, Any>,
    val config: Configuration
)

@Composable
fun ExpensiveComponent(data: ComplexData) {
    // Compose需要深度比较所有字段来判断是否重组
    // 对于复杂对象,这可能很昂贵
    Text("Items count: ${data.items.size}")
}

// ✅ 使用@Immutable:可以优化比较
@Immutable
data class ImmutableComplexData(
    val items: List<Item>,
    val metadata: Map<String, Any>,
    val config: Configuration
)

@Composable
fun OptimizedComponent(data: ImmutableComplexData) {
    // Compose知道这是不可变的,可以使用更高效的比较策略
    Text("Items count: ${data.items.size}")
}

问题2:引用相等性的不确定性

kotlin 复制代码
// ❌ 问题场景:即使内容相同,引用不同导致重组
@Composable
fun ProblematicParent() {
    var count by remember { mutableStateOf(0) }
    
    // 每次重组都创建新的对象,即使内容相同
    val config = UserConfig(
        theme = "dark",
        language = "zh",
        version = 1
    )
    
    ChildComponent(config = config) // 总是重组
    
    Button(onClick = { count++ }) {
        Text("Trigger recomposition")
    }
}

// ✅ 解决方案:使用@Immutable
@Immutable
data class UserConfig(
    val theme: String,
    val language: String,
    val version: Int
)

@Composable
fun OptimizedParent() {
    var count by remember { mutableStateOf(0) }
    
    // 使用remember缓存不可变对象
    val config = remember {
        UserConfig(
            theme = "dark",
            language = "zh", 
            version = 1
        )
    }
    
    ChildComponent(config = config) // 不会重组
    
    Button(onClick = { count++ }) {
        Text("Trigger recomposition")
    }
}

使用场景详解

场景1:配置类和常量类

kotlin 复制代码
@Immutable
data class AppConfig(
    val apiBaseUrl: String,
    val timeoutMs: Long,
    val retryCount: Int,
    val features: Set<String>
) {
    companion object {
        val DEFAULT = AppConfig(
            apiBaseUrl = "https://api.example.com",
            timeoutMs = 30_000L,
            retryCount = 3,
            features = setOf("feature1", "feature2")
        )
    }
}

@Immutable
data class ThemeColors(
    val primary: Color,
    val secondary: Color,
    val background: Color,
    val surface: Color
) {
    companion object {
        val Light = ThemeColors(
            primary = Color(0xFF6200EE),
            secondary = Color(0xFF03DAC6),
            background = Color(0xFFFFFFFF),
            surface = Color(0xFFFFFFFF)
        )
        
        val Dark = ThemeColors(
            primary = Color(0xFFBB86FC),
            secondary = Color(0xFF03DAC6),
            background = Color(0xFF121212),
            surface = Color(0xFF121212)
        )
    }
}

场景2:数据传输对象(DTO)

kotlin 复制代码
@Immutable
data class UserProfile(
    val id: String,
    val name: String,
    val email: String,
    val avatar: String,
    val preferences: UserPreferences
)

@Immutable
data class UserPreferences(
    val notifications: Boolean,
    val darkMode: Boolean,
    val language: String
)

@Immutable
data class ApiResponse<T>(
    val data: T?,
    val message: String,
    val status: Int,
    val timestamp: Long
)

// 使用示例
@Composable
fun UserProfileCard(profile: UserProfile) {
    // 由于profile是不可变的,Compose可以进行高效比较
    Card {
        Column {
            AsyncImage(
                model = profile.avatar,
                contentDescription = null
            )
            Text(profile.name)
            Text(profile.email)
        }
    }
}

场景3:几何和数学类型

kotlin 复制代码
@Immutable
data class Point(val x: Float, val y: Float) {
    operator fun plus(other: Point) = Point(x + other.x, y + other.y)
    operator fun minus(other: Point) = Point(x - other.x, y - other.y)
    operator fun times(scalar: Float) = Point(x * scalar, y * scalar)
}

@Immutable
data class Rectangle(
    val topLeft: Point,
    val size: Size
) {
    val bottomRight: Point get() = Point(
        topLeft.x + size.width,
        topLeft.y + size.height
    )
    
    fun contains(point: Point): Boolean {
        return point.x >= topLeft.x && 
               point.x <= bottomRight.x &&
               point.y >= topLeft.y && 
               point.y <= bottomRight.y
    }
}

@Immutable
data class Size(val width: Float, val height: Float) {
    val area: Float get() = width * height
    val aspectRatio: Float get() = width / height
}

场景4:UI状态快照

kotlin 复制代码
@Immutable
data class LoadingState<T>(
    val isLoading: Boolean = false,
    val data: T? = null,
    val error: String? = null,
    val lastUpdated: Long = 0L
) {
    val isSuccess: Boolean get() = data != null && !isLoading && error == null
    val isError: Boolean get() = error != null && !isLoading
    val isEmpty: Boolean get() = data == null && !isLoading && error == null
    
    companion object {
        fun <T> loading() = LoadingState<T>(isLoading = true)
        fun <T> success(data: T) = LoadingState(
            data = data, 
            lastUpdated = System.currentTimeMillis()
        )
        fun <T> error(message: String) = LoadingState<T>(error = message)
    }
}

@Immutable
data class PagingState<T>(
    val items: List<T> = emptyList(),
    val isLoading: Boolean = false,
    val hasMore: Boolean = true,
    val error: String? = null
) {
    fun loadMore() = copy(isLoading = true, error = null)
    fun loadSuccess(newItems: List<T>, hasMore: Boolean) = copy(
        items = items + newItems,
        isLoading = false,
        hasMore = hasMore,
        error = null
    )
    fun loadError(error: String) = copy(
        isLoading = false,
        error = error
    )
}

场景5:函数式数据结构

kotlin 复制代码
@Immutable
sealed class ImmutableList<out T> {
    abstract val size: Int
    abstract fun get(index: Int): T
    abstract fun add(element: @UnsafeVariance T): ImmutableList<T>
    
    object Empty : ImmutableList<Nothing>() {
        override val size = 0
        override fun get(index: Int) = throw IndexOutOfBoundsException()
        override fun add(element: Nothing) = Node(element, this)
    }
    
    data class Node<T>(
        val head: T,
        val tail: ImmutableList<T>
    ) : ImmutableList<T>() {
        override val size = 1 + tail.size
        
        override fun get(index: Int): T = when {
            index == 0 -> head
            index > 0 -> tail.get(index - 1)
            else -> throw IndexOutOfBoundsException()
        }
        
        override fun add(element: T) = Node(element, this)
    }
}

// 使用示例
@Composable
fun ImmutableListDisplay(list: ImmutableList<String>) {
    LazyColumn {
        items(list.size) { index ->
            Text(list.get(index))
        }
    }
}

带来的优化效果

1. 编译时优化

kotlin 复制代码
// Compose编译器对@Immutable的优化
@Immutable
data class Product(val id: String, val name: String, val price: Double)

@Composable
fun ProductCard(product: Product) {
    // 编译器生成的伪代码:
    // if (product !== previousProduct) {
    //     // 可以直接使用引用比较,无需深度比较
    //     recompose()
    // }
    
    Card {
        Column {
            Text(product.name)
            Text("$${product.price}")
        }
    }
}

2. 内存分配优化

kotlin 复制代码
@Immutable
data class CacheKey(
    val userId: String,
    val resourceType: String,
    val version: Int
)

class OptimizedCache {
    private val cache = mutableMapOf<CacheKey, Any>()
    
    fun get(key: CacheKey): Any? {
        // 由于CacheKey是不可变的,可以安全地用作Map的key
        // 不用担心key被修改导致的问题
        return cache[key]
    }
    
    fun put(key: CacheKey, value: Any) {
        cache[key] = value
    }
}

3. 并发安全性

kotlin 复制代码
@Immutable
data class ThreadSafeConfig(
    val settings: Map<String, String>,
    val flags: Set<String>,
    val values: List<Int>
) {
    // 不可变对象天然线程安全
    // 可以在多个线程间安全共享
}

class ConfigManager {
    private var _config by mutableStateOf(ThreadSafeConfig(
        settings = emptyMap(),
        flags = emptySet(),
        values = emptyList()
    ))
    
    val config: ThreadSafeConfig get() = _config
    
    fun updateConfig(newConfig: ThreadSafeConfig) {
        _config = newConfig // 原子操作,线程安全
    }
}

4. 性能测试对比

kotlin 复制代码
@Composable
fun PerformanceComparison() {
    // 测量重组性能
    val mutableData = remember { MutableData() }
    val immutableData = remember { ImmutableData() }
    
    Column {
        // 可变数据:每次都需要深度比较
        MutableDataComponent(mutableData)
        
        // 不可变数据:快速引用比较
        ImmutableDataComponent(immutableData)
    }
}

data class MutableData(
    var items: MutableList<String> = mutableListOf(),
    var count: Int = 0
)

@Immutable
data class ImmutableData(
    val items: List<String> = emptyList(),
    val count: Int = 0
)

@Composable
fun MutableDataComponent(data: MutableData) {
    // Compose必须检查data的所有字段是否改变
    // 对于大型对象,这会很昂贵
    Text("Mutable: ${data.items.size}")
}

@Composable
fun ImmutableDataComponent(data: ImmutableData) {
    // Compose可以使用快速的引用比较
    Text("Immutable: ${data.items.size}")
}

为什么@Immutable能解决这些问题

1. 编译器信任机制

kotlin 复制代码
// 编译器对@Immutable的处理
@Immutable
class TrustedImmutable(val value: String)

@Composable
fun ComponentWithTrustedParam(param: TrustedImmutable) {
    // 编译器生成的比较逻辑:
    // if (param !== previousParam) {
    //     // 只需要引用比较,因为@Immutable保证了不变性
    //     recompose()
    // } else {
    //     skip() // 跳过重组
    // }
}

2. 引用透明性

kotlin 复制代码
@Immutable
data class MathVector(val x: Double, val y: Double, val z: Double) {
    // 纯函数:相同输入总是相同输出
    fun magnitude(): Double = sqrt(x * x + y * y + z * z)
    fun normalize(): MathVector {
        val mag = magnitude()
        return MathVector(x / mag, y / mag, z / mag)
    }
    
    // 可以安全缓存计算结果
    val cachedMagnitude by lazy { magnitude() }
}

@Composable
fun VectorDisplay(vector: MathVector) {
    // 由于vector是不可变的,Compose可以安全地假设:
    // 1. vector.magnitude()总是返回相同值
    // 2. 可以缓存计算结果
    // 3. 引用相等 == 值相等
    
    Text("Vector: (${vector.x}, ${vector.y}, ${vector.z})")
    Text("Magnitude: ${vector.magnitude()}")
}

3. 结构共享优化

kotlin 复制代码
@Immutable
data class ImmutableTree<T>(
    val value: T,
    val left: ImmutableTree<T>? = null,
    val right: ImmutableTree<T>? = null
) {
    // 添加节点返回新树,但共享未修改的部分
    fun insert(newValue: T): ImmutableTree<T> = when {
        newValue < value -> copy(left = left?.insert(newValue) ?: ImmutableTree(newValue))
        newValue > value -> copy(right = right?.insert(newValue) ?: ImmutableTree(newValue))
        else -> this // 值相同,返回自身
    }
}

@Composable
fun TreeVisualizer(tree: ImmutableTree<Int>) {
    // 由于使用结构共享,大部分节点在修改后仍然是相同的引用
    // Compose可以跳过渲染未改变的子树
    TreeNode(tree)
}

最佳实践指南

1. 何时使用@Immutable

kotlin 复制代码
// ✅ 适合使用@Immutable的场景

// 值对象
@Immutable
data class Money(val amount: BigDecimal, val currency: String) {
    operator fun plus(other: Money): Money {
        require(currency == other.currency)
        return Money(amount + other.amount, currency)
    }
}

// 配置对象
@Immutable
data class DatabaseConfig(
    val host: String,
    val port: Int,
    val database: String,
    val ssl: Boolean
)

// 枚举状态
@Immutable
sealed class NetworkState {
    object Loading : NetworkState()
    object Connected : NetworkState()
    data class Error(val message: String) : NetworkState()
    object Disconnected : NetworkState()
}

2. 何时不使用@Immutable

kotlin 复制代码
// ❌ 不适合使用@Immutable的场景

// 包含可变状态的类
class Repository {
    private var cache: MutableMap<String, Any> = mutableMapOf()
    
    fun getData(key: String): Any? = cache[key]
    fun putData(key: String, value: Any) { cache[key] = value }
}

// 有副作用的类
class Logger {
    fun log(message: String) {
        println(message) // 副作用:输出到控制台
    }
}

// 依赖外部可变状态的类
class TimeBasedCalculator {
    fun getCurrentValue(): Long = System.currentTimeMillis() // 每次调用结果不同
}

3. 组合使用技巧

kotlin 复制代码
// 创建不可变的复合对象
@Immutable
data class GameState(
    val player: Player,
    val enemies: List<Enemy>,
    val score: Int,
    val level: Int
) {
    fun playerMove(direction: Direction): GameState = copy(
        player = player.move(direction)
    )
    
    fun addEnemy(enemy: Enemy): GameState = copy(
        enemies = enemies + enemy
    )
    
    fun updateScore(points: Int): GameState = copy(
        score = score + points
    )
}

@Immutable
data class Player(val x: Int, val y: Int, val health: Int) {
    fun move(direction: Direction): Player = when (direction) {
        Direction.UP -> copy(y = y - 1)
        Direction.DOWN -> copy(y = y + 1)
        Direction.LEFT -> copy(x = x - 1)
        Direction.RIGHT -> copy(x = x + 1)
    }
}

@Immutable
data class Enemy(val x: Int, val y: Int, val type: EnemyType)

enum class Direction { UP, DOWN, LEFT, RIGHT }
enum class EnemyType { GOBLIN, ORC, DRAGON }

4. 与Compose状态集成

kotlin 复制代码
@Composable
fun GameScreen() {
    var gameState by remember { 
        mutableStateOf(
            GameState(
                player = Player(0, 0, 100),
                enemies = emptyList(),
                score = 0,
                level = 1
            )
        )
    }
    
    // 由于GameState是不可变的,状态更新总是创建新实例
    // Compose可以高效地检测变化
    
    GameRenderer(
        state = gameState,
        onPlayerMove = { direction ->
            gameState = gameState.playerMove(direction)
        },
        onScoreUpdate = { points ->
            gameState = gameState.updateScore(points)
        }
    )
}

@Composable
fun GameRenderer(
    state: GameState,
    onPlayerMove: (Direction) -> Unit,
    onScoreUpdate: (Int) -> Unit
) {
    // 由于state是不可变的,只有真正改变时才重组
    Column {
        Text("Score: ${state.score}")
        Text("Level: ${state.level}")
        
        GameBoard(
            player = state.player,
            enemies = state.enemies
        )
        
        ControlPanel(onMove = onPlayerMove)
    }
}

性能监控和调试

1. 重组计数器

kotlin 复制代码
@Composable
fun ImmutablePerformanceTest() {
    var updateTrigger by remember { mutableStateOf(0) }
    
    // 不可变数据
    val immutableData = remember(updateTrigger) {
        ImmutableComplexData.create(updateTrigger)
    }
    
    Column {
        RecompositionCounter("Immutable") {
            ImmutableDataDisplay(immutableData)
        }
        
        Button(onClick = { updateTrigger++ }) {
            Text("Update Data")
        }
    }
}

@Composable
fun RecompositionCounter(
    label: String,
    content: @Composable () -> Unit
) {
    val recomposeCount = remember { mutableIntStateOf(0) }
    
    SideEffect {
        recomposeCount.intValue++
    }
    
    Column {
        Text("$label - 重组次数: ${recomposeCount.intValue}")
        content()
    }
}

2. 内存使用监控

kotlin 复制代码
@Immutable
data class LargeImmutableObject(
    val data: ByteArray,
    val metadata: Map<String, String>
) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is LargeImmutableObject) return false
        return data.contentEquals(other.data) && metadata == other.metadata
    }
    
    override fun hashCode(): Int {
        return 31 * data.contentHashCode() + metadata.hashCode()
    }
    
    companion object {
        fun create(size: Int): LargeImmutableObject {
            val startMemory = Runtime.getRuntime().let { 
                it.totalMemory() - it.freeMemory() 
            }
            
            val obj = LargeImmutableObject(
                data = ByteArray(size),
                metadata = mapOf("size" to size.toString())
            )
            
            val endMemory = Runtime.getRuntime().let { 
                it.totalMemory() - it.freeMemory() 
            }
            
            Log.d("Memory", "创建对象使用内存: ${endMemory - startMemory} bytes")
            return obj
        }
    }
}

总结

@Immutable的核心价值

  1. 性能优化:启用引用相等性比较,避免深度比较
  2. 线程安全:不可变对象天然线程安全
  3. 可预测性:纯函数特性,无副作用
  4. 缓存友好:可以安全地缓存不可变对象
  5. 结构共享:支持高效的不可变数据结构

解决问题的根本原理

  • 编译器信任:告诉Compose可以使用更高效的比较策略
  • 引用透明:相同引用意味着相同内容
  • 不变性保证:一旦创建就永不改变,消除了状态同步问题

选择建议

  • 值对象和数据类:优先使用@Immutable
  • 配置和常量:总是使用@Immutable
  • 需要可变状态:使用@Stable
  • 复杂业务逻辑:根据具体情况选择

记住@Immutable 是一个强契约,只有在真正不可变的类型上使用,错误使用可能导致意外行为!

相关推荐
阿巴斯甜18 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker19 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952720 小时前
Andorid Google 登录接入文档
android
黄林晴21 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android