Android Compose 权限申请完整指南
在 Jetpack Compose 中处理运行时权限申请需要结合传统的权限 API 和 Compose 的状态管理。以下是完整的实现方案:
1. 基本权限申请流程
添加依赖
implementation "com.google.accompanist:accompanist-permissions:0.34.0"
基本权限检查与申请
@Composable
fun PermissionSample() {// 定义需要申请的权限val cameraPermission = remember { PermissionRequest(permission = Manifest.permission.CAMERA,onPermissionResult = { /* 处理结果 */ })}// 检查权限状态val permissionState = rememberPermissionState(permission = Manifest.permission.CAMERA)Column {when {// 已授权permissionState.status.isGranted -> {Text("相机权限已授予")CameraPreview()}// 应展示 rationale (为什么需要权限的解释)permissionState.status.shouldShowRationale -> {Text("需要相机权限才能拍照")Button(onClick = { permissionState.launchPermissionRequest() }) {Text("授予权限")}}// 首次请求或永久拒绝else -> {Button(onClick = { permissionState.launchPermissionRequest() }) {Text("请求相机权限")}}}}
}
2. 多权限处理
同时请求多个权限
@Composable
fun MultiplePermissionsSample() {val permissions = remember {listOf(Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO,Manifest.permission.ACCESS_FINE_LOCATION)}val permissionsState = rememberMultiplePermissionsState(permissions)LaunchedEffect(permissionsState) {if (!permissionsState.allPermissionsGranted) {permissionsState.launchMultiplePermissionRequest()}}when {permissionsState.allPermissionsGranted -> {Text("所有权限已授予")// 显示功能界面}permissionsState.shouldShowRationale -> {AlertDialog(onDismissRequest = { },title = { Text("权限需求") },text = { Text("需要这些权限才能使用完整功能") },confirmButton = {Button(onClick = { permissionsState.launchMultiplePermissionRequest() }) {Text("确定")}})}else -> {Column {Text("部分权限未授予")permissionsState.permissions.forEach { perm ->when (perm.permission) {Manifest.permission.CAMERA -> {Text("- 相机权限: ${perm.status}")}Manifest.permission.RECORD_AUDIO -> {Text("- 录音权限: ${perm.status}")}// 其他权限...}}Button(onClick = { permissionsState.launchMultiplePermissionRequest() }) {Text("重新请求权限")}}}}
}
3. 优化用户体验
优雅处理权限拒绝
@Composable
fun PermissionDeniedHandler() {var showDeniedDialog by remember { mutableStateOf(false) }val permissionState = rememberPermissionState(Manifest.permission.CAMERA) { isGranted ->if (!isGranted) showDeniedDialog = true}if (showDeniedDialog) {AlertDialog(onDismissRequest = { showDeniedDialog = false },title = { Text("权限被拒绝") },text = { Text("请在设置中手动授予权限") },confirmButton = {Button(onClick = {showDeniedDialog = false// 跳转到应用设置permissionState.openSettingScreen(LocalContext.current)}) {Text("去设置")}},dismissButton = {TextButton(onClick = { showDeniedDialog = false }) {Text("取消")}})}// 主界面内容...
}
扩展函数打开设置
fun PermissionState.openSettingScreen(context: Context) {val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {data = Uri.fromParts("package", context.packageName, null)}context.startActivity(intent)
}
4. 权限状态管理
创建可重用权限逻辑
class PermissionManager {private val _permissionStatus = mutableStateOf<Map<String, Boolean>>(emptyMap())val permissionStatus: State<Map<String, Boolean>> = _permissionStatusfun updatePermission(permission: String, isGranted: Boolean) {_permissionStatus.value = _permissionStatus.value + (permission to isGranted)}
}@Composable
fun rememberPermissionManager(): PermissionManager {val context = LocalContext.currentreturn remember {PermissionManager().apply {// 初始化检查已有权限listOf(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION).forEach { permission ->updatePermission(permission,ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED)}}}
}
5. 与 ViewModel 集成
在 ViewModel 中管理权限
class MyViewModel : ViewModel() {private val _permissionState = mutableStateOf<PermissionUiState>(PermissionUiState.Initial)val permissionState: State<PermissionUiState> = _permissionStatefun onPermissionResult(permission: String, isGranted: Boolean) {_permissionState.value = when {isGranted -> PermissionUiState.Granted(permission)ActivityCompat.shouldShowRequestPermissionRationale(activity, permission) -> PermissionUiState.ShowRationale(permission)else -> PermissionUiState.PermanentlyDenied(permission)}}
}sealed class PermissionUiState {object Initial : PermissionUiState()data class Granted(val permission: String) : PermissionUiState()data class ShowRationale(val permission: String) : PermissionUiState()data class PermanentlyDenied(val permission: String) : PermissionUiState()
}@Composable
fun ViewModelIntegratedSample(viewModel: MyViewModel = viewModel()) {val context = LocalContext.currentval activity = context as ComponentActivityval permissionState = rememberPermissionState(Manifest.permission.CAMERA) { isGranted ->viewModel.onPermissionResult(Manifest.permission.CAMERA, isGranted)}// 根据 ViewModel 状态显示不同 UIwhen (val state = viewModel.permissionState.value) {is PermissionUiState.Granted -> CameraScreen()is PermissionUiState.ShowRationale -> RationaleScreen {permissionState.launchPermissionRequest()}is PermissionUiState.PermanentlyDenied -> SettingsScreen {permissionState.openSettingScreen(context)}PermissionUiState.Initial -> LoadingScreen()}
}
6. 特殊权限处理
处理后台定位权限
@Composable
fun BackgroundLocationPermission() {val locationPermissions = remember {listOf(Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION)}val permissionsState = rememberMultiplePermissionsState(locationPermissions)LaunchedEffect(permissionsState) {if (!permissionsState.allPermissionsGranted) {permissionsState.launchMultiplePermissionRequest()}}if (permissionsState.allPermissionsGranted) {Text("前后台定位权限已授予")} else {val backgroundDenied = permissionsState.permissions.any { it.permission == Manifest.permission.ACCESS_BACKGROUND_LOCATION && !it.status.isGranted}if (backgroundDenied) {AlertDialog(title = { Text("后台定位需求") },text = { Text("请授予后台定位权限以持续追踪位置") },confirmButton = {Button(onClick = { permissionsState.launchMultiplePermissionRequest() }) {Text("去设置")}})}}
}
7. 测试策略
编写权限测试
@Test
fun cameraPermissionTest() {composeTestRule.setContent {PermissionSample()}// 检查初始状态composeTestRule.onNodeWithText("请求相机权限").assertExists()// 模拟权限授予val context = InstrumentationRegistry.getInstrumentation().targetContextval permissionState = rememberPermissionState(Manifest.permission.CAMERA)permissionState.status = PermissionStatus.Granted// 检查授权后状态composeTestRule.onNodeWithText("相机权限已授予").assertExists()
}
最佳实践
- 按需请求:只在用户尝试使用相关功能时请求权限
- 解释清楚:对敏感权限提供清晰的解释 (rationale)
- 优雅降级:当权限被拒绝时提供替代方案
- 测试所有路径:测试授予、拒绝和永久拒绝的情况
- 组合使用:将权限状态与业务逻辑分离
- 及时更新:关注 Android 新版本的权限变更
通过以上方法,你可以在 Jetpack Compose 应用中高效、用户友好地处理运行时权限申请。