Android Compose 权限申请完整指南

Android Compose 权限申请完整指南

在 Jetpack Compose 中处理运行时权限申请需要结合传统的权限 API 和 Compose 的状态管理。以下是完整的实现方案:

1. 基本权限申请流程

添加依赖

gradle 复制代码
implementation "com.google.accompanist:accompanist-permissions:0.34.0"

基本权限检查与申请

kotlin 复制代码
@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. 多权限处理

同时请求多个权限

kotlin 复制代码
@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. 优化用户体验

优雅处理权限拒绝

kotlin 复制代码
@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("取消")
                }
            }
        )
    }
    
    // 主界面内容...
}

扩展函数打开设置

kotlin 复制代码
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. 权限状态管理

创建可重用权限逻辑

kotlin 复制代码
class PermissionManager {
    private val _permissionStatus = mutableStateOf<Map<String, Boolean>>(emptyMap())
    val permissionStatus: State<Map<String, Boolean>> = _permissionStatus
    
    fun updatePermission(permission: String, isGranted: Boolean) {
        _permissionStatus.value = _permissionStatus.value + (permission to isGranted)
    }
}

@Composable
fun rememberPermissionManager(): PermissionManager {
    val context = LocalContext.current
    return 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 中管理权限

kotlin 复制代码
class MyViewModel : ViewModel() {
    private val _permissionState = mutableStateOf<PermissionUiState>(PermissionUiState.Initial)
    val permissionState: State<PermissionUiState> = _permissionState
    
    fun 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.current
    val activity = context as ComponentActivity
    val permissionState = rememberPermissionState(Manifest.permission.CAMERA) { isGranted ->
        viewModel.onPermissionResult(Manifest.permission.CAMERA, isGranted)
    }
    
    // 根据 ViewModel 状态显示不同 UI
    when (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. 特殊权限处理

处理后台定位权限

kotlin 复制代码
@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. 测试策略

编写权限测试

kotlin 复制代码
@Test
fun cameraPermissionTest() {
    composeTestRule.setContent {
        PermissionSample()
    }
    
    // 检查初始状态
    composeTestRule.onNodeWithText("请求相机权限").assertExists()
    
    // 模拟权限授予
    val context = InstrumentationRegistry.getInstrumentation().targetContext
    val permissionState = rememberPermissionState(Manifest.permission.CAMERA)
    permissionState.status = PermissionStatus.Granted
    
    // 检查授权后状态
    composeTestRule.onNodeWithText("相机权限已授予").assertExists()
}

最佳实践

  1. 按需请求:只在用户尝试使用相关功能时请求权限
  2. 解释清楚:对敏感权限提供清晰的解释 (rationale)
  3. 优雅降级:当权限被拒绝时提供替代方案
  4. 测试所有路径:测试授予、拒绝和永久拒绝的情况
  5. 组合使用:将权限状态与业务逻辑分离
  6. 及时更新:关注 Android 新版本的权限变更

通过以上方法,你可以在 Jetpack Compose 应用中高效、用户友好地处理运行时权限申请。

相关推荐
tangweiguo030519877 天前
androd的XML页面 跳转 Compose Activity 卡顿问题
compose
tangweiguo030519877 天前
iOS 风格弹框组件集 (Compose版)
compose
tangweiguo030519877 天前
Android Material Design 3 主题配色终极指南:XML 与 Compose 全解析
compose
tangweiguo030519878 天前
Android Compose 中获取和使用 Context 的完整指南
android·compose
tangweiguo0305198710 天前
Jetpack Compose 自定义组件完全指南
compose
tangweiguo0305198711 天前
打破界限:Android XML与Jetpack Compose深度互操作指南
android·kotlin·compose
wavky1 个月前
零经验选手,Compose 一天开发一款小游戏!
compose
亚林瓜子1 个月前
Minio安装(Docker Compose方式)
运维·docker·容器·minio·compose
氦客3 个月前
Android Compose 显示底部对话框 (ModalBottomSheet),实现类似BottomSheetDialog的效果
android·dialog·ui·compose·modal·bottomsheet·底部对话框