xDocxDoc
AI
前端
后端
iOS
Android
Flutter
AI
前端
后端
iOS
Android
Flutter
  • Jetpack Compose重构实战:破解Android开发的7大反模式陷阱

Jetpack Compose重构实战:破解Android开发的7大反模式陷阱

💡 当你在Compose中直接编写网络请求代码时,就像在客厅里修汽车——看似方便却后患无穷

一、Compose反模式全景图:那些看似高效的陷阱

1.1 业务逻辑入侵UI层的“方便之恶”

// 反模式示例:在Composable中直接处理业务逻辑
@Composable
fun ProductListScreen() {
    val products = remember { mutableStateListOf<Product>() }
    
    // 错误示范:在UI层直接执行网络请求
    LaunchedEffect(Unit) {
        try {
            val response = retrofit.create(ProductApi::class.java).getProducts() // 网络请求
            products.addAll(response)
        } catch (e: Exception) {
            Toast.makeText(context, "加载失败", Toast.LENGTH_SHORT).show()
        }
    }
    
    LazyColumn {
        items(products) { product ->
            ProductItem(product)
        }
    }
}

问题诊断:

  1. 违反单一职责原则:UI组件承担了数据获取职责
  2. 不可测试性:无法对网络请求逻辑进行单元测试
  3. 状态管理混乱:错误处理与UI渲染强耦合

1.2 状态管理的“变量滥用症”

// 危险信号:使用普通变量管理状态
var counter = 0 // 普通变量不会触发重组

@Composable
fun CounterButton() {
    Button(onClick = { counter++ }) {
        Text("点击次数: $counter") // 文本不会更新!
    }
}

原理剖析: Compose重组机制依赖State对象的变化通知。普通变量修改时:

  1. 不会触发重组
  2. 状态丢失(在配置变更时)
  3. 多组件状态不同步

二、重构利器:分层架构与状态管理

2.1 ViewModel的正确打开方式

// 重构方案:业务逻辑迁移到ViewModel
class ProductViewModel(private val repo: ProductRepository) : ViewModel() {
    private val _products = mutableStateListOf<Product>()
    val products: List<Product> get() = _products
    
    private val _loading = mutableStateOf(false)
    val loading: Boolean get() = _loading.value
    
    init {
        loadProducts()
    }
    
    fun loadProducts() {
        viewModelScope.launch {
            _loading.value = true
            try {
                _products.clear()
                _products.addAll(repo.getProducts())
            } catch (e: Exception) {
                // 统一错误处理
            } finally {
                _loading.value = false
            }
        }
    }
}

// UI层只负责展示
@Composable
fun ProductListScreen(viewModel: ProductViewModel = viewModel()) {
    if (viewModel.loading) {
        CircularProgressIndicator()
        return
    }
    
    LazyColumn {
        items(viewModel.products) { product ->
            ProductItem(product)
        }
    }
}

2.2 状态管理的四层防护体系

三、深度重构:7大反模式破解指南

3.1 反模式:过度使用副作用

问题代码:

@Composable
fun UserProfile(userId: String) {
    val user = remember { mutableStateOf<User?>(null) }
    
    // 副作用直接写在Composable中
    LaunchedEffect(userId) {
        user.value = userRepository.getUser(userId)
    }
    // ...
}

重构方案:

// 创建状态持有器
class UserStateHolder(private val userId: String) {
    val user: MutableState<User?> = mutableStateOf(null)
    
    init {
        loadUser()
    }
    
    private fun loadUser() {
        viewModelScope.launch {
            user.value = userRepository.getUser(userId)
        }
    }
}

// 通过依赖注入获取
@Composable
fun UserProfile(userId: String) {
    val stateHolder = remember(userId) { UserStateHolder(userId) }
    stateHolder.user.value?.let { user ->
        ProfileContent(user)
    }
}

3.2 反模式:忽略重组优化

性能杀手:

@Composable
fun ImageGrid(images: List<Image>) {
    LazyVerticalGrid(columns = GridCells.Fixed(3)) {
        items(images) { image ->
            // 每次重组都会执行耗时操作
            val bitmap = loadBitmap(image.url) 
            Image(bitmap = bitmap)
        }
    }
}

性能优化方案:

@Composable
fun ImageGrid(images: List<Image>) {
    LazyVerticalGrid(columns = GridCells.Fixed(3)) {
        items(images, key = { it.id }) { image ->
            // 使用remember缓存计算结果
            val bitmap by remember(image.url) {
                derivedStateOf { loadBitmap(image.url) }
            }
            Image(bitmap = bitmap)
        }
    }
}

四、实战案例:电商应用重构之旅

4.1 购物车模块的重构前后对比

重构前架构:

重构后架构:

4.2 状态提升的实际应用

