Accompanist配置变更处理:ViewModel与SavedStateHandle的应用

Accompanist配置变更处理:ViewModel与SavedStateHandle的应用

【免费下载链接】accompanist A collection of extension libraries for Jetpack Compose 【免费下载链接】accompanist 项目地址: https://gitcode.com/gh_mirrors/ac/accompanist

在Android应用开发中,配置变更(Configuration Change)如屏幕旋转、语言切换等场景经常导致界面重建,如何妥善保存和恢复应用状态是开发者面临的核心挑战。本文将结合Accompanist库的实践,详细介绍ViewModel与SavedStateHandle在配置变更处理中的协同应用,帮助你构建更健壮的Jetpack Compose应用。

配置变更的挑战与解决方案

当用户旋转屏幕时,默认情况下Activity会销毁并重建,导致内存中的临时状态丢失。例如在权限请求场景中,用户点击授权后旋转屏幕可能导致状态重置,需要重新请求权限。Accompanist库提供了多种状态保存方案,主要分为三大类:

状态保存方案适用场景持久化能力实现复杂度
remember临时UI状态内存级别简单
rememberSaveable跨配置变更Bundle支持类型中等
ViewModel + SavedStateHandle复杂状态管理支持自定义类型较复杂

Accompanist示例代码中大量使用rememberPermissionState管理权限状态,如sample/src/main/java/com/google/accompanist/sample/permissions/RequestPermissionSample.kt所示:

@OptIn(ExperimentalPermissionsApi::class)
@Composable
private fun Sample() {
    val cameraPermissionState = rememberPermissionState(android.Manifest.permission.CAMERA)
    if (cameraPermissionState.status.isGranted) {
        Text("Camera permission Granted")
    } else {
        // 请求权限UI
    }
}

这种方式在简单场景下足够使用,但当需要在配置变更中保持复杂状态或与业务逻辑解耦时,ViewModel与SavedStateHandle的组合方案更为适合。

ViewModel:配置变更中的状态持有者

ViewModel作为Jetpack架构组件的核心成员,设计目标就是在配置变更时保留数据。它独立于UI控制器的生命周期,在Activity重建时会被保留并关联到新的Activity实例。在Accompanist项目中,虽然未直接提供ViewModel实现,但遵循Jetpack最佳实践,我们可以轻松集成ViewModel到Compose应用中。

ViewModel基本实现

创建一个管理权限状态的ViewModel:

class PermissionViewModel : ViewModel() {
    private val _cameraPermissionGranted = MutableStateFlow(false)
    val cameraPermissionGranted: StateFlow<Boolean> = _cameraPermissionGranted.asStateFlow()

    fun updatePermissionStatus(granted: Boolean) {
        _cameraPermissionGranted.value = granted
    }
}

在Activity中获取ViewModel实例:

class PermissionActivity : ComponentActivity() {
    private val viewModel: PermissionViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyAppTheme {
                PermissionScreen(viewModel)
            }
        }
    }
}

Compose中使用ViewModel

通过viewModel()组合函数在Compose中获取ViewModel实例:

@Composable
fun PermissionScreen(viewModel: PermissionViewModel = viewModel()) {
    val permissionGranted by viewModel.cameraPermissionGranted.collectAsStateWithLifecycle()
    
    if (permissionGranted) {
        Text("Camera permission granted")
    } else {
        RequestPermissionButton { granted ->
            viewModel.updatePermissionStatus(granted)
        }
    }
}

这种架构将状态管理从UI层抽离到ViewModel,即使发生配置变更,ViewModel实例及其持有的状态也会保留,避免重复请求权限或丢失用户操作状态。

SavedStateHandle:状态持久化的增强方案

ViewModel虽然能在配置变更中保留状态,但当系统因内存不足销毁应用进程时,ViewModel也会随之销毁。SavedStateHandle提供了一种机制,可将关键状态数据保存到Bundle中,并在进程重建时恢复。它特别适合以下场景:

  • 用户输入的表单数据
  • 分页加载的当前页码
  • 媒体播放器的播放进度
  • 权限请求的状态标记

SavedStateHandle基本用法

修改上述ViewModel以支持状态持久化:

class PermissionViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    private val _cameraPermissionGranted = MutableStateFlow(
        savedStateHandle.get<Boolean>(KEY_CAMERA_PERMISSION) ?: false
    )
    val cameraPermissionGranted: StateFlow<Boolean> = _cameraPermissionGranted.asStateFlow()

    init {
        _cameraPermissionGranted.onEach { granted ->
            savedStateHandle.set(KEY_CAMERA_PERMISSION, granted)
        }.launchIn(viewModelScope)
    }

    fun updatePermissionStatus(granted: Boolean) {
        _cameraPermissionGranted.value = granted
    }

    companion object {
        private const val KEY_CAMERA_PERMISSION = "camera_permission"
    }
}

自定义类型的持久化

对于复杂数据类型,需要实现Parcelable接口:

@Parcelize
data class UserSession(
    val userId: String,
    val lastLogin: Long
) : Parcelable

class SessionViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    private val _session = MutableStateFlow(
        savedStateHandle.get<UserSession>(KEY_SESSION) ?: UserSession("", 0)
    )
    
    // 状态更新逻辑
}

综合实战:权限请求状态管理

结合Accompanist的权限组件与ViewModel、SavedStateHandle,构建一个完整的权限请求流程:

1. 创建带状态恢复的ViewModel

class CameraPermissionViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    private val _permissionState = MutableStateFlow<PermissionStatus>(
        savedStateHandle.get<PermissionStatus>(KEY_PERMISSION_STATE) ?: PermissionStatus.Denied
    )
    val permissionState: StateFlow<PermissionStatus> = _permissionState.asStateFlow()

    init {
        _permissionState.onEach { state ->
            savedStateHandle.set(KEY_PERMISSION_STATE, state)
        }.launchIn(viewModelScope)
    }

    fun updateState(newState: PermissionStatus) {
        _permissionState.value = newState
    }

    companion object {
        private const val KEY_PERMISSION_STATE = "permission_state"
    }
}

enum class PermissionStatus {
    Granted, Denied, ShouldShowRationale
}

2. 实现Compose权限请求组件

@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun CameraPermissionScreen(
    viewModel: CameraPermissionViewModel = viewModel(),
    onPermissionGranted: () -> Unit
) {
    val permissionState = rememberPermissionState(Manifest.permission.CAMERA)
    val viewModelState by viewModel.permissionState.collectAsStateWithLifecycle()

    LaunchedEffect(permissionState.status) {
        val newState = when {
            permissionState.status.isGranted -> PermissionStatus.Granted
            permissionState.status.shouldShowRationale -> PermissionStatus.ShouldShowRationale
            else -> PermissionStatus.Denied
        }
        viewModel.updateState(newState)
    }

    when (viewModelState) {
        PermissionStatus.Granted -> {
            onPermissionGranted()
        }
        PermissionStatus.ShouldShowRationale -> {
            RationaleContent { permissionState.launchPermissionRequest() }
        }
        PermissionStatus.Denied -> {
            PermissionDeniedContent { permissionState.launchPermissionRequest() }
        }
    }
}

3. 状态流转与配置变更处理

通过这种实现,当用户旋转屏幕或系统销毁进程后重建时,权限状态会通过SavedStateHandle自动恢复。ViewModel与Accompanist权限组件的结合,既保持了Compose的声明式UI风格,又实现了健壮的状态管理。

最佳实践与性能优化

在使用ViewModel和SavedStateHandle时,建议遵循以下最佳实践:

状态粒度控制

  • 避免在SavedStateHandle中存储大型数据集,这会增加序列化/反序列化开销
  • 只保存关键状态标记,而非完整数据(如保存列表位置而非整个列表)
  • 对于大型对象,考虑使用Room数据库持久化

生命周期感知

  • 使用StateFlow和collectAsStateWithLifecycle确保状态收集遵循生命周期
  • 在ViewModel中使用viewModelScope管理协程,避免内存泄漏
  • 及时取消不需要的数据流收集,如:
LaunchedEffect(Unit) {
    viewModel.dataFlow
        .flowWithLifecycle(lifecycle)
        .collect { data ->
            // 更新UI
        }
}

与Accompanist组件配合

  • 结合rememberPermissionState与ViewModel,实现权限状态的双向同步
  • 使用Accompanist的adaptive包组件如TwoPane时,通过ViewModel共享面板状态

总结与扩展学习

本文介绍了在Accompanist项目中使用ViewModel和SavedStateHandle处理配置变更的完整方案,主要包括:

  1. 配置变更的挑战及三种状态保存方案对比
  2. ViewModel在配置变更中的状态保持机制
  3. SavedStateHandle实现状态持久化的方法
  4. 结合Accompanist权限组件的实战案例
  5. 性能优化与最佳实践建议

Accompanist作为Jetpack Compose的扩展库集合,虽然未直接提供ViewModel实现,但完美兼容Jetpack架构组件。通过本文介绍的方法,你可以为Accompanist应用添加健壮的状态管理能力,应对各种配置变更场景。

官方文档:docs/permissions.md 权限示例代码:sample/src/main/java/com/google/accompanist/sample/permissions/ Adaptive布局组件:adaptive/src/main/java/com/google/accompanist/adaptive/

掌握这些技术后,你可以进一步探索:

  • 自定义SavedStateHandle的SaveableStateRegistry
  • 与Hilt结合实现依赖注入
  • 使用DataStore替代SharedPreferences存储复杂状态

【免费下载链接】accompanist A collection of extension libraries for Jetpack Compose 【免费下载链接】accompanist 项目地址: https://gitcode.com/gh_mirrors/ac/accompanist

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值