You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose永久拒绝权限后重启页面状态异常及版本适配问题

解决Jetpack Compose中蓝牙权限的场景化处理问题(Accompanist 0.25.1)

针对你提到的四个权限场景,结合Accompanist Permissions 0.25.1的API变化,核心是通过权限的shouldShowRationale状态+本地存储标记来区分首次请求和永久拒绝的情况,原permissionRequested属性已被移除,需用此方案替代。

核心逻辑梳理

  • 首次进入:权限未授予 + 本地无请求记录 → 显示申请按钮
  • 用户允许:权限已授予 → 显示成功文本
  • 永久拒绝:权限未授予 + shouldShowRationale返回false + 本地有请求记录 → 引导至设置
  • 重启后:读取本地记录,结合shouldShowRationale状态,直接触发引导设置

实现步骤

1. 依赖与权限声明

确保在build.gradle中引入Accompanist Permissions:

implementation "com.google.accompanist:accompanist-permissions:0.25.1"

在AndroidManifest.xml中声明蓝牙权限(根据目标API级别调整):

<!-- Android 12+ -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- 低版本蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />

2. 本地存储记录请求状态

用Jetpack DataStore存储权限是否已被请求过(适配Compose的异步存储方案):

// 定义DataStore实例
val Context.permissionRequestStore by preferencesDataStore(name = "permission_requests")

object PermissionKeys {
    val BLUETOOTH_REQUESTED = booleanPreferencesKey("bluetooth_requested")
}

// 封装标记权限已请求的方法
suspend fun Context.markBluetoothPermissionRequested() {
    permissionRequestStore.edit { settings ->
        settings[PermissionKeys.BLUETOOTH_REQUESTED] = true
    }
}

// 封装读取权限请求状态的方法
suspend fun Context.isBluetoothPermissionRequested(): Boolean {
    return permissionRequestStore.data.first()[PermissionKeys.BLUETOOTH_REQUESTED] ?: false
}

3. 权限状态管理与UI逻辑

在Compose页面中,结合rememberMultiplePermissionsState和DataStore实现场景判断:

@Composable
fun BluetoothPermissionScreen() {
    // 根据API级别定义蓝牙权限集合
    val bluetoothPermissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        listOf(Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT)
    } else {
        listOf(Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN)
    }

    val permissionState = rememberMultiplePermissionsState(bluetoothPermissions)
    val context = LocalContext.current

    // 读取本地权限请求标记
    val isRequested = remember { mutableStateOf(false) }
    LaunchedEffect(Unit) {
        isRequested.value = context.isBluetoothPermissionRequested()
    }

    PermissionsRequired(
        multiplePermissionsState = permissionState,
        permissionsGrantedContent = {
            // 场景2:权限已授予
            Text(text = "权限已授予")
        },
        permissionsNotGrantedContent = {
            when {
                !isRequested.value -> {
                    // 场景1:首次进入
                    Column(horizontalAlignment = Alignment.CenterHorizontally) {
                        Text(text = "Abc需要权限")
                        Button(onClick = {
                            permissionState.launchMultiplePermissionRequest()
                            // 请求后标记为已请求
                            LaunchedEffect(Unit) {
                                context.markBluetoothPermissionRequested()
                            }
                        }) {
                            Text("申请权限")
                        }
                    }
                }
                else -> {
                    // 场景3:两次拒绝后引导设置
                    ShowPermissionSettingGuide(context)
                }
            }
        },
        permissionsNotAvailableContent = {
            // 场景4:重启后永久拒绝,直接显示引导
            ShowPermissionSettingGuide(context)
        }
    )
}

// 抽离引导设置的UI组件
@Composable
private fun ShowPermissionSettingGuide(context: Context) {
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = "请手动前往设置开启权限")
        Button(onClick = {
            val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
                data = Uri.fromParts("package", context.packageName, null)
            }
            context.startActivity(intent)
        }) {
            Text("前往设置")
        }
    }
}

关键说明

  • shouldShowRationale:用户拒绝权限并勾选"不再询问"后返回false,首次请求时也返回false,必须结合本地标记区分两种场景。
  • 本地存储的作用:记录权限是否已被用户处理过,确保重启App后能正确识别永久拒绝状态。
  • permissionsNotAvailableContent:Accompanist提供的专门处理权限永久不可用的回调,和本地标记逻辑配合可覆盖重启后的场景。

内容的提问来源于stack exchange,提问作者Compose Learner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 00:20:17