导航与权限:在导航流程中处理运行时权限
权限处理,说白了就是应用和用户之间的「信任博弈」。你想想看,用户刚打开 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 中实践过这套方案。当时有个功能需要读取联系人,我们是这样设计的:
- 用户点击「邀请好友」按钮
- 导航到 PermissionFragment,检查联系人权限
- 如果权限未授予,展示一个友好的说明页面,告诉用户「邀请好友需要读取联系人,我们不会上传你的隐私数据」
- 用户同意后,发起权限请求
- 权限获取成功,自动跳转到联系人选择页面
- 权限被拒绝,跳转到「手动输入手机号」的降级页面
个人经验:降级方案很重要。不是所有用户都愿意授予权限,给他们一个替代方案,用户体验会好很多。我那个 App 因为加了手动输入功能,权限拒绝率从 60% 降到了 30%。
总结一下关键点
| 场景 | 处理方式 | 导航策略 |
|---|---|---|
| 权限已授予 | 直接放行 | 正常导航到目标页面 |
| 首次请求权限 | 展示解释说明 | 导航到 PermissionFragment |
| 用户拒绝 | 提供降级方案 | 导航到降级页面 |
| 永久拒绝 | 引导到系统设置 | 展示设置引导页面 |
说白了,权限处理的核心不是技术实现,而是用户体验设计。Navigation 组件给了我们一个很好的框架,让我们可以把权限请求自然地融入到导航流程中,而不是生硬地打断用户的操作。
记住一点:权限请求是导航流程的一部分,不是例外情况。把这个思路理清了,代码写起来就顺了。