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

Jetpack Compose新增mutableStateOf后权限回调触发两次的原因及解决方法

问题触发原因
  • 权限申请逻辑直接写在Composable的重组流程中,未做触发次数限制:每次DisplayPermissionDialog重组时都会重新检查权限,只要权限未授予,就会通过Handler.post再次调用launcher.launch发起权限申请。
  • 新增displayEmptyScreen后,拒绝权限时对这个MutableState赋值会触发当前Composable重组,此时权限仍为拒绝状态,就会二次发起权限申请,导致回调执行两次、日志打印两次。
  • 最初没有修改displayEmptyScreen的逻辑时,拒绝权限不会触发当前Composable重组,也就不会二次发起申请,因此日志仅打印一次。

额外说明:你当前的权限判断逻辑permissions.values.first { true }存在问题,该写法等价于直接取第一个权限的授权结果,若你申请了多个权限,判断会不准确,建议改为permissions.values.all { it }来校验所有申请的权限都已授权。

修复方案

你可以通过限制权限申请的触发次数解决问题,推荐将权限申请逻辑放到仅执行一次的LaunchedEffect中,避免每次重组都重复发起申请,修改后代码如下:

@Composable
private fun DisplayPermissionDialog(shouldDisplayQuestionDialog: MutableState<Boolean>) {
    val displayEmptyScreen = remember { mutableStateOf(false) }
    val launcher = rememberLauncherForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        // 修复权限判断逻辑:所有权限都授予才走成功分支
        if (permissions.values.all { it }) {
            log.info("Permission is granted")
            shouldDisplayQuestionDialog.value = true
        } else {
            log.info("Permission is not granted")
            displayEmptyScreen.value = true
        }
    }
    DisplayQuestionAlertDialog(shouldDisplayQuestionDialog)
    if (displayEmptyScreen.value) {
        Box {
            // 背景层
            Box(
                modifier = Modifier
                    .matchParentSize()
                    .background(Color.White)
            )
        }
    }
    val context = LocalContext.current
    // 权限检查和申请逻辑放到LaunchedEffect中,仅组件首次加载时执行一次
    LaunchedEffect(Unit) {
        when (PackageManager.PERMISSION_GRANTED) {
            ContextCompat.checkSelfPermission(
                context,
                Manifest.permission.READ_EXTERNAL_STORAGE
            ) -> {
                log.info("Set State for mutableStateFlow")
                viewModel.setState()
                DisplayQuestionAlertDialog(shouldDisplayQuestionDialog)
            }
            else -> {
                // LaunchedEffect默认在主线程执行,无需额外通过Handler.post切换
                launcher.launch(
                    arrayOf(
                        Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE
                    )
                )
            }
        }
    }
}

如果你不想用LaunchedEffect,也可以新增一个标记位限制申请次数:

// 新增标记位记录是否已经发起过权限申请
val hasRequestedPermission = remember { mutableStateOf(false) }
val context = LocalContext.current
when (PackageManager.PERMISSION_GRANTED) {
    ContextCompat.checkSelfPermission(
        context,
        Manifest.permission.READ_EXTERNAL_STORAGE
    ) -> {
        LaunchedEffect(Unit) {
            log.info("Set State for mutableStateFlow")
            viewModel.setState()
        }
        DisplayQuestionAlertDialog(shouldDisplayQuestionDialog)
    }
    else -> {
        // 仅未发起过申请时才调用launch
        if (!hasRequestedPermission.value) {
            hasRequestedPermission.value = true
            Handler(Looper.getMainLooper()).post {
                launcher.launch(
                    arrayOf(
                        Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE
                    )
                )
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 13:15:00