HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(三十三):权限管理——用一套“安检系统”告别散装代码
摘要:从第1篇到第30篇,《灵犀厨房》已经集成了相机(拍照识别食材)、麦克风(声控指令)、通知(烹饪提醒)等系统能力。每一处都需要运行时权限——但之前的写法就像是每家每户自己挖井:
RecipeDetailPage里写一段权限检查,IngredientCamera里又写一段几乎一模一样的代码……重复、散乱、改一处漏三处。今天,我们新建一个PermissionHelper工具类,把权限检查、请求、引导设置三项操作封装成三个静态方法,用一套“统一安检系统”管住所有敏感权限。从此,你的代码行数减少75%,维护成本趋近于零。
一、HarmonyOS 权限模型:门禁卡的三个等级
想象你走进一栋智能大厦:
normal 权限:就像大厦的旋转门——任何人都可以自由通过(例如访问网络
INTERNET),安装 App 时自动授权,你甚至感觉不到它的存在。system_basic 权限:像进入办公区的门禁——你需要刷一下工牌,系统会弹出一个对话框问“是否允许进入”。
CAMERA(相机)和MICROPHONE(麦克风)就属于这一类。用户第一次使用时,系统弹出授权提示。system_core 权限:像进入数据中心机房——不仅需要刷工牌,还要输入动态验证码,甚至需要管理员后台审批。这类权限(如读取通讯录、精准定位)用户需要手动到系统设置页开启。
CAMERA和MICROPHONE属于system_basic级——首次使用时系统弹框询问“允许/拒绝”。如果用户勾选了“拒绝且不再询问”,后续调用直接失败,这时我们必须引导用户跳转到系统设置页手动开启。
金句:所有权限必须在
module.json5中提前“声明”。声明就像在施工图纸上画好“这里要装一扇门”,而运行时请求才是真正“刷卡开门”。两者缺一不可。
二、PermissionHelper 设计:把“散装安检”升级为“统一闸机”
在重构之前,每一处需要权限的页面都重复着这样 12 行“散装代码”:
constatManager=abilityAccessCtrl.createAtManager();constctx=this.getUIContext().getHostContext()ascommon.UIAbilityContext;consttokenId=ctx.applicationInfo.accessTokenId;constgranted=atManager.checkAccessTokenSync(tokenId,'ohos.permission.MICROPHONE')===abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;if(!granted){constresult=awaitatManager.requestPermissionsFromUser(ctx,['ohos.permission.MICROPHONE']);if(result.authResults[0]!==0){/* 用户拒绝,提示跳转设置 */}}每个人都在重复造轮子,而且容易出错:tokenId取错、回调处理不统一、跳转设置的逻辑五花八门……
现在,我们把这三个核心能力封装成一个静态工具类——就像大厦统一安装的“人脸识别闸机”,所有楼层共用同一套系统。
2.1 类图:一图看懂三个方法
2.2 isGranted —— 同步“查门禁”
staticisGranted(context:common.UIAbilityContext,permission:string):boolean{constatManager=abilityAccessCtrl.createAtManager();returnatManager.checkAccessTokenSync(context.applicationInfo.accessTokenId,permission)===abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;}为什么用同步 API?
权限状态本质是系统 token 中的一个标记,读取它就像看一眼门禁灯——绿灯/红灯,瞬间就知道。用同步方法可以避免不必要的异步等待,尤其适合在页面初始化时快速判断(例如决定是否显示“请授权相机”提示条)。
2.3 checkAndRequest —— 异步“请求放行”
staticasynccheckAndRequest(context:common.UIAbilityContext,permission:string):Promise<boolean>{// 先检查:已有权限直接放行if(PermissionHelper.isGranted(context,permission))returntrue;// 未授权:弹出系统对话框请求constatManager=abilityAccessCtrl.createAtManager();try{constresult=awaitatManager.requestPermissionsFromUser(context,[permission]);// authResults[0] === 0 表示用户点击“允许”returnresult.authResults.length>0&&result.authResults[0]===0;}catch(err){console.error('[PermissionHelper] 请求权限失败:',JSON.stringify(err));returnfalse;}}这个方法像一个智能接待员:先看你是不是已经有门禁卡(isGranted),有就直接放行;没有就弹出系统对话框请你刷脸(requestPermissionsFromUser),然后告诉你“放行”还是“拒绝”。
下面的流程图直观展示了整个过程:
2.4 goToSettings —— 跳转“后勤部”
如果用户曾经拒绝且勾选了“不再询问”,后续checkAndRequest将直接返回false,系统不会再弹框。此时我们需要引导用户去系统设置页手动开启权限——就像门禁坏了,你得去物业办公室补办一张卡。
staticgoToSettings(context:common.UIAbilityContext):void{try{constwant:Want={bundleName:'com.huawei.hmos.settings',// 系统设置包名abilityName:'com.huawei.hmos.settings.MainAbility',uri:'application_info_entry',// 跳转到应用详情页parameters:{pushParams:context.applicationInfo.name}};context.startAbility(want);}catch(err){console.error('[PermissionHelper] 跳转设置失败:',JSON.stringify(err));}}设计细节:
goToSettings不抛异常——跳转失败(比如用户正在使用低版本系统)不会导致 App 崩溃,最多是无法打开设置页。静默吞掉错误,让主流程继续。
三、使用示例:从“散装12行”到“精致3行”
3.1 拍照前检查相机权限(第6篇食材识别场景)
改造前—— 重复、冗余、容易遗漏引导逻辑:
constatManager=abilityAccessCtrl.createAtManager();constctx=this.getUIContext().getHostContext()ascommon.UIAbilityContext;consttokenId=ctx.applicationInfo.accessTokenId;constgranted=atManager.checkAccessTokenSync(tokenId,'ohos.permission.CAMERA')===abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;if(!granted){constresult=awaitatManager.requestPermissionsFromUser(ctx,['ohos.permission.CAMERA']);if(result.authResults[0]!==0){promptAction.showToast({message:'需要相机权限才能拍照'});return;// 往往忘了引导去设置页}}// 12 行,而且丢失了引导逻辑改造后—— 3 行搞定,语义清晰:
constctx=this.getUIContext().getHostContext()ascommon.UIAbilityContext;constgranted=awaitPermissionHelper.checkAndRequest(ctx,'ohos.permission.CAMERA');if(!granted){promptAction.showToast({message:'请到设置页开启相机权限'});PermissionHelper.goToSettings(ctx);return;}// 继续拍照逻辑3.2 三种典型使用模式
| 场景 | 代码模板 | 说明 |
|---|---|---|
| 页面初始化判断(决定是否显示引导条) | if (!PermissionHelper.isGranted(ctx, perm)) this.showHint = true; | 同步、无弹框 |
| 按钮点击请求权限(如“开始录音”) | const ok = await PermissionHelper.checkAndRequest(ctx, perm); if (ok) this.startRecord(); | 自动弹框,异步等待 |
| 请求失败后引导 | if (!ok) { PermissionHelper.goToSettings(ctx); } | 跳转系统设置 |
四、代码交付清单
| 文件 | 操作 | 行数变化 | 说明 |
|---|---|---|---|
common/PermissionHelper.ets | 新增 | +50 | 三个静态方法,纯工具类 |
module.json5 | 无需修改 | 0 | 权限声明(相机/麦克风/通知)已在之前篇章添加 |
IngredientCamera.ets | 重构 | -9 | 移除散装权限代码,调用 Helper |
RecipeDetailPage.ets | 重构 | -12 | 麦克风权限检查统一调用 Helper |
净减少代码约 20 行,但更重要的是——未来新增任何需要权限的功能,只需一行await PermissionHelper.checkAndRequest(),维护成本降为 0。
五、设计决策:为什么这么写?
| 决策 | 理由 |
|---|---|
| 全静态方法 | 权限工具无状态,无需new。就像Math.max(),直接调用即可 |
isGranted用同步 API | 权限检查本质是读系统 token,同步返回结果,避免不必要的异步开销 |
goToSettings不抛异常 | 跳转设置失败是“锦上添花”的功能,不应打断主流程。静默失败,仅打日志 |
| 权限声明与请求分离 | module.json5是编译时“备料”,运行时checkAndRequest是“下锅”。各司其职 |
| 不封装多权限同时请求 | 简化设计:一次只处理一个权限。调用方自行循环调用,更清晰,避免“部分成功”的混乱 |
六、总结与下篇预告
今天,我们给《灵犀厨房》装上了一套统一权限管理“安检系统”:
- 用
isGranted快速查门禁(同步) - 用
checkAndRequest智能请求放行(异步 + 自动弹框) - 用
goToSettings跳转后勤部(手动开启)
从此,任何需要相机、麦克风、通知的地方,都只需 3 行代码完成权限检查。代码更干净、逻辑更统一、用户体验更一致。
但这只是性能优化的前奏。下一站,我们将打开 DevEco Studio 的Profiler 工具,像一个“性能侦探”一样分析《灵犀厨房》的 CPU、内存、帧率瓶颈——找出那些让页面卡顿的“真凶”,然后一个个优化掉。敬请期待第 32 篇!
📚 专栏持续更新中:下一期,我们化身“性能侦探”,用 Profiler 给 App 做一次深度体检。不见不散!
🔗专栏入口:[《HarmonyOS6.1全场景实战》合集]
📦 获取基线版本源码包:包括第1-15篇所有代码 + 架构文档 + Flask 后端
**如果你发现本文还有任何不严谨之处,欢迎随时指出,我们一起共建最优质的 HarmonyOS 6.1 学习内容!如果觉得有帮助,请不要吝啬你的点赞 👍、收藏 ⭐ 和评论 💬!
纯血鸿蒙,用心造厨。我们下一篇见!