Android M 运行时权限被拒绝处理指南:shouldShowRequestPermissionRationale 的语义与实战 文档教程知识库【免费下载链接】android-tech-frontier【停止维护】一个定期翻译国外Android优质的技术、开源库、软件架构设计、测试等文章的开源项目项目地址https://gitcode.com/gh_mirrors/an/android-tech-frontier点击查看免费下载本文以 在Android M中权限被拒绝时该如何处理 为核心结合仓库中 权限 - 第一篇、权限 - 第二篇、权限 - 第三篇、权限 - 第四篇 系列译文展开讲解 Android MAndroid 6.0运行时权限模型下如何正确判断用户拒绝权限的意图、何时该展示解释理由以及如何处理不再提示导致的永久拒绝场景。导读从 Android MAPI 23开始危险权限不再于安装时一次性授予而需要在运行时由用户逐个确认。开发者必须面对权限被拒绝这一常态是用户第一次拒绝、还是勾选了不再提示永久拒绝Android 官方为此在 SDK 中提供了shouldShowRequestPermissionRationale()方法本文围绕该方法的返回值语义、三种拒绝场景的判断逻辑、Fragment 中的已知 Bug 与绕行方案并结合仓库内权限系列译文给出完整的可落地代码实践帮助你构建稳定、不崩溃且对用户友好的运行时权限处理流程。一、背景Android M 为何要引入运行时权限在 Android M 之前API 1.0 起权限的声明方式非常简单——在AndroidManifest.xml中使用uses-permission声明即可用户在安装时统一授权。Android M 改变了这一模型正常权限Normal Permission如MODIFY_AUDIO_SETTINGS对用户隐私基本没有风险安装时由系统自动授权危险权限Dangerous Permission如RECORD_AUDIO、CAMERA、定位等可能影响用户安全或隐私必须在运行时由用户明确授权。这里有一个关键前提只有将targetSdkVersion设置为23 或更高应用才会触发运行时权限请求机制。正如 权限 - 第一篇 所强调的当时已有不少开发者无意中将targetSdkVersion升到最新版本却未实现运行时请求权限的代码导致应用正常运行中直接崩溃。因此处理权限被拒绝是升级到 API 23 后绕不开的必修课。二、shouldShowRequestPermissionRationale()判定是否该解释的核心方法Android M Preview 2 的 SDK 引入了Activity.shouldShowRequestPermissionRationale()方法用于告知 App在调用需要权限的功能之前是否应该向用户展示为什么需要该权限的理由。该方法的返回值取决于权限当前的历史状态具体语义如下场景返回值含义与建议做法App 刚安装尚未请求过该权限false可以直接调用需要权限的功能系统会正常弹出权限对话框无需额外解释用户之前拒绝过该权限但未勾选不再提示true应当在再次调用权限功能前展示解释理由仅当此前未说明过原因时需要用户永久拒绝点击了不再提示false后续任何请求都会被系统自动拒绝此时也不必再提供解释简而言之返回值true是需要向用户解释的明确信号而返回值false则可能对应两种截然不同的状态——从没问过或永远不会再给。这一点决定了我们无法仅凭返回值区分首次安装与永久拒绝需要在业务层自行记录请求历史详见下文第五节。三、三种场景的完整处理流程可复用的代码模式要将shouldShowRequestPermissionRationale()用好必须把它嵌入标准的检查 → 请求 → 回调流程中。下面这套代码模式综合了 权限 - 第一篇 的权限检查封装与 权限 - 第三篇 的请求回调逻辑并在其中补入理由展示环节。3.1 第一步检查权限是否已被授予在 API 23 的Context中可使用checkSelfPermission()检测授权状态但为了兼容 M 之前的系统更稳妥的做法是使用ContextCompat.checkSelfPermission()——在 Marshmallow 之前的设备上该调用总是返回PERMISSION_GRANTED旧系统在 Manifest 声明后即默认授予因此同一份代码在所有 OS 层级都能工作无需手写 API 级别判断// PermissionsChecker.java —— 参考 issue-40 权限系列第一篇 class PermissionsChecker { private final Context context; public PermissionsChecker(Context context) { this.context context; } // 只要有一个权限缺失即返回 true public boolean lacksPermissions(String... permissions) { for (String permission : permissions) { if (lacksPermission(permission)) { return true; } } return false; } private boolean lacksPermission(String permission) { return ContextCompat.checkSelfPermission(context, permission) PackageManager.PERMISSION_DENIED; } }把检测逻辑独立成类是为了让 App 中所有 Activity 复用避免重复代码、提升可维护性。3.2 第二步请求权限并接收回调requestPermissions()的运作方式与startActivityForResult()类似系统弹出授权对话框用户在对话框中的选择最终通过onRequestPermissionsResult()回调返回// PermissionsActivity.java —— 参考 issue-40 权限系列第三篇 public class PermissionsActivity extends AppCompatActivity { private static final int PERMISSION_REQUEST_CODE 0; private PermissionsChecker checker; private boolean requiresCheck; Override protected void onResume() { super.onResume(); if (requiresCheck) { String[] permissions getIntent().getStringArrayExtra(EXTRA_PERMISSIONS); if (checker.lacksPermissions(permissions)) { // 调用系统的运行时权限请求 ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE); } else { allPermissionsGranted(); } } else { requiresCheck true; } } Override public void onRequestPermissionsResult(int requestCode, NonNull String[] permissions, NonNull int[] grantResults) { if (requestCode PERMISSION_REQUEST_CODE hasAllPermissionsGranted(grantResults)) { requiresCheck true; allPermissionsGranted(); } else { requiresCheck false; // 权限被拒绝进入解释或引导分支详见下一节 handlePermissionDenied(); } } private boolean hasAllPermissionsGranted(NonNull int[] grantResults) { for (int grantResult : grantResults) { if (grantResult PackageManager.PERMISSION_DENIED) { return false; } } return true; } }3.3 第三步在回调中利用shouldShowRequestPermissionRationale()分流用户拒绝权限后onRequestPermissionsResult()收到的grantResults并不能告诉你用户是第一次拒绝还是勾选了不再提示。此时就轮到shouldShowRequestPermissionRationale()登场private void handlePermissionDenied() { // 从 Intent 中取出本次请求的权限列表 String[] permissions getPendingPermissions(); // 只要仍有权限允许再次询问就应当展示解释理由 boolean shouldExplain false; for (String permission : permissions) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) { shouldExplain true; break; } } if (shouldExplain) { // 场景 A用户拒绝过但尚未勾选不再提示 // 展示为什么需要该权限的理由然后重新请求 showRationaleDialog(permissions); } else { // 场景 B全部权限均被永久拒绝勾选了不再提示 // 返回 false继续请求会被自动拒绝应引导用户前往系统设置页 openAppSettings(); } }说明在旧系统或支持库场景中也可使用ActivityCompat.shouldShowRequestPermissionRationale(Activity, String)对应 support-v4 中的实现它会在低版本系统上返回合适的默认值使同一套逻辑保持兼容。四、不再提示与永久拒绝false背后的陷阱当用户勾选了不再提示部分文档中亦称不再询问并再次拒绝后shouldShowRequestPermissionRationale()会返回false——此时系统对后续权限请求一律自动拒绝任何再次requestPermissions()的尝试都注定失败。正如 权限 - 第三篇 所指出开发者无法得知用户是否勾选了不再提示因此要按最坏情况做预案。对于 App 运行所必需的核心权限如果被永久拒绝正确做法是弹窗明确告知用户原因并提供前往设置的入口指引用户在系统设置页手动授权// 引导用户前往系统应用详情页手动授权 private void startAppSettings() { Intent intent new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse(package: getPackageName())); startActivity(intent); }同时注意从用户的视角看还存在另一条改变授权状态的路径用户随时可以进入系统设置的应用详情页手动撤销某个权限。因此不应只在启动时检查一次权限而应在 Activity 恢复时onResume()重新检测——权限 - 第二篇 正是这样做的用户在设置页撤销权限后返回 ApponResume()检测到权限缺失即可立即转入请求流程防止后续调用权限功能时崩溃。五、Fragment 中的已知 Bug 与绕行方案本文主题方法在 Android M Preview 2 的 SDK 中存在一个已知 BugFragment.shouldShowRequestPermissionRationale()会一直返回false。也就是说在 Fragment 中直接调用该方法即使权限曾被用户拒绝过也拿不到true导致解释理由分支永远无法触发。该问题在后续版本中修复但在 Preview 2 阶段官方给出的绕行方案是在 Fragment 中调用宿主 Activity 的方法// Fragment 中绕行 Bug 的正确写法 getActivity().shouldShowRequestPermissionRationale(permission);结合上一节的完整流程当权限处理逻辑写在 Fragment 内时onRequestPermissionsResult()中判断是否需要解释也应统一走getActivity()的路径确保判断结果与系统真实状态一致。六、最佳实践什么时候才该解释与请求shouldShowRequestPermissionRationale()提供了是否该解释的判断能力但用好它还需要配合正确的权限请求策略。综合 权限 - 第四篇 的总结以下几点值得在工程中落实把权限分为核心必需与非必需两类。核心权限如相机 App 的CAMERA缺失时 App 无法工作可启动即请求、拒绝后引导设置页非必需权限如地理标签所需的定位权限应在功能真正被触发的时机请求而非启动时一股脑弹窗。避免启动时轰炸式请求。用户对 App 的第一印象若是一连串权限弹窗很可能会直接卸载。仅在需要时请求既帮助用户理解每个权限的用途提高授予率也让用户更快体验 App 价值。只请求真正需要的权限。随着业务演进曾经的必需权限可能已失去用途应定期审查 Manifest 并清理多余的uses-permission声明。警惕清除数据会重置权限。用户清除应用数据时不仅清空业务数据还会重置所有权限状态——此前已授权的权限全部回到未授予状态shouldShowRequestPermissionRationale()的判断历史也随之归零。这既是测试权限流程的便捷手段也是客服应对为什么已同意还要再问问题的标准答案。结合业务记录做兜底。由于false无法区分从未请求与永久拒绝建议自行持久化记录是否已请求过某权限。若返回false且历史记录显示已请求过即可判定为永久拒绝直接走设置页引导分支避免让用户反复看到无意义的请求弹窗。七、总结处理 Android M 运行时权限被拒绝核心在于准确识别用户的拒绝意图。shouldShowRequestPermissionRationale()提供了三态语义首次安装返回false直接请求即可、用户拒绝后返回true应展示理由、永久拒绝后返回false应引导设置页。配合ContextCompat.checkSelfPermission()检查、requestPermissions()请求与onRequestPermissionsResult()回调即可搭建一套完整的权限生命周期管理。同时不要忘记两个关键细节M Preview 2 中Fragment.shouldShowRequestPermissionRationale()的 Bug 需通过getActivity()绕行权限状态可能随时被系统设置或清除数据改变因此要在onResume()中反复校验而不是只在启动时检查一次。需要进一步深入时可继续阅读仓库内的 权限 - 第一篇 至 权限 - 第四篇 完整系列其中包含从权限声明、检查封装到请求回调、设置页引导的逐步演进代码。赞分享文档教程知识库【免费下载链接】android-tech-frontier【停止维护】一个定期翻译国外Android优质的技术、开源库、软件架构设计、测试等文章的开源项目项目地址https://gitcode.com/gh_mirrors/an/android-tech-frontier点击查看免费下载相关推荐发明专利点挖掘与融合基于 patent-disclosure-skill 的 Step 3–4 实战指南发明专利点挖掘与融合基于 patent disclosure skill 的 Step 3–4 实战指南 本文聚焦 patent disclosure ski文档教程知识库Dexter权限拒绝处理永久拒绝与临时拒绝的差异指南Dexter权限拒绝处理永久拒绝与临时拒绝的差异指南 在Android应用开发中权限管理是一个至关重要的环节。Dexter作为一款优秀的Android权限请移动开发认证鉴权XXPermissions进阶教程处理权限被永久拒绝场景XXPermissions进阶教程处理权限被永久拒绝场景 在Android应用开发中权限请求是保障应用功能正常运行的关键环节。当用户勾选不再询问并拒绝权移动开发认证鉴权上一篇深入 VS Code Product Icon ReferenceCodicon 图标标识体系与 vscode-copilot-chat 的 CodeMapper 大文档改写验证下一篇终极指南3步免费解锁WeMod Pro全部功能告别2小时限制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考