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()
}
最佳实践
- 按需请求:只在用户尝试使用相关功能时请求权限
- 解释清楚:对敏感权限提供清晰的解释 (rationale)
- 优雅降级:当权限被拒绝时提供替代方案
- 测试所有路径:测试授予、拒绝和永久拒绝的情况
- 组合使用:将权限状态与业务逻辑分离
- 及时更新:关注 Android 新版本的权限变更
通过以上方法,你可以在 Jetpack Compose 应用中高效、用户友好地处理运行时权限申请。