- 移动开发
- 认证鉴权
【免费下载链接】easypermissions
Simplify Android M system permissions
本篇技术指南以开源仓库 easypermissions 的 README 为核心骨架,系统讲解如何在 Android M(API 23)及更高版本上,通过pub.devrel:easypermissions库大幅简化运行时权限的检查、请求、Rationale 弹窗与"永久拒绝后引导去设置页"等繁琐逻辑。读完本文,你将掌握从依赖引入、结果转发、@AfterPermissionGranted注解、PermissionCallbacks/RationaleCallbacks回调到AppSettingsDialog的完整实战方案,并理解其底层PermissionHelper调用链与低版本兼容机制。
一、为什么需要 EasyPermissions:Android M 运行时权限的痛点
从 Android 6.0(API 23,代号 Marshmallow)开始,系统引入了运行时权限模型:高危权限(如相机、定位、通讯录、短信等)不再只在安装时一次性授权,而是需要在应用运行过程中随时动态请求,并处理用户的"允许 / 拒绝 / 不再询问"三种结果。开发者必须自己维护一整套样板代码:检查权限、判断是否需要展示 rationale(解释为何需要该权限)、发起请求、在onRequestPermissionsResult中分发结果、处理"永久拒绝"后的设置页引导……
EasyPermissions 正是为解决这套重复逻辑而生的封装库(wrapper library),定位与源码注释一致:"Utility to request and check System permissions for apps targeting Android M (API >= 23)"(见 EasyPermissions.java)。它的核心价值可以概括为:
- 用
hasPermissions(...)一行完成任意数量权限的批量检查; - 用
requestPermissions(...)自动判断"是否需要先展示 rationale 弹窗",需要时自动弹出; - 用
@AfterPermissionGranted注解 + 唯一 requestCode,实现"权限全部授予后自动继续执行原任务"; - 用
somePermissionPermanentlyDenied(...)+AppSettingsDialog一键把用户引导到系统设置页。
补充说明:README 指出,如果应用使用 Kotlin 编写,可关注基于本库封装的 easypermissions-ktx 扩展库(提供 Kotlin 扩展函数),本仓库核心仍为 Java 实现。
二、安装与依赖配置
在app/build.gradle的dependencies块中加入以下依赖即可(这是 README 给出的官方配置):
dependencies { // For developers using AndroidX in their applications implementation 'pub.devrel:easypermissions:3.0.0' // For developers using the Android Support Library implementation 'pub.devrel:easypermissions:2.0.1' }需要注意两点:
- 按工程使用的 UI 体系选择版本:如果应用已迁移到 AndroidX,使用
3.0.0;如果仍在使用旧版 Android Support Library,则使用2.0.1。两者 API 形态一致,但底层分别依赖androidx.appcompat与android.support组件。 - 运行前提:本文描述的运行时权限 API 面向 targetSdk 为 Android M(API 23)及以上的应用;库内部会对低于 23 的系统走"免请求"兼容路径(详见下文"低版本兼容"一节)。
从仓库结构看,项目由两个 Gradle 模块组成:app/是可运行的示例工程,easypermissions/是库模块本体(核心源码位于 easypermissions/src/main/java/pub/devrel/easypermissions/)。
三、快速开始:在 Activity / Fragment 中转发权限结果
EasyPermissions 不接管系统回调,而是要求你把自己的onRequestPermissionsResult结果转发给库。README 给出的最小接入方式如下:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); // Forward results to EasyPermissions EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); } }这段转发的含义是:把系统返回的requestCode、permissions、grantResults三元组连同宿主对象this一起交给EasyPermissions.onRequestPermissionsResult(...)。从 EasyPermissions.java 的实现可以看到,库会在此处完成三件事:
- 将
permissions与grantResults一一对应,拆分为granted(已授予)与denied(被拒绝)两个列表; - 若宿主实现了
PermissionCallbacks,则触发onPermissionsGranted/onPermissionsDenied回调; - 若本次请求全部授予成功(
granted非空且denied为空),则通过反射执行所有带@AfterPermissionGranted(requestCode)注解、且 requestCode 匹配的方法。
receivers是可变参数(Object... receivers),意味着你可以同时传入 Activity 和它内部的 Fragment 等多个对象,让它们各自收到对应回调。
四、请求权限的完整姿势:检查 → 请求 → 注解自动续跑
README 给出的典型请求场景是"某功能同时需要CAMERA和ACCESS_FINE_LOCATION两个权限"。示例方法通常与@AfterPermissionGranted注解配合,形成一个自洽的"检查—请求—续跑"闭环:
@AfterPermissionGranted(RC_CAMERA_AND_LOCATION) private void methodRequiresTwoPermission() { String[] perms = {Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION}; if (EasyPermissions.hasPermissions(this, perms)) { // Already have permission, do the thing // ... } else { // Do not have permissions, request them now EasyPermissions.requestPermissions(this, getString(R.string.camera_and_location_rationale), RC_CAMERA_AND_LOCATION, perms); } }使用时有几个要点,README 已明确说明,此处逐条展开:
EasyPermissions#hasPermissions(...):检查应用当前是否已具备全部所需权限,可变参数(String... perms)可一次传入任意多个权限。实现上通过ContextCompat.checkSelfPermission逐个比对PackageManager.PERMISSION_GRANTED(见 EasyPermissions.java)。对 SDK < 23 的设备则直接返回true(源码注释甚至标注了 "DANGER ZONE!!! Changing this will break the library",即该分支是整个库兼容性的根基)。EasyPermissions#requestPermissions(...):真正发起系统权限请求;若系统判断"应当展示 rationale",则先弹出解释文案。requestCode需要在本请求内唯一,也是可变参数接收权限列表。@AfterPermissionGranted注解(可选,但非常方便):当某次请求的所有权限全部被授予时,所有标注了对应 requestCode 的注解方法都会被自动执行,因此务必保证 requestCode 唯一。被注解的方法必须满足两个硬性约束——返回值必须是void,且不能有输入参数;如果有需要在回调后保留的中间状态,README 建议改用onSaveInstanceState保存,以便在注解方法执行时恢复(这既是为了简化"权限到位后继续干活"的常见流程,也是反射method.invoke(object)的机制要求)。当然,你也可以放弃注解,把"继续执行"的逻辑写在onPermissionsGranted回调里,效果等价。
在注解的实现层面,@AfterPermissionGranted是@Retention(RUNTIME)、@Target(METHOD)的运行时注解(见 AfterPermissionGranted.java),其唯一的int value()就是匹配用的 requestCode。库在 runAnnotatedMethods 中通过反射遍历宿主类及其父类链上所有带该注解的方法:requestCode 不匹配则跳过;方法带参数(getParameterTypes().length > 0)会直接抛出RuntimeException提示"非 void 方法 / 带参数方法无法执行";匹配成功则setAccessible(true)后invoke。因此注解方法声明为private也完全可以(示例工程的MainFragment.smsTask()就是 private 方法,见 MainFragment.java)。
五、用 PermissionRequest 精细控制 rationale 弹窗
如果不满足于默认弹窗,README 提供了PermissionRequest的 Builder 方式,可以对 rationale 弹窗进行更精细的定制:
EasyPermissions.requestPermissions( new PermissionRequest.Builder(this, RC_CAMERA_AND_LOCATION, perms) .setRationale(R.string.camera_and_location_rationale) .setPositiveButtonText(R.string.rationale_ask_ok) .setNegativeButtonText(R.string.rationale_ask_cancel) .setTheme(R.style.my_fancy_style) .build());PermissionRequest是一个不可变(immutable)的请求模型对象,构造函数中会对权限数组做clone()防御性拷贝,并实现equals/hashCode(基于权限数组与 requestCode),相关细节见 PermissionRequest.java。其 Builder 提供的方法与默认值如下(依据 build() 方法 及源码注释整理):
| Builder 方法 | 作用 | 未设置时的默认值 |
|---|---|---|
setRationale(String / int resId) | 设置首次被拒后展示的解释文案 | 库资源R.string.rationale_ask("This app may not work correctly without the requested permissions.",见 strings.xml) |
setPositiveButtonText(String / int resId) | 设置弹窗确认按钮文字 | android.R.string.ok("确定/OK") |
setNegativeButtonText(String / int resId) | 设置弹窗取消按钮文字 | android.R.string.cancel("取消") |
setTheme(int styleRes) | 设置弹窗主题样式 | 使用默认主题(Builder 初始mTheme = -1,仅当 > 0 时才应用自定义主题) |
(构造参数)requestCode | 请求标识,必须小于 256 | 无(必填) |
关于requestCode的0~255约束:requestPermissions与onRequestPermissionsResult两个方法的 requestCode 参数都标注了@IntRange(from = 0, to = 255)(见 EasyPermissions.java),这是 Android 系统Activity.requestPermissions对 requestCode 的硬性上限,超出会抛异常,使用时需注意。
此外还有一个值得注意的行为:EasyPermissions.requestPermissions(PermissionRequest)在真正发起请求前会先做一次hasPermissions预检查,如果权限已全部具备,则不再弹窗、不再请求,而是直接走notifyAlreadyHasPermissions,模拟PERMISSION_GRANTED结果回调(见 requestPermissions 方法 与 notifyAlreadyHasPermissions)。这意味着"已授权 → 直接触发回调/注解方法"的路径同样被自动化处理。
六、实现 PermissionCallbacks:精确响应授权结果
如果你需要精细控制授权结果(而不是只依赖注解自动续跑),可以让 Activity / Fragment 实现EasyPermissions.PermissionCallbacks接口。README 给出的完整示例:
public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); // Forward results to EasyPermissions EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); } @Override public void onPermissionsGranted(int requestCode, List<String> list) { // Some permissions have been granted // ... } @Override public void onPermissionsDenied(int requestCode, List<String> list) { // Some permissions have been denied // ... } }接口定义在 EasyPermissions.java:PermissionCallbacks extends ActivityCompat.OnRequestPermissionsResultCallback,包含onPermissionsGranted(int requestCode, List<String> perms)与onPermissionsDenied(int requestCode, List<String> perms)两个方法。注意回调中的List<String>是本次请求中被授予 / 被拒绝的权限子集,而不是全部请求权限——这意味着一次请求可能同时触发两个回调(部分授权、部分拒绝),分发逻辑见onRequestPermissionsResult中对granted/denied两个列表的处理。这一行为在测试用例 EasyPermissionsTest.java 中有明确验证:当结果数组为{DENIED, GRANTED}时,onPermissionsDenied收到READ_SMS,onPermissionsGranted收到ACCESS_FINE_LOCATION。
七、处理"永久拒绝":用 AppSettingsDialog 引导用户去系统设置
某些场景下,应用缺少特定权限就无法正常工作。如果用户勾选了"不再询问(Never Ask Again)",系统将不再弹权限框,应用也无法再通过普通方式请求该权限,只能引导用户去系统设置页手动开启。README 给出的处理方式是EasyPermissions.somePermissionPermanentlyDenied(...)+AppSettingsDialog:
@Override public void onPermissionsDenied(int requestCode, List<String> perms) { Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size()); // (Optional) Check whether the user denied any permissions and checked "NEVER ASK AGAIN." // This will display a dialog directing them to enable the permission in app settings. if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { new AppSettingsDialog.Builder(this).build().show(); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == AppSettingsDialog.DEFAULT_SETTINGS_REQ_CODE) { // Do something after user returned from app settings screen, like showing a Toast. Toast.makeText(this, R.string.returned_from_app_settings_to_activity, Toast.LENGTH_SHORT) .show(); } }README 特别强调了一个重要限制:受 Android 框架权限 API 提供的信息所限,somePermissionPermanentlyDenied只有在"权限已被拒绝、且应用已收到onPermissionsDenied回调"之后调用才有效;否则库无法区分"永久拒绝"和"尚未拒绝"两种情况。所以在onPermissionsDenied回调内部使用它是最正确的姿势。
从实现角度看:
somePermissionPermanentlyDenied底层委托给PermissionHelper#somePermissionPermanentlyDenied,其判定逻辑是:遍历被拒权限,任一权限满足!shouldShowRequestPermissionRationale(perm)即为永久拒绝(见 PermissionHelper.java)——因为 Android 的设计是:一旦用户勾选"不再询问",系统对该权限的 rationale 询问将永远返回false。测试用例 EasyPermissionsTest.java 分别验证了"不再展示 rationale → 判定为永久拒绝"与"仍展示 rationale → 判定非永久拒绝"两个分支。AppSettingsDialog.Builder(this)支持Activity或Fragment两种宿主,可链式定制标题、文案、按钮、主题、请求码等(详见下节参数表)。调用.build().show()后,库内部会启动AppSettingsDialogHolderActivity来承载弹窗,用户点击"去设置"后,通过Settings.ACTION_APPLICATION_DETAILS_SETTINGS携带本应用包名跳转系统应用详情页(见 AppSettingsDialogHolderActivity.java)。- 从设置页返回后,结果会回传到原宿主的
onActivityResult,请求码默认是AppSettingsDialog.DEFAULT_SETTINGS_REQ_CODE = 16061(定义见 AppSettingsDialog.java)。你可以在返回回调里用EasyPermissions.hasPermissions(...)复查权限是否已被用户手动打开(示例工程 MainActivity.java 就是通过检查 Camera / 定位通讯录 / 短信权限并弹出 Toast 反馈的)。
AppSettingsDialog.Builder的可配置项与默认值如下(依据 Builder 源码整理):
| Builder 方法 | 作用 | 默认值 |
|---|---|---|
setThemeResId(int styleRes) | 弹窗主题 | 应用默认主题 |
setTitle(String / int resId) | 弹窗标题 | R.string.title_settings_dialog("Permissions Required") |
setRationale(String / int resId) | 弹窗解释文案 | R.string.rationale_ask_again(提示应用可能无法正常工作、请到设置页开启权限,见 strings.xml) |
setPositiveButton(String / int resId) | 确认按钮文字 | android.R.string.ok |
setNegativeButton(String / int resId) | 取消按钮文字 | android.R.string.cancel |
setRequestCode(int) | 跳设置页请求码 | DEFAULT_SETTINGS_REQ_CODE(16061) |
setOpenInNewTask(boolean) | 是否以FLAG_ACTIVITY_NEW_TASK在新任务中打开设置页 | false |
八、与 rationale 弹窗交互:RationaleCallbacks
如果你希望在"解释为什么需要权限"的弹窗被用户点击后做出响应,可以实现EasyPermissions.RationaleCallbacks接口:
@Override public void onRationaleAccepted(int requestCode) { // Rationale accepted to request some permissions // ... } @Override public void onRationaleDenied(int requestCode) { // Rationale denied to request some permissions // ... }关键提醒(README 原文要点):rationale 回调并不代表权限发生了变化——用户接受了 rationale 只说明系统接下来会弹出权限请求框(或直接请求),最终授权与否仍要通过PermissionCallbacks判断。
从源码看,这两个回调的触发点在 RationaleDialogClickListener.onClick:用户点弹窗"确认"时回调onRationaleAccepted并立刻以directRequestPermissions发起真实权限请求;点"取消"时回调onRationaleDenied并向PermissionCallbacks模拟通知onPermissionsDenied。另外,无论弹窗在 Fragment 还是 Activity 上展示,click listener 都会优先取父 Fragment 作为宿主,否则回退到 Activity。
九、源码级原理:一次权限请求背后的完整调用链
将以上内容串联起来,一次完整的权限请求在库内部的调用链如下(对应关系均可从源码确认):
- 入口:
EasyPermissions.requestPermissions(...)(Activity / Fragment /PermissionRequest三种重载),统一收敛到requestPermissions(PermissionRequest)。先做hasPermissions预检查:已具备 → 直接模拟授予回调并结束;否则进入下一步。 - 策略分发:
PermissionHelper.newInstance(host)根据宿主机类型与系统版本选择合适的 Helper(见 PermissionHelper.newInstance):Build.VERSION.SDK_INT < M→LowApiPermissionsHelper;- 宿主是
AppCompatActivity→AppCompatActivityPermissionsHelper; - 宿主是普通
Activity→ActivityPermissionHelper; - 宿主是
androidx.fragment.app.Fragment→SupportFragmentPermissionHelper。
- rationale 判断:
PermissionHelper#requestPermissions中,若任一权限满足shouldShowRequestPermissionRationale,则走showRequestPermissionRationale(弹出RationaleDialogFragment或RationaleDialogFragmentCompat,分别对应 framework Dialog 与 support v4/v7 的 AppCompat 环境,二者共用RationaleDialogConfig序列化参数并统一构造不可取消的AlertDialog,见 RationaleDialogConfig.java);否则直接directRequestPermissions发起系统请求(见 PermissionHelper.java)。BaseSupportPermissionsHelper还会检查同名 rationale Fragment 是否已展示,避免重复弹窗(见 BaseSupportPermissionsHelper.java)。 - 结果回流:系统回调
onRequestPermissionsResult→ 应用转发给EasyPermissions.onRequestPermissionsResult→ 拆分 granted/denied → 触发PermissionCallbacks回调 → 全部成功时反射执行@AfterPermissionGranted方法。 - 永久拒绝兜底:
onPermissionsDenied中调用somePermissionPermanentlyDenied→AppSettingsDialog→AppSettingsDialogHolderActivity→ 系统设置页 →onActivityResult收尾。
十、低版本兼容(API < 23)
对于 Android M 之前的系统(API < 23),运行时权限模型并不存在,安装时授权即可。库通过LowApiPermissionsHelper做了显式兼容(见 LowApiPermissionsHelper.java):
hasPermissions(...)在 SDK < M 时恒定返回true(配合源码中的警告注释),让业务代码无需分叉;- 低版本上调用
requestPermissions/ 展示 rationale 会抛出IllegalStateException("Should never be requesting permissions on API < 23!"),从机制上杜绝无意义的请求。
配套的 EasyPermissionsLowApiTest.java(Robolectric@Config(sdk = 19))验证了:低版本下hasPermissions恒为真,且从 Activity / Support FragmentActivity / AppCompatActivity / Fragment 发起的请求都会直接得到onPermissionsGranted回调。
十一、测试用例佐证:行为即契约
库的测试代码本身就是"行为契约"的最佳说明,值得阅读以加深理解:
- EasyPermissionsTest.java(Robolectric,SDK 23)覆盖了权限检查、回调分发、注解方法执行、rationale 弹窗展示与按钮文案、
somePermissionDenied/somePermissionPermanentlyDenied/permissionPermanentlyDenied的全部正向与反向分支; - AppSettingsDialogTest.java 验证了
AppSettingsDialog从 Activity 与 Support Fragment 构建时的跳转 intent、默认请求码(DEFAULT_SETTINGS_REQ_CODE)以及正负按钮点击监听; - EasyPermissionsLowApiTest.java 如上所述,锁定了低版本兼容行为。
十二、示例工程速览
仓库的app/模块提供了一个完整的示例应用,是 README 各 API 的"可运行版本":
- MainActivity.java:
AppCompatActivity同时实现PermissionCallbacks与RationaleCallbacks,演示单权限(相机)、双权限(定位 + 通讯录)的请求,以及永久拒绝后的设置页引导与返回复查; - MainFragment.java:演示在
Fragment中请求短信权限,并使用 private 的@AfterPermissionGranted方法; - strings.xml:包含各权限的 rationale 文案与"从设置页返回"的提示模板。
附:开源许可
EasyPermissions 遵循 Apache License 2.0(版权所有 Google,2017),完整许可文本见 LICENSE。该协议允许自由使用、修改与分发,但需保留版权声明与免责条款。
使用提示:本文所有代码与行为描述均以当前仓库(
easypermissions库模块源码、app示例模块与easypermissions/src/test测试用例)为准;接入时请以 README 中标注的依赖版本(AndroidX 环境3.0.0、Support Library 环境2.0.1)为准,并确保targetSdk不低于 23。
- 移动开发
- 认证鉴权
【免费下载链接】easypermissions
Simplify Android M system permissions
相关推荐
EasyPermissions:简化你的Android权限管理
EasyPermissions:简化你的Android权限管理 项目基础介绍与编程语言 EasyPermissions 是一个由Google支持的开源项目,专为
移动开发认证鉴权Android M 运行时权限被拒绝处理指南:shouldShowRequestPermissionRationale 的语义与实战
Android M 运行时权限被拒绝处理指南:shouldShowRequestPermissionRationale 的语义与实战 本文以 在Android
文档教程知识库163MusicLyrics:如何3秒搞定音乐歌词获取难题?
163MusicLyrics:如何3秒搞定音乐歌词获取难题? 你是否曾为寻找一首歌的完整歌词而翻遍全网?或者需要批量处理数百首歌曲的字幕却无从下手?音乐歌词获取
桌面应用音视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考