导航与权限:在导航流程中处理运行时权限

权限处理,说白了就是应用和用户之间的「信任博弈」。你想想看,用户刚打开 App,突然弹个对话框要读取联系人,换谁都得犹豫一下。我在项目中遇到过好几次,因为权限处理不当,用户直接就把 App 给卸载了。

在单 Activity 架构下,权限请求和页面导航的协调,其实是个挺有意思的挑战。Navigation 组件本身不直接处理权限,但我们可以通过一些巧妙的方式,把这两者无缝衔接起来。

权限请求的「正确姿势」

我个人习惯把权限请求封装成一个独立的 Fragment。为什么这么做?因为权限请求的 UI 逻辑和业务页面是解耦的。你想想看,如果每个需要权限的页面都自己处理请求,代码会变得非常冗余。

class PermissionFragment : Fragment() {
    private val args: PermissionFragmentArgs by navArgs()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 检查权限状态
        when {
            ContextCompat.checkSelfPermission(
                requireContext(), 
                args.permission
            ) == PackageManager.PERMISSION_GRANTED -> {
                // 权限已授予,直接跳转
                findNavController().navigate(
                    PermissionFragmentDirections.actionToTarget(args.targetDestination)
                )
            }
            shouldShowRequestPermissionRationale(args.permission) -> {
                // 需要解释为什么需要这个权限
                showRationaleDialog()
            }
            else -> {
                // 直接请求权限
                requestPermissions(
                    arrayOf(args.permission),
                    PERMISSION_REQUEST_CODE
                )
            }
        }
    }
}

小技巧:把目标页面作为参数传给 PermissionFragment,这样它就能在权限获取后自动导航到正确的位置。我在项目中用这种方式,减少了至少 30% 的样板代码。

导航流程中的权限协调

权限请求和页面跳转的协调,核心在于「状态管理」。我见过很多开发者直接在 Activity 的 onRequestPermissionsResult 里处理导航,结果代码变得一团糟。

更好的做法是:把权限状态作为导航图的一部分。来看一个实际的例子:

// 在 Navigation 图中定义权限检查点
<fragment
    android:id="@+id/permissionCheckFragment"
    android:name=".PermissionFragment">
    <argument
        android:name="permission"
        app:argType="string" />
    <argument
        android:name="targetDestination"
        app:argType="integer" />
    <action
        android:id="@+id/actionToTarget"
        app:destination="@id/targetFragment" />
</fragment>

这样做的好处很明显:权限检查变成了导航图中的一个节点。用户从 A 页面到 B 页面,如果中间需要权限,系统会自动插入权限检查流程。

核心思路:权限请求不是「打断」导航流程,而是「嵌入」导航流程。这样用户的操作路径是连续的,体验会好很多。

权限拒绝后的处理策略

嗯,这里要注意。用户拒绝权限是常态,不是异常。我曾经犯过一个错误:用户拒绝权限后直接弹 Toast 说「权限被拒绝,无法使用」,结果被用户骂惨了。

正确的做法是分情况处理:

  • 首次拒绝:展示友好的解释说明,让用户理解为什么需要这个权限
  • 再次拒绝并勾选「不再询问」:引导用户去系统设置中手动开启
  • 永久拒绝:提供降级方案,而不是直接崩溃或卡死
override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<String>,
    grantResults: IntArray
) {
    when {
        grantResults[0] == PackageManager.PERMISSION_GRANTED -> {
            // 权限已授予,继续导航
            findNavController().navigate(
                PermissionFragmentDirections.actionToTarget(args.targetDestination)
            )
        }
        !shouldShowRequestPermissionRationale(permissions[0]) -> {
            // 用户选择了「不再询问」
            showSettingsNavigationDialog()
        }
        else -> {
            // 用户拒绝了但还可以再次请求
            showRetryDialog()
        }
    }
}

避坑指南:千万不要在权限被拒绝后直接 finish() 当前页面。我曾经这么干过,结果用户反馈说「App 闪退」。实际上是我们自己把页面关了,但用户以为出 Bug 了。

权限状态与导航状态的同步

单 Activity 架构下,权限状态的变化可能会影响多个页面的导航逻辑。我建议使用一个统一的权限状态管理器:

class PermissionStateManager {
    private val _permissionState = MutableStateFlow<Map<String, Boolean>>(emptyMap())
    val permissionState: StateFlow<Map<String, Boolean>> = _permissionState.asStateFlow()
    
    fun updatePermission(permission: String, granted: Boolean) {
        _permissionState.update { current ->
            current + (permission to granted)
        }
    }
    
    fun isPermissionGranted(permission: String): Boolean {
        return _permissionState.value[permission] ?: false
    }
}

这样,任何页面都可以通过观察这个 StateFlow 来感知权限状态的变化,并做出相应的导航决策。

SVG 流程图:权限导航协调流程

权限导航协调流程 用户点击需要权限的功能 检查权限状态 权限是否 已授予? 直接导航到目标页面 是否需要 解释? 展示 解释 请求权限 处理请求结果 拒绝处理 降级/引导

实际项目中的最佳实践

我在一个社交类 App 中实践过这套方案。当时有个功能需要读取联系人,我们是这样设计的:

  1. 用户点击「邀请好友」按钮
  2. 导航到 PermissionFragment,检查联系人权限
  3. 如果权限未授予,展示一个友好的说明页面,告诉用户「邀请好友需要读取联系人,我们不会上传你的隐私数据」
  4. 用户同意后,发起权限请求
  5. 权限获取成功,自动跳转到联系人选择页面
  6. 权限被拒绝,跳转到「手动输入手机号」的降级页面

个人经验:降级方案很重要。不是所有用户都愿意授予权限,给他们一个替代方案,用户体验会好很多。我那个 App 因为加了手动输入功能,权限拒绝率从 60% 降到了 30%。

总结一下关键点

场景 处理方式 导航策略
权限已授予 直接放行 正常导航到目标页面
首次请求权限 展示解释说明 导航到 PermissionFragment
用户拒绝 提供降级方案 导航到降级页面
永久拒绝 引导到系统设置 展示设置引导页面

说白了,权限处理的核心不是技术实现,而是用户体验设计。Navigation 组件给了我们一个很好的框架,让我们可以把权限请求自然地融入到导航流程中,而不是生硬地打断用户的操作。

记住一点:权限请求是导航流程的一部分,不是例外情况。把这个思路理清了,代码写起来就顺了。

公众号:蓝海资料掘金营,微信deep3321