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

相关推荐
不用89k14 天前
Compose Kotlin Multiplatform跨平台基础运行
compose
缘来的精彩1 个月前
Kotlin与Jetpack Compose的详细使用指南
android·kotlin·android studio·compose·viewmodel
清霜之辰1 个月前
安卓 Compose 相对传统 View 的优势
android·内存·性能·compose
tangweiguo030519871 个月前
Android Compose Activity 页面跳转动画详解
android·compose
tangweiguo030519871 个月前
在 Jetpack Compose 中实现 iOS 风格输入框
android·compose
tangweiguo030519872 个月前
androd的XML页面 跳转 Compose Activity 卡顿问题
compose
tangweiguo030519872 个月前
iOS 风格弹框组件集 (Compose版)
compose
tangweiguo030519872 个月前
Android Material Design 3 主题配色终极指南:XML 与 Compose 全解析
compose
tangweiguo030519872 个月前
Android Compose 中获取和使用 Context 的完整指南
android·compose