news 2026/9/23 14:56:01

PermissionsDispatcher 的 Java 使用指南:注解驱动的 Android 运行时权限处理完整实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PermissionsDispatcher 的 Java 使用指南:注解驱动的 Android 运行时权限处理完整实践

PermissionsDispatcher 的 Java 使用指南:注解驱动的 Android 运行时权限处理完整实践

【免费下载链接】PermissionsDispatcherA declarative API to handle Android runtime permissions.项目地址: https://gitcode.com/gh_mirrors/pe/PermissionsDispatcher

导读

本文以 PermissionsDispatcher 官方 Java 文档(doc/java_usage.md)为主体,完整讲解如何在纯 Java 项目中用声明式注解替代繁琐的checkSelfPermission/requestPermissions样板代码。你将掌握 5 个核心注解的语义、注解处理器自动生成的XxxPermissionsDispatcher类的委托方式,以及底层生成代码的真实结构,并最终能在自己的 Activity / Fragment 中落地一套完整、可运行、经测试验证的运行时权限方案。

一、先了解 PermissionsDispatcher 的定位

PermissionsDispatcher 是一个基于注解处理(annotation processing)的运行时权限库:你在源码里用少量注解声明"哪个方法需要什么权限、权限被拒时做什么",编译期处理器会自动生成一个辅助类,把权限检查、请求发起、结果回调、rationale 弹窗等逻辑全部接管。项目 README 明确给出三条特性:完全支持 Kotlin / Java、支持特殊权限(Special Permissions)、100% 无反射(见 README.md)。Java 侧走annotationProcessor路径,即本文主题。

二、零、准备 AndroidManifest:声明权限

无论使用哪个运行时权限库,第一步都是先在AndroidManifest.xml中声明目标权限:

<uses-permission android:name="android.permission.CAMERA" />

运行时权限(dangerous permission)必须先在 Manifest 中声明,代码中才能请求。本文示例以相机权限(Manifest.permission.CAMERA)贯穿始终;若使用联系人或特殊权限,照葫芦画瓢替换权限名即可。

三、一、为类与方法挂上注解

PermissionsDispatcher 只引入少量注解,API 保持精简。下表是官方文档的注解总览(含各注解的源码定位):

AnnotationRequiredDescription源码位置
@RuntimePermissions在类上注册一个ActivityFragment,声明其需要被权限框架接管annotation/.../RuntimePermissions.java
@NeedsPermission标注"真正执行需要权限的操作"的方法,可指定一个或多个权限annotation/.../NeedsPermission.java
@OnShowRationale标注解释"为什么需要该权限"的方法,接收一个PermissionRequest对象用于在用户输入后继续或中止请求annotation/.../OnShowRationale.java
@OnPermissionDenied用户未授予权限时调用的方法annotation/.../OnPermissionDenied.java
@OnNeverAskAgain用户勾选"不再询问"时调用的方法annotation/.../OnNeverAskAgain.java

注意:官方文档特别强调——被注解的方法不能是private。这是注解处理器校验规则的一部分,处理器中还有PrivateMethodException等一整套校验异常(见 processor/.../exception/PrivateMethodException.kt)。

下面是一个完整的最小示例,MainActivity需要Manifest.permission.CAMERA

@RuntimePermissions public class MainActivity extends AppCompatActivity { @NeedsPermission(Manifest.permission.CAMERA) void showCamera() { getSupportFragmentManager().beginTransaction() .replace(R.id.sample_content_fragment, CameraPreviewFragment.newInstance()) .addToBackStack("camera") .commitAllowingStateLoss(); } @OnShowRationale(Manifest.permission.CAMERA) void showRationaleForCamera(final PermissionRequest request) { new AlertDialog.Builder(this) .setMessage(R.string.permission_camera_rationale) .setPositiveButton(R.string.button_allow, (dialog, button) -> request.proceed()) .setNegativeButton(R.string.button_deny, (dialog, button) -> request.cancel()) .show(); } @OnPermissionDenied(Manifest.permission.CAMERA) void showDeniedForCamera() { Toast.makeText(this, R.string.permission_camera_denied, Toast.LENGTH_SHORT).show(); } @OnNeverAskAgain(Manifest.permission.CAMERA) void showNeverAskForCamera() { Toast.makeText(this, R.string.permission_camera_neverask, Toast.LENGTH_SHORT).show(); } }