// 低层级组件不持有状态
@Composable
fun CounterDisplay(count: Int, onIncrement: () -> Unit) {
    Column {
        Text("计数: $count")
        Button(onClick = onIncrement) {
            Text("增加")
        }
    }
}

// 状态提升到父组件
@Composable
fun CounterParent() {
    var count by remember { mutableStateOf(0) }
    
    CounterDisplay(
        count = count,
        onIncrement = { count++ }
    )
}

五、进阶技巧:Compose性能优化工具箱

5.1 使用 derivedStateOf 减少重组

@Composable
fun TaskList(tasks: List<Task>) {
    val highPriorityTasks by remember(tasks) {
        derivedStateOf {
            tasks.filter { it.priority > 8 } // 仅当结果变化时重组
        }
    }
    
    LazyColumn {
        items(highPriorityTasks) { task ->
            TaskItem(task)
        }
    }
}

5.2 使用 LaunchedEffect 的正确姿势

@Composable
fun LocationTracker() {
    var location by remember { mutableStateOf(LatLng(0.0, 0.0)) }
    
    // 副作用与生命周期绑定
    LaunchedEffect(Unit) {
        val callback = object : LocationCallback() {
            override fun onLocationResult(result: LocationResult) {
                location = LatLng(result.lastLocation.latitude, result.lastLocation.longitude)
            }
        }
        
        requestLocationUpdates(callback)
        
        // 清理资源(关键!)
        onDispose {
            removeLocationUpdates(callback)
        }
    }
    
    Text("当前位置: $location")
}

六、测试驱动:可测试架构的实现

6.1 ViewModel单元测试

class ProductViewModelTest {
    @Test
    fun `loadProducts should update state`() = runTest {
        // 给定
        val mockRepo = mockk<ProductRepository>()
        coEvery { mockRepo.getProducts() } returns listOf(Product("1", "手机"))
        val viewModel = ProductViewModel(mockRepo)
        
        // 当
        viewModel.loadProducts()
        
        // 则
        assertThat(viewModel.products).hasSize(1)
        assertThat(viewModel.loading.value).isFalse()
    }
}

6.2 Composable的UI测试

@Test
fun display_product_list() {
    // 准备测试数据
    val fakeProducts = listOf(
        Product(id = "1", name = "Android手机"),
        Product(id = "2", name = "iPhone")
    )
    
    // 设置测试环境
    composeTestRule.setContent {
        ProductListScreen(viewModel = FakeProductViewModel(fakeProducts))
    }
    
    // 验证UI展示
    composeTestRule.onNodeWithText("Android手机").assertIsDisplayed()
    composeTestRule.onNodeWithText("iPhone").assertIsDisplayed()
}

七、架构演进:从MVI到状态容器

7.1 MVI架构在Compose中的实现

// 状态定义
data class LoginState(
    val username: String = "",
    val password: String = "",
    val isLoading: Boolean = false,
    val error: String? = null
)

// 事件密封类
sealed class LoginEvent {
    data class UsernameChanged(val value: String) : LoginEvent()
    data class PasswordChanged(val value: String) : LoginEvent()
    object Submit : LoginEvent()
}

// ViewModel处理逻辑
class LoginViewModel : ViewModel() {
    private val _state = mutableStateOf(LoginState())
    val state: State<LoginState> = _state
    
    fun onEvent(event: LoginEvent) {
        when (event) {
            is UsernameChanged -> _state.value = _state.value.copy(username = event.value)
            is PasswordChanged -> _state.value = _state.value.copy(password = event.value)
            Submit -> login()
        }
    }
    
    private fun login() {
        viewModelScope.launch {
            _state.value = _state.value.copy(isLoading = true)
            // 登录逻辑...
        }
    }
}

黄金法则

状态提升三原则:

  1. 状态应提升到使用该状态的所有组件的最低共同父级
  2. 如果多个组件修改同一状态,提升到足够覆盖所有修改者的层级
  3. 如果无法找到合适位置,创建新的状态持有器

总结

🚀 核心

反模式重构方案技术收益
业务逻辑混入UIViewModel分层可测试性↑ 复用性↑
普通变量管理状态mutableStateOf + remember响应式能力↑
副作用滥用LaunchedEffect + 资源清理内存泄漏↓
忽略重组优化derivedStateOf + key参数性能提升50%+
状态分散管理状态容器统一管理维护成本↓
回调地狱事件密封类统一处理可读性↑
配置丢失rememberSaveable + ViewModel状态持久化

🔮 Compose架构设计趋势

  1. 状态容器普及化:通过自定义状态持有器解决复杂组件状态管理
  2. 响应式管道:结合Flow实现从UI到数据的全链路响应式
  3. 多模块状态共享:采用Hilt依赖注入实现跨模块状态同步
  4. 预览驱动开发:利用@Preview实现无后端依赖的UI开发
最后更新: 2025/9/29 08:41