☰
EasyPermissions 实战指南:用一套 API 简化 Android M 及以上的运行时权限逻辑
2026/10/14 20:31:29 网站建设 项目流程
  • 移动开发
  • 认证鉴权

【免费下载链接】easypermissions

Simplify Android M system permissions

项目地址:https://gitcode.com/gh_mirrors/ea/easypermissions
点击查看免费下载

本篇技术指南以开源仓库 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' }

需要注意两点:

  1. 按工程使用的 UI 体系选择版本:如果应用已迁移到 AndroidX,使用3.0.0;如果仍在使用旧版 Android Support Library,则使用2.0.1。两者 API 形态一致,但底层分别依赖androidx.appcompat与android.support组件。
  2. 运行前提:本文描述的运行时权限 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 的实现可以看到,库会在此处完成三件事:

  1. 将permissions与grantResults一一对应,拆分为granted(已授予)与denied(被拒绝)两个列表;
  2. 若宿主实现了PermissionCallbacks,则触发onPermissionsGranted/onPermissionsDenied回调;
  3. 若本次请求全部授予成功(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。

九、源码级原理:一次权限请求背后的完整调用链

将以上内容串联起来,一次完整的权限请求在库内部的调用链如下(对应关系均可从源码确认):

  1. 入口:EasyPermissions.requestPermissions(...)(Activity / Fragment /PermissionRequest三种重载),统一收敛到requestPermissions(PermissionRequest)。先做hasPermissions预检查:已具备 → 直接模拟授予回调并结束;否则进入下一步。
  2. 策略分发:PermissionHelper.newInstance(host)根据宿主机类型与系统版本选择合适的 Helper(见 PermissionHelper.newInstance):
    • Build.VERSION.SDK_INT < M→LowApiPermissionsHelper;
    • 宿主是AppCompatActivity→AppCompatActivityPermissionsHelper;
    • 宿主是普通Activity→ActivityPermissionHelper;
    • 宿主是androidx.fragment.app.Fragment→SupportFragmentPermissionHelper。
  3. 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)。
  4. 结果回流:系统回调onRequestPermissionsResult→ 应用转发给EasyPermissions.onRequestPermissionsResult→ 拆分 granted/denied → 触发PermissionCallbacks回调 → 全部成功时反射执行@AfterPermissionGranted方法。
  5. 永久拒绝兜底: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

项目地址:https://gitcode.com/gh_mirrors/ea/easypermissions
点击查看免费下载
上一篇:Awesome Fiber数据库集成:PostgreSQL、MongoDB连接的最佳实践
下一篇:如何用Homebrew Cask在3分钟内安装Google Chrome和其他macOS应用

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询