shouldShowRequestPermissionRationale — 是一种Android API方法,它向开发者提示是否需要在请求危险权限之前向用户显示解释。根据Android Developer Reference, 2024,rational方法在用户之前拒绝过请求但未设置Never Ask Again标志时返回true。这是在使用运行时权限时构建友好UX的关键工具。
要点
shouldShowRequestPermissionRationale — 是Android中Activity和Fragment类的方法,通过ActivityCompat可访问以实现兼容性。它接收权限名称并返回Boolean,指示是否需要在重复请求之前向用户显示额外解释。该方法出现在Android 6.0 Marshmallow中,与运行时权限模型一起引入。
Rationale机制建立在跟踪用户与权限对话框交互历史的基础上。系统记住用户之前是否拒绝了请求。如果拒绝发生在未设置Never Ask Again标志的情况下,shouldShowRequestPermissionRationale返回true。这是给开发者的信号:用户不明白为什么需要该权限,需要额外解释。根据Google Material Design指南,首次拒绝后显示rationale对话框,将权限重新授予的可能性提高35个百分点。
理解返回值的语义很重要:true意味着显示对话框有意义,false — 对话框要么不需要(权限已授予或从未被请求),要么无用(Never Ask Again处于活动状态)。该方法不保证对话框一定会显示 — 它只是给出建议。开发者自己决定显示什么UI作为响应。
shouldShowRequestPermissionRationale在API Level 23中与运行时权限方法组一起引入。在Android 6.0之前,所有权限都在安装时请求,不需要解释机制 — 用户一次性接受或拒绝整个列表。运行时模型使得用户在不理解上下文的情况下拒绝请求成为可能,而这正是rationale需要的原因。
该方法的逻辑如下。首次为特定权限调用requestPermissions时,shouldShowRequestPermissionRationale返回false — 用户尚未遇到对话框。如果用户拒绝了请求(按下了Deny),该方法开始返回true。在带有Never Ask Again标志的重复拒绝后,该方法返回false。
完整状态表:
| 状态 | shouldShowRationale | checkSelfPermission | 开发者操作 |
|---|---|---|---|
| 未请求过 | false | DENIED | 显示系统对话框 |
| 已授予 | false | GRANTED | 执行功能 |
| 首次被拒 | true | DENIED | 显示rationale,然后系统对话框 |
| Never Ask Again | false | DENIED | 引导至设置 |
shouldShowRequestPermissionRationale = false和checkSelfPermission = DENIED的组合 — 是最难处理的情况。这意味着要么权限从未被请求,要么已设置Never Ask Again。开发者需要区分这两种状态。唯一的方法 — 在SharedPreferences中存储firstRequest标志或使用SavedStateHandle。在第一次请求时设置标志,如果shouldShowRationale返回false且标志已为true — 这意味着Never Ask Again。
如果用户删除并重新安装应用程序、清除应用程序数据或重置权限设置,shouldShowRequestPermissionRationale会重置。重新安装后,该方法将再次为第一个请求返回false。系统更新和Android版本更改不会重置历史记录 — 它存储在应用程序数据中。
正确实现rationale包括三个组件:拒绝后检查shouldShowRequestPermissionRationale、显示带有解释的自定义对话框以及在用户肯定回答后重新调用requestPermissions。对话框应简洁、具体,并解释为什么应用程序需要这个特定权限。
private fun requestLocationWithRationale() {
val permission = Manifest.permission.ACCESS_FINE_LOCATION
when {
ContextCompat.checkSelfPermission(
this, permission
) == PackageManager.PERMISSION_GRANTED -> {
startLocationTracking()
}
ActivityCompat.shouldShowRequestPermissionRationale(
this, permission
) -> {
showRationaleDialog(permission)
}
else -> {
requestPermissionLauncher.launch(permission)
}
}
}
private fun showRationaleDialog(
permission: String
) {
AlertDialog.Builder(this)
.setTitle("为什么需要地理位置访问权限")
.setMessage(
"应用程序使用地理位置来标记地图上的位置" +
"。没有此权限" +
"该功能将无法运行。"
)
.setPositiveButton("允许") { _, _ ->
requestPermissionLauncher.launch(permission)
}
.setNegativeButton("取消", null)
.show()
}
Material Design的最佳实践建议使用bottom sheet或inline banner而不是模态对话框来显示rationale。Bottom sheet不那么打扰,并给用户提供上下文。屏幕上的内联元素(例如,带有解释和允许按钮的卡片)显示该功能在没有权限的情况下不可用,但不会阻止其余界面。
Rationale的文本应本地化并针对特定功能进行调整。不要使用像“这是应用程序运行所必需的”这样的通用短语。具体说明:“用于显示您附近的天气”或“用于将照片保存到相册”。根据Google UX Research,具体的解释将权限授予的可能性提高了50个百分点。
shouldShowRequestPermissionRationale = true(首次拒绝)和DENIED时false(Never Ask Again)之间的区别 — 是权限处理中的关键点。在第一种情况下,用户犹豫了,额外的解释可以说服他授予访问权限。在第二种情况下 — 用户做出了最终决定,重复的系统对话框只会引起烦恼。
拒绝后的处理算法应如下:
重要的是不要混淆顺序:首先检查shouldShowRequestPermissionRationale,而不是checkSelfPermission。checkSelfPermission在两种情况下都会返回DENIED。只有shouldShowRequestPermissionRationale能够区分首次拒绝和Never Ask Again。使用SavedStateHandle或SharedPreferences存储“已发送第一次请求”标志 — 这是区分“未请求”和“已阻止”的唯一可靠方法。
只显示一次rationale。如果用户在rationale后再次拒绝了请求 — 不再显示解释。直接转向打开设置的建议。重复显示rationale被视为打扰,并会降低应用程序评分。最佳场景:请求 — 拒绝 — rationale — 重复请求 — 拒绝 — 设置。
在第一次请求之前不要显示rationale。一些开发者错误地在第一个对话框之前显示解释,理由是“用户应该理解”。这会恶化用户体验:用户连续看到两个对话框而不是一个。Google建议立即显示系统对话框,而rationale只在拒绝后显示。
使用与功能真正需要时相关联的上下文rationale。不要在应用程序启动时请求所有权限 — 这是最低的授予率。在用户按下“拍照”按钮时请求CAMERA,在他打开地图时请求LOCATION。上下文请求与rationale结合,可将授予率提高到80个百分点,而初始请求只有30个百分点。
测试shouldShowRequestPermissionRationale需要检查表中的四种状态:未请求、已授予、已拒绝、Never Ask Again。在单元测试中,使用具有可配置shouldShowRationale行为的FakePermissionHandler。在仪器测试中 — 使用UiAutomator或Espresso模拟对系统对话框的响应。
class RationaleViewModelTest {
private val handler = FakePermissionHandler()
private val viewModel = PermissionsViewModel(handler)
fun testFirstDenial_shouldShowRationale() {
handler.shouldShowRationale = true
handler.cameraResult =
PermissionResult.DENIED(true)
viewModel.onCameraRequested()
assertEquals(
PermissionUiState.Denied(true),
viewModel.uiState.value
)
}
fun testNeverAskAgain_redirectToSettings() {
handler.shouldShowRationale = false
handler.cameraResult =
PermissionResult.DENIED(false)
viewModel.onCameraRequested()
assertEquals(
PermissionUiState.RedirectToSettings,
viewModel.uiState.value
)
}
}
仪器测试的关键场景 — 检查rationale对话框在首次拒绝后是否确实显示。使用带有idling resources的Espresso等待系统对话框,然后按Deny,检查带有解释文本的自定义对话框出现,然后按Allow — 检查授予。UIAutomator允许通过按钮文本与系统对话框交互,这使测试更稳定。
还应该测试在rationale对话框内拒绝的场景。如果用户在自定义解释中按了Deny,shouldShowRequestPermissionRationale应再次返回true,因为Never Ask Again尚未激活。最佳实践 — 在连续两次拒绝后直接引导至设置,以免重复解释打扰用户并降低应用程序评分。
常见问题
true — 如果请求之前被拒绝且未设置Never Ask Again。false — 如果权限未被请求、已授予或永久阻止。false + DENIED组合需要通过额外标志进行检查。
仅在用户首次拒绝后且shouldShowRequestPermissionRationale返回true时显示rationale。在第一次请求之前不需要rationale — 这会使UX恶化并创建不必要的对话框。
在SharedPreferences或SavedStateHandle中存储isFirstRequest标志。如果shouldShowRationale = false、checkSelfPermission = DENIED且标志 = true — 表示Never Ask Again已激活。如果标志 = false — 这是第一次请求。
显示带有“打开设置”按钮的对话框,将用户引导至ACTION_APPLICATION_DETAILS_SETTINGS。不要再次调用requestPermissions — 对话框不会出现,结果会以DENIED形式返回且没有消息。
在单元测试中,使用具有可配置shouldShowRationale字段的FakePermissionHandler。在仪器测试中 — 使用Espresso或UIAutomator模拟系统对话框。检查表中的所有4种状态。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。