做 Cordova 开发迟早要和 Android 运行时权限打交道。我在几个项目里被摄像头、定位、存储这些权限反复折腾之后终于把 cordova-plugin-android-permissions 权限检查这套流程整理得比较顺手。如果你也在 Cordova 页面里遇到过白屏、功能没反应、回调结果对不上那这篇内容应该对你有用。我下面会从插件原理、API 拆解、完整落地流程、坑点排查几个角度来写所有代码都是我在实际构建中验证过的写法不绕弯子。适合刚接触 Cordova 的也适合已经写了一阵子但被权限问题卡住的朋友。先把权限这件事搞明白再动手写代码后面会省很多事。1. 先搞清楚 Cordova 项目为什么绕不开权限检查1.1 Android 权限体系的变化很多新同学一上来就写插件调用结果总是一脸懵为什么同一个权限有的手机上弹窗有的手机直接通过这里要先看 Android 权限模型的变化。Android 6.0 是一个分水岭。6.0 之前安装 App 时会在安装界面把所有权限列出来用户同意后安装之后整个生命周期里这些权限都默认可用了。6.0 开始系统改成“运行时权限”机制安装时不再把所有权限一次性暴露给用户而是到了你真正要用某个敏感能力的时候才弹窗询问用户同不同意。也就是说权限状态变成了“动态的、可能随时改变”的状态。Cordova 项目不同你的业务代码大多跑在 WebView 里。WebView 里面的 JavaScript 本身没有权限上下文它只是被原生壳包裹起来的页面。系统把权限授权给的是那个原生 Activity 和 App 进程而不是页面里的 JS。所以你在 JS 里没办法直接调用系统权限窗口必须通过一层原生插件作为桥梁把“我有什么权限”“我要申请什么权限”这些系统能力暴露给 JS 侧。这就是 cordova-plugin-android-permissions 这类插件的生存空间。1.2 没有权限检查时会发生什么我最早踩到权限坑是在一个扫码项目里。页面实现得有模有样扫码按钮一按直接调摄像头插件。结果在 Android 7.0 测试机上运行时页面能打开但摄像头区域一片黑过一会儿弹了个“摄像头初始化失败”。当时第一反应是摄像头插件没配置好查了半天才发现是宿主 App 没有申请 CAMERA 权限系统把摄像头能力直接拒绝了。这就是权限检查的核心价值它让你在调用敏感功能之前先确认自己是否具备使用条件。没有这道检查后面所有动作都是在不确定性里碰运气。相机相关、定位相关、录音相关、联系人相关任何一个环节缺权限轻则功能不生效重则闪退或者引发用户投诉说“怎么什么都没反应”。Cordova 项目尤其容易踩这个坑因为页面和原生能力是分离的。很多依赖原生能力的插件会在自己内部做权限申请但有些插件不会它们只负责“调用”不负责“申请”。这时候就得你在业务层补上权限检查与申请的逻辑。1.3 为什么很多人选择这个插件提到 Cordova 权限申请可选方案其实不少。功能更全的插件可能支持定位开关、通知开关、多个权限组的检查还能直接打开系统设置页但相应的包体也会更重API 也更复杂。而这个普通插件的定位非常单纯它只做两件事检查权限状态发起权限申请。API 也是三个方法学起来非常快。我个人的建议是如果你的项目只是需要在调用敏感能力之前做一个“门卫”不需要一堆额外功能那这个轻量方案就够用了。越轻的插件升级 Cordova 平台时越不容易出幺蛾子。你不需要为了一个权限跳转功能引入一堆用不到的原生代码。另一个原因是它在 Android 6.0 到 Android 14 这一大段版本区间里处理的都是同一套运行时权限模型。虽然高版本权限名称和分组有不少变化但插件底层的 ActivityCompat.requestPermissions 调用方式基本稳定只要权限字符串、Manifest 声明、targetSdkVersion 配好兼容性很少出大问题。2. 权限检查核心 API 拆解2.1 检查权限hasPermission插件的检查方法叫 hasPermission。它接收两个参数权限名、成功回调和失败回调。权限名是 Android 系统的标准权限字符串比如android.permission.CAMERA。下面是一段最基础的调用document.addEventListener(deviceready, function () { var permissions cordova.plugins.permissions; permissions.hasPermission(android.permission.CAMERA, function (status) { if (status.hasPermission) { console.log(已经有相机权限可以放行); } else { console.log(没有相机权限准备去申请); } }, function (error) { console.error(检查权限出错, error); }); }, false);注意第一行先监听 deviceready 事件。Cordova 插件只有在原生环境准备好之后才可用你如果放在页面普通 DOMContentLoaded 回调里直接调用大概率会报cannot read property permissions of undefined。成功回调里的status是一个对象重点字段就是hasPermission。它为 true 时说明当前应用已经拿到了这个权限false 时说明还没有或者已经被用户拒绝。2.2 请求权限requestPermission 与 requestPermissions检查出来没有权限下一步就是申请。插件提供了两个方法requestPermission申请单个权限requestPermissions批量申请多个权限在业务里我其实更推荐直接用批量方法因为很多功能不会只依赖一个权限。比如扫码功能通常需要相机如果还要保存扫码结果图片可能又涉及存储权限。一次性把相关权限都请求了用户体验更顺。cordova.plugins.permissions.requestPermissions([ android.permission.CAMERA, android.permission.READ_EXTERNAL_STORAGE ], function (status) { if (status.hasPermission) { console.log(所有权限都已通过); } else { console.log(有权限被拒绝需要做降级处理); // 这里不要急着给用户提示建议再用 hasPermission 逐个确认 } }, function (error) { console.error(请求权限失败, error); });这里有个很容易踩的坑requestPermissions的 success 回调不代表用户一定点了“允许”它只表示这次请求流程走完了。如果用户拒绝了其中一个权限这个方法也可能进入 success 回调但status.hasPermission变成了 false。所以你在回调里不能只看有没有走到成功还要看状态值。我的习惯是请求结束之后再针对关键权限调用一次hasPermission以二次确认的结果为准。因为系统权限状态可能因为权限组、系统版本、用户操作而变得比想象中复杂多一次确认能少很多误判。2.3 权限名称到底怎么写这个插件接收的权限名是 Android 系统里 Manifest 的标准名称。格式固定为android.permission.XXX。不少人写错过大小写或者忘记android.permission.前缀导致插件报权限无法识别。常用的危险权限整理如下权限组权限名使用场景相机android.permission.CAMERA拍照、扫码、录像存储android.permission.READ_EXTERNAL_STORAGE读取相册、外部文件存储android.permission.WRITE_EXTERNAL_STORAGE保存文件到相册位置android.permission.ACCESS_FINE_LOCATION精确定位位置android.permission.ACCESS_COARSE_LOCATION粗略定位、省电麦克风android.permission.RECORD_AUDIO录音、语音输入电话android.permission.READ_PHONE_STATE读取设备信息、IMEI联系人android.permission.READ_CONTACTS读取通讯录联系人android.permission.WRITE_CONTACTS写通讯录日历android.permission.READ_CALENDAR读取日历日历android.permission.WRITE_CALENDAR写日历短信android.permission.READ_SMS读取短信短信android.permission.SEND_SMS发送短信传感器android.permission.BODY_SENSORS心率等身体传感器通知android.permission.POST_NOTIFICATIONSAndroid 13 通知权限有些权限是不需要动态请求的比如网络权限android.permission.INTERNET这类 normal 权限在安装时自动授予插件里没必要列进去。另外要注意这些权限字符串必须先在 Android 工程里声明。插件本身不会自动帮你把所有权限都加到 AndroidManifest.xml 里。你申请一个没声明过的权限系统可能不会弹窗甚至请求回调会直接异常。2.4 调用时机和回调逻辑权限检查不能放在应用启动时无脑请求所有权限。我见过一个应用一启动就把定位、相机、存储、通讯录全部弹一遍用户直接卸载了。正确做法是“在真正需要时才请求”。你在用户点击某个按钮、进入某个页面、使用某个能力之前先调用hasPermission判断再决定要不要走申请流程。这样用户能理解你为什么要权限授权意愿反而更高。还要注意回调线程。插件的回调默认在 UI 线程执行这也意味着你不能在回调里做耗时操作。比如拿到相机权限后立刻初始化摄像头这是可以的如果要下载模型、拉取配置请把耗时逻辑挪到 Worker 或异步任务里。3. 一次完整的权限检查落地流程从安装到上线前验证3.1 安装插件和工程配置安装插件很简单在项目根目录执行cordova plugin add cordova-plugin-android-permissions装完插件之后别急着写代码。先检查你的工程 targetSdkVersion 是不是大于等于 23。如果目标版本低于 23系统会走老旧的安装时授权逻辑运行时请求就会被忽略。我的做法是把 targetSdkVersion 设置到一个比较新的稳定版本比如当前可以写到 34。在 config.xml 里加上preference nameandroid-targetSdkVersion value34 /权限声明也建议统一维护。如果你直接改platforms/android/app/src/main/AndroidManifest.xml下次重新执行cordova platform add android会被覆盖。更稳妥的做法是在工程根目录的 config.xml 里用 edit-config 合并进去。edit-config fileapp/src/main/AndroidManifest.xml modemerge target/manifest uses-permission android:nameandroid.permission.CAMERA / uses-permission android:nameandroid.permission.READ_EXTERNAL_STORAGE / uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE / /edit-config这样权限声明会随着工程一起重建不会丢失。改完 config.xml 后执行cordova prepare android让设置生效。3.2 自己封装一个权限管理器直接在业务里到处调用 cordova.plugins.permissions 也能跑但代码会很散。我建议封装一个小模块把检查、请求、错误处理都收拢到一个地方。下面是一个 Promise 版本的封装我在实际项目里一直在用。var PermissionManager { _requesting: false, check: function (permission) { return new Promise(function (resolve, reject) { if (!window.cordova || !cordova.plugins || !cordova.plugins.permissions) { return reject(new Error(permissions插件未安装或未初始化)); } cordova.plugins.permissions.hasPermission(permission, function (status) { resolve(status.hasPermission); }, function (error) { reject(error); }); }); }, request: function (permissions) { var list Array.isArray(permissions) ? permissions : [permissions]; var self this; return new Promise(function (resolve, reject) { if (self._requesting) { return reject(new Error(已有权限请求正在进行请勿重复触发)); } self._requesting true; cordova.plugins.permissions.requestPermissions(list, function (status) { self._requesting false; resolve(status); }, function (error) { self._requesting false; reject(error); }); }); } };封装里加了一把简单的锁_requesting。这个细节很有用因为用户手指一快可能连续点两次按钮导致两个权限弹窗一起出现系统行为会很怪。用锁把重复请求拦下来再配合 UI 层按钮置灰体验会好很多。每次请求结束后我还会再调用一次check来确认最终状态而不是直接信任request的返回结果。理由前面讲过系统权限组的授权情况比理论复杂二次确认是最稳妥的。3.3 和业务场景串起来封装好之后业务侧就干净多了。拿扫码功能举例当用户点击“开始扫码”按钮时先检查相机权限再决定是否放行。async function startScan() { try { var granted await PermissionManager.check(android.permission.CAMERA); if (!granted) { await PermissionManager.request(android.permission.CAMERA); granted await PermissionManager.check(android.permission.CAMERA); } if (granted) { initScanner(); } else { showPermissionGuide(需要摄像头权限才能扫码请在系统设置中开启); } } catch (error) { console.error(扫码权限流程异常, error); } }流程看起来很简洁但中间三步形成了一个完整闭环先查再请最后复查。前两步解决首次授权问题最后一步解决用户拒绝后的确认问题。这种写法的好处是无论用户怎么操作最后落入业务逻辑的都是一个确定状态。我还建议每个功能的权限引导做成“独立弹窗”而不是统一在首页提示。因为在具体场景里告诉用户“为什么需要这个权限”比冷冰冰的“请授予权限”有效得多。3.4 验证权限场景的测试矩阵写完代码之后不要只拿一台新手机点一次“允许”就完事。我在测试时至少会覆盖这些场景场景期望表现额外说明首次点击功能系统弹窗弹窗正常出现授权后进入功能注意点击“允许”和“拒绝”两种路径首次拒绝业务降级提示不闪退再次点击功能时是否还会弹窗取决于系统策略拒绝后勾选“不再询问”推荐引导去设置页开启不要再调 requestPermissions系统设置里手动关闭权限下次进入页面检查为 false部分 ROM 需要杀掉进程才生效低版本 Android 6.0 以下检查结果通常为 true安装时已授权高版本 Android 13新增通知、附近设备等权限需要同步更新 Manifest测试矩阵我会贴在项目文档里每次升级 Cordova 或者调整 targetSdkVersion 后重新过一遍能避免很多线上回归。4. 权限检查实战中的高频问题与排查方法4.1 权限弹窗为什么没出现这是群里问得最多的一个问题。权限弹窗是系统弹的不是插件弹的所以没出现基本能归结为以下几种原因。第一Manifest 里没有声明该权限。插件申请一个不存在的权限系统无从弹窗。排查方式很简单看生成出来的 AndroidMainfest.xml 里有没有对应的uses-permission。第二调用太早。deviceready 还没触发JS 侧拿到的插件对象是空的或者原生层还没准备好接收请求。这时候需要检查页面加载顺序把权限调用放在 deviceready 之后。第三targetSdkVersion 过低。如果 targetSdkVersion 小于 23系统会走安装时授权逻辑运行时请求会被忽略。你可以故意打个日志看请求有没有回调但多半不会弹窗。第四用户之前已经拒绝过一次并勾选了“不再询问”。这之后你再调用 requestPermissionAndroid 系统不会弹出任何窗口而是直接把结果置为拒绝。这种情况不能依赖弹窗只能引导用户去系统设置里改。第五一些国产定制 ROM 在电量优化、行为管理里做了拦截。即使代码没问题弹窗也可能不出现。遇到这类设备先换一台系统接近原生的机器验证确认是 ROM 问题还是代码问题。4.2 hasPermission 返回结果和系统实际状态对不上有时候系统设置里明明开着权限hasPermission 却返回 false。我碰到过几种情况。一种原因是权限组联动。Android 权限是按组管理的比如定位组里有粗略定位和精确定位只要组内有一个权限被授予同组其他权限在检查时可能返回已授权但如果你单独检查一个权限名结果可能不符合直觉。简单说别拿单一权限名和系统设置页逐条对比要以业务行为是否正常为准。另一种原因是 ROM 的授权策略和原生 Android 不完全一致。某些品牌手机会默认给一些权限放行或者默认静默拒绝。插件只是把系统状态读出来它本身不会说谎但系统厂商可能给了个“特殊”状态。处理这类问题我的思路是不要让权限检查决定一切。权限只是前置条件真正要用的功能跑不跑得起来才是最终依据。比如权限检查通过但相机黑屏那就是其他问题权限检查不通过但功能正常那这个权限可能根本没必要卡这么死。4.3 用户拒绝一次之后还能不能继续请求Android 系统的交互规则是第一次拒绝后再次请求仍有可能弹窗但如果用户勾选了“不再询问”后续请求就不会弹窗了并且系统会直接返回拒绝。插件本身没有专门判断“是否不再询问”的接口所以你要从结果反向推断。当调用 requestPermission 后系统没有弹窗回调用也没有走到成功状态那大概率就是这个情况。这时候要做的是引导用户去应用设置页手动打开。Cordova 插件生态里也有专门跳设置页的方案底层通常是通过 Intent 调用Settings.ACTION_APPLICATION_DETAILS_SETTINGS。如果你不想引入新插件也可以自己写一小段原生代码封装成 Cordova 插件代码量不大但能解决体验问题。在产品交互上需要给用户明确的说明。不要只说“权限被拒绝”而要告诉他接下来要去哪里点开、怎么找到应用权限开关。文案最好写在页面里比系统弹窗更可控。4.4 Android 高版本和特殊 ROM 的适配清单Android 版本更新得很快权限规则也在不断变化。插件能保证的是把系统请求正常触发但规则适配还得靠你。Android 10 开始后台定位被单独分成一个权限普通定位权限只能管应用在前台时的定位后台定位需要在 Manifest 里单独声明并且系统会单独弹窗询问。Android 11 引入了“包可见性”概念读取其他 App 信息会受限制。如果你的产品需要检测其他应用得在 Manifest 里加queries配置。这个不是运行时权限但同样会影响功能实现。Android 12 开始蓝牙扫描和 Wi-Fi 附近设备扫描不再只依赖位置权限还需要BLUETOOTH_SCAN、BLUETOOTH_CONNECT这类新权限。Android 13 又加入了通知权限POST_NOTIFICATIONS如果你的应用要发通知必须在运行时请求这个权限。这些都可以用同一个插件去请求但前提是 Manifest 里声明了。所以每次升级 targetSdkVersion我都习惯先翻一遍系统权限变更文档然后把新增权限列入测试矩阵。插件不变规则要跟着变。4.5 常见错误信息速查表错误信息原因解决思路cordova.plugins.permissions is not defined插件没装好或者没有在 deviceready 后调用确认插件是否安装重启应用进程hasPermission is not a function插件对象被覆盖或版本太旧检查 window.cordova.plugins 里是否有权限插件请求回调没有任何反应权限已不可弹窗或是 ROM 拦截检查“不再询问”状态去系统设置排查权限名不合法控制台报Unknown permission权限字符串写错或未声明对照系统权限名检查大小写和前缀编译报找不到插件类Cordova 重新 prepare 后原生工程没同步重新执行cordova prepare android这个表不算全但覆盖了我日常遇见的 90% 问题。看到错误先往这几个方向查通常比直接翻源码有效。4.6 权限文案和隐私合规需要注意的事权限检查做的是技术活但产品侧不能忽略。应用商店审核越来越严格如果你的应用在用户还没有理解用途时就弹权限很容易被用户投诉审核阶段也容易被要求补充说明。我建议在首次启动时弹一个隐私说明把即将可能用到的权限和目的列清楚用户同意后再进主界面。真正请求权限时再针对当前场景弹一个轻提示。比如“需要访问摄像头拍摄识别码是否允许”这样用户不是被动接受而是带着上下文做选择。还有一个容易忽视的点不要因为用户拒绝一个非核心权限就强制退出应用。这也是一种常见审核隐患。正确的做法是降级处理或者引导用户手动开启。用户有权拒绝产品也要给拒绝后的退路。5. 实战后的几点真实心得5.1 权限请求要“最小化”和“场景化”如果你能在功能上少用一个权限就绝对不要多申请一个。比如有些扫码应用只需要相机权限却因为要保存二维码图片而申请了存储权限结果用户一看要读写相册就不敢授权了。后来我们改成了直接返回图片数据、不落盘存储权限就完全不需要了审核也顺利了不少。我见过一个项目启动时一口气请求了八个权限用户授权率反而很低。改成进入具体模块时逐个申请后功能使用率明显回升。权限请求不是越多越好而是越精准越好。5.2 权限状态不能只信一次判断权限状态会在使用过程中随时变化用户可以随时去系统设置里关掉某个权限。所以不能只在启动时检查一次之后全程都不管。我的习惯是在关键的、会真正使用敏感能力的操作入口处都保留一次实际检查。比如进入扫码页时检查相机点击定位时检查定位录音前检查麦克风。这样即使权限中途被关了也能第一时间发现并引导用户而不是让用户卡在一个“看起来点了按钮但没反应”的尴尬状态。另外写完权限逻辑后建议多找几台不同厂商、不同 Android 版本的机器测试。原生系统上跑通不算数国产 ROM 的权限管理太野只有实测过心里才踏实。最后再分享一个小技巧调试权限问题时不要只盯着控制台日志在系统设置里不断切换权限开关然后回到应用观察变化往往能比看日志更快定位问题。权限这玩意涉及系统和用户两条线代码写对只是第一步用户真正拿到权限才是最终目标。