关键点拆解:

  • @NeedsPermission(Manifest.permission.CAMERA)NeedsPermissionvalue()String[]类型(见 NeedsPermission.java),因此可以一次声明多个权限,例如@NeedsPermission(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)——sample 中的showContacts()正是这么写的(见 sample/.../MainActivity.kt)。
  • @OnShowRationalePermissionRequest:rationale 方法接收一个PermissionRequest参数。PermissionRequest只有两个方法proceed()cancel()(见 annotation/.../PermissionRequest.java),分别表示"用户同意继续请求"与"用户拒绝"。官方文档补充:如果 rationale 方法不指定参数,编译器会生成process${NeedsPermissionMethodName}ProcessRequestcancel${NeedsPermissionMethodName}ProcessRequest两个方法,可替代PermissionRequest使用(例如配合DialogFragment的场景)。
  • 关联注解的权限列表必须一致@OnShowRationale/@OnPermissionDenied/@OnNeverAskAgainvalue()必须与对应的@NeedsPermission完全一致。处理器通过findMatchingMethodForNeeds按权限值精确配对(见 processor/.../util/Helpers.kt),配不上会直接编译失败。

四、二、把权限处理委托给生成类

编译后,注解处理器会为MainActivity生成一个名为MainActivityPermissionsDispatcher的类(命名规则为[Activity Name] + PermissionsDispatcher,对应常量GEN_CLASS_SUFFIX = "PermissionsDispatcher",见 processor/.../util/Constants.kt)。

你唯一要做的就是把这个 helper 类"接进"生命周期回调:

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.button_camera).setOnClickListener(v -> { // NOTE: delegate the permission handling to generated method MainActivityPermissionsDispatcher.showCameraWithPermissionCheck(this); }); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); // NOTE: delegate the permission handling to generated method MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults); }

两处委托的含义:

  1. 入口showCameraWithPermissionCheck(this)是生成方法(后缀常量GEN_WITH_PERMISSION_CHECK_SUFFIX = "WithPermissionCheck")。它内部先做权限状态检查:已授权 → 直接调用你的showCamera();未授权 → 按需走 rationale 或直接发起系统请求。
  2. 出口:在onRequestPermissionsResult中把回调转交给MainActivityPermissionsDispatcher.onRequestPermissionsResult(...),由生成代码根据 requestCode 匹配到对应请求,再根据授权结果分发到showCamera()/showDeniedForCamera()/showNeverAskForCamera()

关于 requestCode:生成类里每个@NeedsPermission方法都有一个专属的REQUEST_XXX静态常量字段,由RequestCodeProviderAtomicInteger原子递增产生,保证整个应用内唯一(见 processor/.../RequestCodeProvider.kt)。你在onRequestPermissionsResult里无需关心具体数值,框架会自己匹配。

五、深入原理:生成代码长什么样

虽然生成代码在编译期才产出,但从处理器源码可以精确还原它的结构。JavaBaseProcessorUnit.createTypeSpec显示,生成的类包含(见 processor/.../impl/java/JavaBaseProcessorUnit.kt):

  • 静态常量字段:每个注解方法对应REQUEST_XXX(请求码)、PERMISSION_XXX(权限字符串数组)、必要时还有PENDING_XXX(挂起的GrantableRequest)以及带参数方法所需的参数缓存字段;
  • 私有构造器:类不可实例化,全部以静态方法对外;
  • createWithPermissionCheckMethods:生成xxxWithPermissionCheck(target)系列入口方法;
  • createOnShowRationaleCallbackMethods:生成 rationale 回调链;
  • createPermissionHandlingMethods:生成onRequestPermissionsResult分发逻辑;
  • createPermissionRequestClasses:为每个带PermissionRequest参数的方法生成内部XxxPermissionRequest类。

针对 Activity,JavaActivityProcessorUnit揭示了实际调用链(见 processor/.../impl/java/JavaActivityProcessorUnit.kt):

  • 权限状态判断使用androidx.core.app.ActivityCompat/ContextCompat体系;
  • rationale 判断调用ActivityCompat.shouldShowRequestPermissionRationale(target, permission)
  • 发起请求调用ActivityCompat.requestPermissions(target, permission, requestCode)

也就是说,生成代码内部完全基于 AndroidX 的ActivityCompat实现,这正是 README 所述4.x 仅支持 Jetpack的原因;使用 appcompat 的旧项目需要停留在 3.x(详见 README.md 与 doc/migration_guide.md)。

另外,JavaBaseProcessorUnit中还有一张特殊权限映射表,把android.permission.WRITE_SETTINGSandroid.permission.SYSTEM_ALERT_WINDOW分别路由到WriteSettingsHelper/SystemAlertWindowHelper(见 JavaBaseProcessorUnit.kt)。这两类权限不走requestPermissions,而是打开系统设置页让用户手动授权,框架会生成不同的 helper 处理。详细用法见 doc/special_permissions.md。

六、行为矩阵:测试用例给出的可验证结论

仓库在 test/.../ActivityWithAllAnnotationsPermissionsDispatcherTest.kt 中通过 PowerMock 完整验证了生成类的行为,这些结论可以直接当作使用手册:

场景期望行为
权限已授予直接调用showCamera()already granted call the method
权限未授予且 rationale 为 true不调用目标方法,调用showRationaleForCamera(request)
权限未授予且 rationale 为 false不调用 rationale 方法
onRequestPermissionsResult返回 GRANTED调用showCamera()
返回 DENIED 且 rationale 为 true调用showDeniedForCamera()
返回 DENIED 且 rationale 为 false调用showNeverAskForCamera()
requestCode 与库无关所有回调方法均不触发
SDK < 23(测试中为 22)直接依据checkSelfPermission结果决定是否调用目标方法

最后两行很重要:在 Android 6.0(API 23)之前的设备上,权限在安装时授予,无需运行时请求,生成代码对此做了兼容分支——测试blow M follows checkSelfPermissions result false/true正是模拟该场景(见 ActivityWithAllAnnotationsPermissionsDispatcherTest.kt)。

七、Java 侧集成配置(Gradle)

要让注解处理器真正跑起来,需要在app 模块build.gradle中添加依赖(${latest.version}请以 Maven Central 上的实际版本号为准):

dependencies { implementation "com.github.permissions-dispatcher:permissionsdispatcher:${latest.version}" annotationProcessor "com.github.permissions-dispatcher:permissionsdispatcher-processor:${latest.version}" }

Java 项目用annotationProcessor;Kotlin 项目则改用kapt(配合apply plugin: 'kotlin-kapt'),详见 README.md 的 Installation 一节。需要注意的是,仓库已从 jCenter 迁移至 Maven Central,迁移细节见 doc/migration_guide.md。

八、进一步实践建议

  • 完整可运行示例:仓库的sample模块(sample/src/main/kotlin/permissions/dispatcher/sample/MainActivity.kt)同时演示了单权限(CAMERA)与多权限(READ_CONTACTS + WRITE_CONTACTS)两种写法,还包含用AlertDialog展示 rationale 的showRationaleDialog辅助方法,建议对照阅读。
  • maxSdkVersion@NeedsPermission还支持maxSdkVersion参数(默认 0,见 NeedsPermission.java),用于声明"仅在某个 SDK 版本以下才需要该权限",可避免高版本系统上的多余请求,详见 doc/maxsdkversion.md。
  • Kotlin 开发者:若项目使用 Kotlin,官方推荐优先使用ktx模块(基于协程与扩展函数,见 ktx/README.md),或退而使用kapt走与本文完全相同的注解流程。

结语

使用 PermissionsDispatcher 的 Java 流程可以总结为四步:Manifest 声明权限 → 类上加@RuntimePermissions、方法上加配套注解 → 在 UI 入口调用生成的xxxWithPermissionCheck(target)→ 在onRequestPermissionsResult中委托给生成的静态方法。整个过程中,权限的"检查—请求—rationale—拒绝—不再询问"状态机完全由注解处理器生成的代码接管,你的业务类只需保留最纯粹的"有权限时做什么、没权限时提示什么"逻辑。

【免费下载链接】PermissionsDispatcherA declarative API to handle Android runtime permissions.项目地址: https://gitcode.com/gh_mirrors/pe/PermissionsDispatcher

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 14:55:56

3步搞定腾讯云学生服务器续费:从报错到精通避坑指南

3步搞定腾讯云学生服务器续费:从报错到精通避坑指南 盯着屏幕上一堆红色的 StackTrace 报错,你是不是也头大?别慌,这不是代码写崩了,而是你的“学生身份”或“支付通道”卡住了。很多刚入门的朋友,把 腾讯云学生服务器续费…

作者头像 李华
网站建设 2026/9/23 14:55:53

一丶从零搭建面试题库:3个核心模块破解原理难题的最佳实践

一丶从零搭建面试题库:3个核心模块破解原理难题的最佳实践 面试被问原理答不上来,是因为只背了八股文没动过手。很多应届生简历上写着“熟悉Python”,结果面试官问“Python GIL锁是怎么实现的”,大脑一片空白。这种尴尬,靠死记硬背解决不了。真正的 最佳实践…

作者头像 李华
网站建设 2026/9/23 14:55:27

三维地图制作性能优化一文搞懂:解决API变动后的卡顿难题

三维地图制作性能优化一文搞懂:解决API变动后的卡顿难题 版本升级后 API 全变了,你的三维地图还在掉帧吗?别急着骂娘,先看看是不是渲染逻辑没跟上。很多开发者在 Cesium 或 Three.js 从旧版迭代到新版时,发现原本流畅的交互瞬间变成…

作者头像 李华
网站建设 2026/9/23 14:55:25

35资料网拆解:搞定高频面试题的源码逻辑

35资料网拆解:搞定高频面试题的源码逻辑 配置环境就卡半天,是不是常态? 别急着骂娘,大概率是依赖版本没对齐。 今天聊点硬核的,结合【35资料网】上的实战案例,拆解一个经典的高频面试题:并发场景下的状态同步。 这问题看似简单,实则坑多。 很多候选人只背了“加锁”,却说不清死锁怎么破。…

作者头像 李华
网站建设 2026/9/23 14:54:49

搞定httpwww:3个性能优化点让你代码跑通

搞定httpwww:3个性能优化点让你代码跑通 复制来的 httpwww 相关代码,是不是经常报错?别急,这通常是环境配置或底层原理没搞懂。 面试中被问到 HTTP 性能优化,很多人只会背“加缓存”,其实细节才决定成败。 今天拆解 httpwww 在实战中的高频坑点,帮你把代码跑通,顺便拿下面试。…

作者头像 李华
网站建设 2026/9/23 14:54:35

5步搞定前任约见面是什么心态项目最佳实践

5步搞定前任约见面是什么心态项目最佳实践 看了一堆教程还是不会写项目?别急,这不是你笨,是没人告诉你 最佳实践 到底长什么样。今天这篇《前任约见面是什么心态》实战教程,直接带你从0到1搭建一个可运行的Web应用。 项目目标…

作者头像 李华