当前位置: 首页 > news >正文

UniApp权限管理别再写if-else了!封装一个Promise版checkPermission函数(附完整安卓权限表)

UniApp权限管理的工程化实践:从Promise封装到完整解决方案

在移动应用开发中,权限管理一直是开发者必须面对的挑战。UniApp作为跨平台开发框架,虽然简化了多端适配的复杂度,但在权限处理上依然存在诸多痛点。传统if-else嵌套的回调方式不仅让代码难以维护,还容易造成逻辑混乱。本文将带你探索一种更优雅的解决方案——基于Promise的权限管理封装,同时提供完整的安卓权限参考表,助你构建更健壮的应用权限体系。

1. 为什么需要重构权限管理

大多数UniApp开发者在初次接触权限管理时,往往会采用最直接的方式:在需要权限的API调用处,通过回调函数处理授权结果。这种模式虽然简单,但随着业务复杂度提升,很快会暴露出几个典型问题:

  • 代码重复:相同的权限检查逻辑散落在各个业务模块中
  • 嵌套回调:多重权限检查导致"回调地狱",降低代码可读性
  • 缺乏统一处理:权限拒绝后的引导逻辑不一致,用户体验割裂
  • 维护困难:权限变更时需要修改多处代码,容易遗漏
// 传统方式示例 uni.getSetting({ success(res) { if (!res.authSetting['scope.userLocation']) { uni.authorize({ scope: 'scope.userLocation', success() { // 实际业务逻辑 }, fail() { uni.showModal({ content: '需要位置权限才能使用此功能' }) } }) } } })

Promise化的权限管理可以完美解决这些问题。通过将权限检查抽象为独立的服务,我们能够实现:

  • 单一职责:权限检查逻辑集中管理
  • 链式调用:避免回调嵌套,代码更扁平
  • 统一异常处理:标准化权限拒绝流程
  • 易于扩展:支持多权限批量检查

2. Promise化权限检查的核心实现

让我们从零开始构建一个健壮的权限检查工具。这个实现不仅包含基本的授权检查,还整合了权限引导、错误处理等实用功能。

2.1 基础Promise封装

首先创建一个permission.js工具文件,实现最基础的Promise封装:

// @/utils/permission.js export function checkPermission(permissions, options = {}) { return new Promise((resolve, reject) => { if (!Array.isArray(permissions)) { permissions = [permissions] } plus.android.requestPermissions( permissions, (result) => { if (result.deniedAlways && result.deniedAlways.length > 0) { // 用户选择了"拒绝且不再询问" handlePermissionReject(result.deniedAlways, options, reject) } else if (result.denied && result.denied.length > 0) { // 用户暂时拒绝 handlePermissionReject(result.denied, options, reject) } else { // 全部授权通过 resolve(true) } } ) }) } function handlePermissionReject(deniedPermissions, options, reject) { const { guideMessage = '需要以下权限才能继续使用此功能' } = options uni.showModal({ title: '权限提示', content: guideMessage, confirmText: '去设置', success(res) { if (res.confirm) { openAppSettings() } reject(new PermissionError(deniedPermissions)) } }) }

2.2 高级功能扩展

基础版本已经可用,但我们还可以添加更多实用功能:

多权限批量检查

// 支持同时检查多个权限 checkPermission([ 'android.permission.CAMERA', 'android.permission.RECORD_AUDIO' ]).then(() => { // 所有权限都已授权 }).catch((err) => { // 处理被拒绝的权限 console.error('被拒绝的权限:', err.deniedPermissions) })

自定义引导信息

// 为不同权限设置不同的引导信息 const permissionGuideMap = { 'android.permission.CAMERA': '需要相机权限进行扫码和拍照', 'android.permission.RECORD_AUDIO': '需要麦克风权限进行语音输入' } checkPermission('android.permission.CAMERA', { guideMessage: permissionGuideMap['android.permission.CAMERA'] })

权限状态缓存

// 添加简单的内存缓存,避免频繁弹窗 const permissionCache = new Map() export function checkPermissionWithCache(permission, options) { const cacheKey = Array.isArray(permission) ? permission.join('|') : permission if (permissionCache.has(cacheKey)) { return Promise.resolve(permissionCache.get(cacheKey)) } return checkPermission(permission, options).then((result) => { permissionCache.set(cacheKey, result) return result }) }

2.3 完整的工具类实现

将上述功能整合,我们得到一个功能完善的权限工具类:

// @/utils/permission.js class PermissionError extends Error { constructor(deniedPermissions) { super('权限被拒绝') this.deniedPermissions = deniedPermissions this.name = 'PermissionError' } } const permissionCache = new Map() export default { /** * 检查权限(Promise版) * @param {string|string[]} permissions 需要检查的权限 * @param {object} options 配置选项 * @param {string} options.guideMessage 权限引导提示信息 * @param {boolean} options.forceCheck 是否强制检查(忽略缓存) * @returns {Promise<boolean>} */ check(permissions, options = {}) { if (!Array.isArray(permissions)) { permissions = [permissions] } const cacheKey = permissions.join('|') // 检查缓存 if (!options.forceCheck && permissionCache.has(cacheKey)) { return Promise.resolve(permissionCache.get(cacheKey)) } return new Promise((resolve, reject) => { plus.android.requestPermissions( permissions, (result) => { if (result.deniedAlways && result.deniedAlways.length > 0) { handleReject(result.deniedAlways, options, reject) } else if (result.denied && result.denied.length > 0) { handleReject(result.denied, options, reject) } else { permissionCache.set(cacheKey, true) resolve(true) } } ) }) }, /** * 清除权限缓存 * @param {string|string[]} [permissions] 指定要清除的权限,不传则清除所有 */ clearCache(permissions) { if (!permissions) { permissionCache.clear() return } if (!Array.isArray(permissions)) { permissions = [permissions] } const cacheKey = permissions.join('|') permissionCache.delete(cacheKey) } } function handleReject(deniedPermissions, options, reject) { const { guideMessage = '需要以下权限才能继续使用此功能' } = options uni.showModal({ title: '权限提示', content: `${guideMessage}: \n${deniedPermissions.join('\n')}`, confirmText: '去设置', success(res) { if (res.confirm) { openAppSettings() } reject(new PermissionError(deniedPermissions)) } }) } function openAppSettings() { const Intent = plus.android.importClass('android.content.Intent') const Settings = plus.android.importClass('android.provider.Settings') const Uri = plus.android.importClass('android.net.Uri') const mainActivity = plus.android.runtimeMainActivity() const intent = new Intent() intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) const uri = Uri.fromParts('package', mainActivity.getPackageName(), null) intent.setData(uri) mainActivity.startActivity(intent) }

3. 实际应用场景与最佳实践

有了强大的权限工具,我们来看看如何在真实项目中应用它。

3.1 基础使用示例

单个权限检查

