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),仅供参考

相关新闻

个人工作室合作脑机单词速记,租共享场地前要确认什么?

个人工作室合作脑机单词速记,租共享场地前要确认什么?

个人工作室了解脑机单词速记合作时,租共享场地前应确认能否开展拟定的学习服务、谁负责学生接待、学习舱如何保管,以及训练后在哪里检测和复习。这些条件关系到能否持续开课,应和课程采购、赠舱及培训方案一起核验。 脑机单词速记的产品价值…

2026/10/10 2:25:58 阅读更多 →
离散数学命题与谓词逻辑实战解析:从符号化到范式转换

离散数学命题与谓词逻辑实战解析:从符号化到范式转换

简介:本资源是刘玉珍编著《离散数学》教材配套的完整课后习题答案详解,面向计算机、软件工程、人工智能等专业本科生及考研学生,专为攻克命题逻辑、真值计算、范式转换、逻辑等价与推理证明等核心难点提供精准参考。答案覆盖第1章全部习题&am…

2026/10/10 2:25:58 阅读更多 →
为 AI 智能体写出优秀规范:Vibe Coding 项目中的五原则与 Spec-Driven 实战指南

为 AI 智能体写出优秀规范:Vibe Coding 项目中的五原则与 Spec-Driven 实战指南

文档教程Vibe Coding示例工程 【免费下载链接】vibe-vibe The First Systematic Vibe Coding Open-Source Tutorial | From Zero to Full-Stack, Empowering Everyone to Build Products with AI | Live at: www.vibevibe.cn ;首个系统化 Vibe Coding 开源教程 | 零…

2026/10/10 2:24:58 阅读更多 →

最新新闻

Spring Boot多环境配置实战:Profile选型、优先级与避坑指南

Spring Boot多环境配置实战:Profile选型、优先级与避坑指南

开发环境跑得好好的,一到测试环境就连不上数据库,或者本地正常的文件上传到了服务器就切成了绝对路径,这类问题我在不同团队见过太多次。根子往往不在代码逻辑,而在于Spring Boot多环境配置没有做扎实。配置这个东西,你…

2026/10/10 3:12:12 阅读更多 →
Meta也买Claude?大模型多模型路由与成本控制实战

Meta也买Claude?大模型多模型路由与成本控制实战

看到这个题目,第一反应可能是“不理解”。Meta 是 Llama 系列开源模型背后的公司,长期强调自研和开源路线,为什么要反过来向 Anthropic 购买 AI 服务?Anthropic 的 Claude 系列是闭源模型,两家在商业上还是竞争对手。这…

2026/10/10 3:12:12 阅读更多 →
AI产物治理:从生成到管理的工程化之路

AI产物治理:从生成到管理的工程化之路

过去半年,我发现很多团队对 AI 编程的抱怨正在悄悄变化:模型生成得“准不准”已经不是首要矛盾,“产出物管不管得住”变成了新的瓶颈。代码从一个对话框里来,技术方案从另一个对话框里来,中间还有 AI 画的原型图、整理…

2026/10/10 3:12:12 阅读更多 →
RabbitMQ死信队列实战:原理、配置与避坑指南

RabbitMQ死信队列实战:原理、配置与避坑指南

RabbitMQ 里的死信队列,很多人一开始都理解成“把消息扔进一个叫死信队列的地方”,但真正配置下来才发现,它其实是“过一遍死信交换机,再决定去哪儿”。我最开始接触这个功能是在做订单超时未支付关单的需求时,主队列里…

2026/10/10 3:12:12 阅读更多 →
在华为 MetaERP 语境下,“采购模块本体论建模”不是画 ER 图,而是把 PTP(Procure-to-Pay 采购到付款)​ 做成一套机器可读的业务语义底座:TBox(本体层):定义类

在华为 MetaERP 语境下,“采购模块本体论建模”不是画 ER 图,而是把 PTP(Procure-to-Pay 采购到付款)​ 做成一套机器可读的业务语义底座:TBox(本体层):定义类

在华为 MetaERP 语境下,“采购模块本体论建模”不是画 ER 图,而是把 PTP(Procure-to-Pay 采购到付款)​ 做成一套机器可读的业务语义底座:TBox(本体层):定义类、关系、公理规则&…

2026/10/10 3:12:12 阅读更多 →
多路IO复用详解:select、poll与epoll实战对比

多路IO复用详解:select、poll与epoll实战对比

1. 写在前面:从“头疼的并发”说起刚开始写网络程序那会儿,最烦的就是处理多个连接。早期我做了一个小的聊天室Demo,服务端逻辑很简单:accept 一个客户端,然后 recv 数据、再 send 回去。单连接跑起来一切正常&#xf…

2026/10/10 3:11:12 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →