Android 基础补强 B20|读 PermissionX 学封装:一个好用的 API 应该承诺什么
《第一行代码》第 16 章通过 PermissionX 讲解开源库开发。把重复代码移到一个新模块,只是封装的开始;调用者真正需要知道的是输入、结果、生命周期、失败与兼容边界。
本篇以权限结果表达为例讨论库设计,联系现代 Activity Result API。重点是理解封装方法,不复制一个未经平台验证的权限库,也不把书中的历史仓库发布步骤当作当前可直接执行的流程。
先定义库要隐藏什么,不能隐藏什么
权限库可以减少重复注册、结果整理和回调分发,但不能替应用决定为什么需要某项权限,也不能绕过用户选择和系统限制。调用者仍负责把申请放在合适业务时机,解释用途,并处理拒绝后的替代路径。
只返回一个 Boolean 常常不够。一次请求多项权限时,可能部分获得、部分拒绝;有些能力又随系统版本、授权范围和后续设置变化。API 应表达实际返回结果,而不是把复杂情况压成一个容易误解的“全部搞定”。
用结果模型暴露有用信息
下面的纯 Kotlin 示例只整理已经取得的结果,不负责弹出权限申请。调用方要先完成当前授权检查与系统回调合并,保证传入的是本次请求相关信息;缺失项按未获准处理。示例未在稿件中编译。
data class PermissionReport(
val granted: Set<String>,
val denied: Set<String>
) {
val allGranted: Boolean get() = denied.isEmpty()
}
fun permissionReport(
requested: Set<String>,
results: Map<String, Boolean>
): PermissionReport {
val granted = requested.filter { results[it] == true }.toSet()
return PermissionReport(granted, requested – granted)
}
这个模型故意不直接返回“永久拒绝”。仅凭一次 false 或是否应该解释理由,并不足以在所有历史状态下作出统一判断。申请历史、系统行为和再次引导策略应单独分析;应用也不应反复打断用户强制申请。
空请求集合怎样处理也要写进契约。上述函数按集合逻辑得到全部满足,但真正申请入口可以提前拒绝无意义调用或直接返回无需申请。重要的是规则一致,并且测试能够验证。
注册与发起不是同一个时机
Activity Result API 把结果注册与启动操作分开,注册应按生命周期要求稳定完成,启动则响应合适的用户动作。封装若只提供一个随处可调用的静态 request,内部却临时持有任意 Activity,就可能把生命周期问题藏起来。Activity Result API
设计时可以要求宿主在创建阶段建立请求器,由请求器在适当时机 launch;结果交给调用方处理。页面重建后注册关系如何恢复,尚未返回的请求怎样对应,回调是否捕获旧 View,都应有明确验证。
不必为了复现书中的封装手法,就在新实现里无条件加入不可见 Fragment。应比较当前平台 API 已经提供什么,以及自己的封装还真正减少了哪些重复工作。
测试库时从调用者视角出发
先测试结果整理:全同意、全拒绝、部分同意、缺失项、额外无关字段、空集合。再用样例应用验证生命周期:申请中旋转、返回时 View 已重建、用户取消、重新进入功能。纯函数测试与平台交互测试各有范围,不能互相替代。
如果库内部增加缓存授权结果,必须考虑用户后来在设置里撤销权限。缓存只能服务于明确短期逻辑,不能代替使用功能前的适当检查。API 文档应说明结果是某个时刻的观察,不是永远不变的保证。
故障实验可以先把多项结果压成任意一项成功就 true,再输入部分同意,观察调用者错误继续执行功能。恢复完整结果后,验证只有依赖已获权限的部分继续,其他部分给出可恢复提示。
模块发布不仅是上传一个 AAR
Android library 模块与 application 模块职责不同,产物需要包含适当代码、资源和元数据。对外发布还应说明坐标、版本、最低支持条件、依赖、公开 API、样例、许可与变更说明。Android 库发布
构建变体也需要明确:发布哪种 variant,消费者将得到什么依赖和资源,是否需要保留规则。不要为了让一个样例工程能跑,就把无关的应用依赖全部暴露出去。发布变体配置
书中的 jcenter 发布章节应按历史背景阅读。当前发布流程需要依据目标仓库的现行要求重新配置;Android 官方也提供了 JCenter 服务变化与迁移说明。JCenter 迁移说明 本篇只讨论准备与验证,不进行任何真实仓库发布。
一个 API 改动会影响谁
如果调用方原来接收 Boolean,后来改成 PermissionReport,源代码调用方式会变化;如果只是新增一个带默认值的参数,仍要检查 Java 调用者、二进制兼容和平台约束。语义变化即使没有修改函数名,也可能破坏调用者预期。
因此变更说明应写清之前行为、之后行为和迁移方法。样例项目既是演示,也是消费者视角的验证入口;测试通过不代表说明文档自动正确,二者需要一起更新。
三道原创面试自测
封装得越少参数越好吗? 不一定,隐藏必要生命周期或业务条件可能让调用更危险。追问“哪些应暴露”,暴露调用者必须决定的范围、结果和前提,隐藏可重复的机械细节。
权限库能否保证授权一直有效? 不能,用户和系统状态会变化。追问“调用前做什么”,在实际使用功能时按平台要求检查能力,并处理失效与拒绝。
开源库测试只跑样例够吗? 不够,还需要结果边界、生命周期、兼容和发布元数据验证。追问“为什么保留样例”,它能够从消费者视角暴露难用的 API 和遗漏的配置。
完成本篇后,尝试给一个最小工具写出清晰契约、两个异常路径和消费者样例,再考虑是否值得成为独立库。可复用的价值来自稳定行为,而不只是更短的调用代码。



