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 应用中高效、用户友好地处理运行时权限申请。

相关推荐
le1616169 天前
Android Compose——尺寸修饰符的调用顺序构成的不同尺寸约束效果
android·compose·modifier
le16161610 天前
Android Compose Modifier修饰符
android·compose·modifier
小书房10 天前
Android UI为什么由XML转向Compose
xml·ui·compose·声明式ui
le16161611 天前
Android Compose基础布局——从传统XML的视角切入了解
xml·compose
赏金术士16 天前
企业级 Jetpack Compose 项目(入门版)最佳结构
android·kotlin·compose
Jomurphys17 天前
Compose 调用 - 液态玻璃 Backdrop
android·compose
氦客20 天前
Android Compose 图层的合成 : BlendMode
android·compose·jetpack·layer·blendmode·graphics·图层的合成
赏金术士21 天前
第六章:UI组件与Material3主题
android·ui·kotlin·compose
赏金术士21 天前
Jetpack Compose 底部导航实战教程(完整版)
android·kotlin·compose
程序员煊子22 天前
用 Cursor 从零搭一个 Compose 本地记账 App:实战记录与源码解析
android·kotlin·compose·cursor