import permission from '@/utils/permission' permission.check('android.permission.CAMERA', { guideMessage: '需要相机权限才能使用扫码功能' }).then(() => { // 调用扫码API uni.scanCode({ /* ... */ }) }).catch((err) => { console.error('权限被拒绝:', err.deniedPermissions) })

多个权限检查

// 同时检查相机和存储权限 permission.check([ 'android.permission.CAMERA', 'android.permission.READ_EXTERNAL_STORAGE' ], { guideMessage: '需要相机和存储权限才能使用拍照上传功能' }).then(() => { // 执行需要权限的操作 }).catch((err) => { if (err instanceof PermissionError) { // 处理特定权限错误 } })

3.2 与页面生命周期结合

在页面生命周期中合理使用权限检查可以提升用户体验:

// 页面加载时检查必要权限 onLoad() { this.checkRequiredPermissions() }, methods: { async checkRequiredPermissions() { try { await permission.check([ 'android.permission.ACCESS_FINE_LOCATION', 'android.permission.ACCESS_COARSE_LOCATION' ], { guideMessage: '需要位置权限才能获取周边信息' }) // 权限已授权,获取位置 this.getLocation() } catch (err) { // 可选的降级处理 this.showDegradedUI() } }, getLocation() { uni.getLocation({ type: 'gcj02', success: (res) => { // 处理位置信息 } }) }, showDegradedUI() { // 显示无权限状态下的UI } }

3.3 高级组合技巧

权限检查与API调用组合

// 创建一个需要权限的API调用高阶函数 function createPermissionedApi(apiFunc, requiredPermissions, options) { return async function(...args) { try { await permission.check(requiredPermissions, options) return apiFunc(...args) } catch (err) { if (err instanceof PermissionError) { // 可以选择在这里统一处理权限错误 throw new Error(`API调用失败: 缺少必要权限`) } throw err } } } // 使用示例 const scanCodeWithPermission = createPermissionedApi( uni.scanCode, ['android.permission.CAMERA'], { guideMessage: '需要相机权限才能扫码' } ) // 在业务代码中直接调用 scanCodeWithPermission({ success(res) { console.log(res.result) } })

Vue指令封装

// 注册一个权限指令 Vue.directive('permission', { inserted(el, binding) { const { value } = binding if (!value) return permission.check(value.permissions, value.options).catch(() => { // 权限未授权时禁用元素 el.style.opacity = '0.5' el.style.pointerEvents = 'none' // 添加点击事件引导用户授权 el.addEventListener('click', () => { permission.check(value.permissions, { ...value.options, forceCheck: true }).then(() => { // 权限通过后恢复元素 el.style.opacity = '1' el.style.pointerEvents = 'auto' }) }) }) } }) // 使用示例 <button v-permission="{ permissions: 'android.permission.CAMERA', options: { guideMessage: '需要相机权限才能使用此按钮' } }">扫码</button>

4. 安卓权限参考大全

为了帮助开发者更好地管理权限,以下是按功能分类的安卓权限参考表。在实际开发中,应根据最小权限原则,只申请必要的权限。

4.1 常用权限分类

分类权限说明
相机android.permission.CAMERA访问摄像头进行拍照或录像
android.permission.RECORD_AUDIO录制音频,通常与相机权限一起使用
位置android.permission.ACCESS_FINE_LOCATION通过GPS获取精确位置
android.permission.ACCESS_COARSE_LOCATION通过网络获取粗略位置
存储android.permission.READ_EXTERNAL_STORAGE读取外部存储
android.permission.WRITE_EXTERNAL_STORAGE写入外部存储
通讯录android.permission.READ_CONTACTS读取联系人
android.permission.WRITE_CONTACTS写入联系人
电话android.permission.READ_PHONE_STATE读取电话状态
android.permission.CALL_PHONE直接拨打电话
日历android.permission.READ_CALENDAR读取日历事件
android.permission.WRITE_CALENDAR写入日历事件
传感器android.permission.BODY_SENSORS访问身体传感器数据
短信android.permission.READ_SMS读取短信
android.permission.SEND_SMS发送短信

4.2 危险权限列表

安卓将权限分为普通权限和危险权限,危险权限需要运行时申请。以下是完整的危险权限列表:

  • 日历

    • READ_CALENDAR
    • WRITE_CALENDAR
  • 相机

    • CAMERA
  • 联系人

    • READ_CONTACTS
    • WRITE_CONTACTS
    • GET_ACCOUNTS
  • 位置

    • ACCESS_FINE_LOCATION
    • ACCESS_COARSE_LOCATION
  • 麦克风

    • RECORD_AUDIO
  • 电话

    • READ_PHONE_STATE
    • CALL_PHONE
    • READ_CALL_LOG
    • WRITE_CALL_LOG
    • ADD_VOICEMAIL
    • USE_SIP
    • PROCESS_OUTGOING_CALLS
  • 传感器

    • BODY_SENSORS
  • 短信

    • SEND_SMS
    • RECEIVE_SMS
    • READ_SMS
    • RECEIVE_WAP_PUSH
    • RECEIVE_MMS
  • 存储

    • READ_EXTERNAL_STORAGE
    • WRITE_EXTERNAL_STORAGE

4.3 权限使用建议

  1. 最小权限原则:只申请应用必须的权限,多余的权限会增加用户疑虑
  2. 适时申请:在真正需要使用功能时再申请权限,不要一开始就申请所有权限
  3. 解释用途:在申请权限前,用简单明了的语言解释为什么需要这个权限
  4. 优雅降级:当权限被拒绝时,应用应该有相应的降级方案,而不是完全无法使用
  5. 测试各种场景
    • 用户首次拒绝
    • 用户选择"拒绝且不再询问"
    • 权限被系统自动拒绝(如电池优化)
    • 从设置中手动关闭权限
// 权限检查的完整流程示例 async function requestCameraPermission() { try { await permission.check('android.permission.CAMERA', { guideMessage: '需要相机权限才能使用扫码功能' }) // 权限已授予,执行操作 startScan() } catch (err) { if (err instanceof PermissionError) { // 处理权限被拒绝的情况 if (err.deniedPermissions.includes('android.permission.CAMERA')) { showAlternativeOption() } } else { // 处理其他错误 console.error('未知错误:', err) } } }

在实际项目中,我发现将权限管理封装为独立服务后,不仅代码更清晰,而且权限相关的变更只需在一处修改。特别是当应用需要适配不同地区的权限要求时,这种集中管理的优势更加明显。例如,某些地区可能对相机权限有更严格的要求,我们可以在权限工具中轻松添加地区适配逻辑,而不需要修改各个业务模块。

http://www.jsqmd.com/news/750296/

相关文章:

  • TranslucentTB Windows 11更新后无法启动的完整修复指南:从诊断到彻底解决
  • 终极Windows与Office激活解决方案:KMS智能激活工具完全指南
  • HSPICE仿真结果导出全攻略:从.print到.probe,手把手教你生成波形与数据报告
  • D3KeyHelper:暗黑3玩家的智能按键助手完全指南
  • Copaw:轻量级HTTP(S)内网穿透工具的原理、部署与实战
  • ESP32-S3能源计量模块与智能家居电力监控
  • 别再让模型‘乱跑’了:用XGBoost的单调性约束,让业务规则稳稳落地
  • 3个步骤为Windows创建无限虚拟显示器:ParsecVDisplay完全指南
  • OpenCore Legacy Patcher终极指南:4步让旧Mac焕发新生
  • 告别Mask R-CNN的繁琐,用SOLO实例分割5分钟搞定你的目标抠图需求
  • 创业团队如何利用 Taotoken 统一管理多个 AI 应用项目的 API 调用与成本
  • AI对话式GTM管理:用自然语言配置Google Tag Manager标签与转化跟踪
  • 告别反转!用Simulink手把手复现永磁同步电机脉冲注入法初始位置辨识(附模型下载)
  • Piclaw:开箱即用的本地AI工作空间,集成开发与智能协作
  • 新手开发者五分钟内完成TaotokenAPIKey配置与第一个请求
  • 互联网大厂 Java 求职者面试:深入探讨微服务与云原生
  • 九大网盘直链解析神器:告别限速,开启高效下载新时代
  • KMS_VL_ALL_AIO:Windows与Office批量激活的智能化架构解析
  • 中国农业大学考研辅导班推荐:排名深度评测与哪家好选择 - michalwang
  • 别再乱用create_clock了!聊聊DC/PT里时钟约束的那些‘坑’与实战避坑指南
  • 避免踩坑!杉德斯玛特卡回收注意事项及常见问题全面解析 - 可可收
  • 告别网盘限速:8大平台直链解析神器完全指南,下载速度提升10倍!
  • 用zotero-better-notes打造你的文献知识库:主笔记+模板实战教程
  • 终极英雄联盟换肤解决方案:R3nzSkin国服特供版完整指南
  • PADS Layout新手避坑:板框导圆角和斜角的完整操作流程(附选项设置详解)
  • 为什么你的网络总是不稳定?3个简单方法彻底解决连接问题
  • AI智能体技能栈构建:基于Claw/Hermes框架与Telegram Bot的工程实践
  • GitHub开源项目日报 · 2026年5月2日 · 多智能体AI项目引领技术热潮
  • 告别手动破解:实测4n6.VBA Password Remover在Win11上批量处理Office宏文件的效率
  • 从‘盲选’到‘精筛’:聊聊RPN如何取代传统Selective Search,并彻底改变了目标检测的玩法