HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(三十三):权限管理——用一套“安检系统”告别散装代码

HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(三十三):权限管理------用一套"安检系统"告别散装代码

摘要 :从第1篇到第30篇,《灵犀厨房》已经集成了相机(拍照识别食材)、麦克风(声控指令)、通知(烹饪提醒)等系统能力。每一处都需要运行时权限------但之前的写法就像是每家每户自己挖井RecipeDetailPage 里写一段权限检查,IngredientCamera 里又写一段几乎一模一样的代码......重复、散乱、改一处漏三处。今天,我们新建一个 PermissionHelper 工具类,把权限检查、请求、引导设置三项操作封装成三个静态方法,用一套"统一安检系统"管住所有敏感权限。从此,你的代码行数减少75%,维护成本趋近于零。


一、HarmonyOS 权限模型:门禁卡的三个等级

想象你走进一栋智能大厦:

  • normal 权限 :就像大厦的旋转门 ------任何人都可以自由通过(例如访问网络INTERNET),安装 App 时自动授权,你甚至感觉不到它的存在。

  • system_basic 权限 :像进入办公区 的门禁------你需要刷一下工牌,系统会弹出一个对话框问"是否允许进入"。CAMERA(相机)和 MICROPHONE(麦克风)就属于这一类。用户第一次使用时,系统弹出授权提示。

  • system_core 权限 :像进入数据中心机房------不仅需要刷工牌,还要输入动态验证码,甚至需要管理员后台审批。这类权限(如读取通讯录、精准定位)用户需要手动到系统设置页开启。

CAMERAMICROPHONE 属于 system_basic 级------首次使用时系统弹框询问"允许/拒绝"。如果用户勾选了"拒绝且不再询问",后续调用直接失败,这时我们必须引导用户跳转到系统设置页手动开启。

金句 :所有权限必须在 module.json5 中提前"声明"。声明就像在施工图纸上画好"这里要装一扇门",而运行时请求才是真正"刷卡开门"。两者缺一不可。


二、PermissionHelper 设计:把"散装安检"升级为"统一闸机"

在重构之前,每一处需要权限的页面都重复着这样 12 行"散装代码":

typescript 复制代码
const atManager = abilityAccessCtrl.createAtManager();
const ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
const tokenId = ctx.applicationInfo.accessTokenId;
const granted = atManager.checkAccessTokenSync(tokenId, 'ohos.permission.MICROPHONE')
  === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
if (!granted) {
  const result = await atManager.requestPermissionsFromUser(ctx, ['ohos.permission.MICROPHONE']);
  if (result.authResults[0] !== 0) { /* 用户拒绝,提示跳转设置 */ }
}

每个人都在重复造轮子,而且容易出错:tokenId 取错、回调处理不统一、跳转设置的逻辑五花八门......

现在,我们把这三个核心能力封装成一个静态工具类------就像大厦统一安装的"人脸识别闸机",所有楼层共用同一套系统。

2.1 类图:一图看懂三个方法

#mermaid-svg-OlWggLfqjbPMmpEl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OlWggLfqjbPMmpEl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OlWggLfqjbPMmpEl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OlWggLfqjbPMmpEl .error-icon{fill:#552222;}#mermaid-svg-OlWggLfqjbPMmpEl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OlWggLfqjbPMmpEl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OlWggLfqjbPMmpEl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OlWggLfqjbPMmpEl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OlWggLfqjbPMmpEl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OlWggLfqjbPMmpEl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OlWggLfqjbPMmpEl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OlWggLfqjbPMmpEl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OlWggLfqjbPMmpEl .marker.cross{stroke:#333333;}#mermaid-svg-OlWggLfqjbPMmpEl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OlWggLfqjbPMmpEl p{margin:0;}#mermaid-svg-OlWggLfqjbPMmpEl g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-OlWggLfqjbPMmpEl g.classGroup text .title{font-weight:bolder;}#mermaid-svg-OlWggLfqjbPMmpEl .cluster-label text{fill:#333;}#mermaid-svg-OlWggLfqjbPMmpEl .cluster-label span{color:#333;}#mermaid-svg-OlWggLfqjbPMmpEl .cluster-label span p{background-color:transparent;}#mermaid-svg-OlWggLfqjbPMmpEl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OlWggLfqjbPMmpEl .cluster text{fill:#333;}#mermaid-svg-OlWggLfqjbPMmpEl .cluster span{color:#333;}#mermaid-svg-OlWggLfqjbPMmpEl .nodeLabel,#mermaid-svg-OlWggLfqjbPMmpEl .edgeLabel{color:#131300;}#mermaid-svg-OlWggLfqjbPMmpEl .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-OlWggLfqjbPMmpEl .label text{fill:#131300;}#mermaid-svg-OlWggLfqjbPMmpEl .labelBkg{background:#ECECFF;}#mermaid-svg-OlWggLfqjbPMmpEl .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-OlWggLfqjbPMmpEl .classTitle{font-weight:bolder;}#mermaid-svg-OlWggLfqjbPMmpEl .node rect,#mermaid-svg-OlWggLfqjbPMmpEl .node circle,#mermaid-svg-OlWggLfqjbPMmpEl .node ellipse,#mermaid-svg-OlWggLfqjbPMmpEl .node polygon,#mermaid-svg-OlWggLfqjbPMmpEl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OlWggLfqjbPMmpEl .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-OlWggLfqjbPMmpEl g.clickable{cursor:pointer;}#mermaid-svg-OlWggLfqjbPMmpEl g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-OlWggLfqjbPMmpEl g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-OlWggLfqjbPMmpEl .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-OlWggLfqjbPMmpEl .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-OlWggLfqjbPMmpEl .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-OlWggLfqjbPMmpEl .dashed-line{stroke-dasharray:3;}#mermaid-svg-OlWggLfqjbPMmpEl .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-OlWggLfqjbPMmpEl #compositionStart,#mermaid-svg-OlWggLfqjbPMmpEl .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-OlWggLfqjbPMmpEl #compositionEnd,#mermaid-svg-OlWggLfqjbPMmpEl .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-OlWggLfqjbPMmpEl #dependencyStart,#mermaid-svg-OlWggLfqjbPMmpEl .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-OlWggLfqjbPMmpEl #dependencyStart,#mermaid-svg-OlWggLfqjbPMmpEl .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-OlWggLfqjbPMmpEl #extensionStart,#mermaid-svg-OlWggLfqjbPMmpEl .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-OlWggLfqjbPMmpEl #extensionEnd,#mermaid-svg-OlWggLfqjbPMmpEl .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-OlWggLfqjbPMmpEl #aggregationStart,#mermaid-svg-OlWggLfqjbPMmpEl .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-OlWggLfqjbPMmpEl #aggregationEnd,#mermaid-svg-OlWggLfqjbPMmpEl .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-OlWggLfqjbPMmpEl #lollipopStart,#mermaid-svg-OlWggLfqjbPMmpEl .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-OlWggLfqjbPMmpEl #lollipopEnd,#mermaid-svg-OlWggLfqjbPMmpEl .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-OlWggLfqjbPMmpEl .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-OlWggLfqjbPMmpEl .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-OlWggLfqjbPMmpEl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-OlWggLfqjbPMmpEl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-OlWggLfqjbPMmpEl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} <<static>>
PermissionHelper
+isGranted(context, permission) : boolean
+checkAndRequest(context, permission) : Promise<boolean>
+goToSettings(context) : void
三个静态方法,无需实例化
同步检查权限状态,即插即用
检查→请求→返回结果,适合按钮点击场景
跳转系统设置页,供用户手动开启

2.2 isGranted ------ 同步"查门禁"

typescript 复制代码
static isGranted(context: common.UIAbilityContext, permission: string): boolean {
  const atManager = abilityAccessCtrl.createAtManager();
  return atManager.checkAccessTokenSync(
    context.applicationInfo.accessTokenId,
    permission
  ) === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
}

为什么用同步 API?

权限状态本质是系统 token 中的一个标记,读取它就像看一眼门禁灯------绿灯/红灯,瞬间就知道。用同步方法可以避免不必要的异步等待,尤其适合在页面初始化时快速判断(例如决定是否显示"请授权相机"提示条)。

2.3 checkAndRequest ------ 异步"请求放行"

typescript 复制代码
static async checkAndRequest(
  context: common.UIAbilityContext,
  permission: string
): Promise<boolean> {
  // 先检查:已有权限直接放行
  if (PermissionHelper.isGranted(context, permission)) return true;

  // 未授权:弹出系统对话框请求
  const atManager = abilityAccessCtrl.createAtManager();
  try {
    const result = await atManager.requestPermissionsFromUser(context, [permission]);
    // authResults[0] === 0 表示用户点击"允许"
    return result.authResults.length > 0 && result.authResults[0] === 0;
  } catch (err) {
    console.error('[PermissionHelper] 请求权限失败:', JSON.stringify(err));
    return false;
  }
}

这个方法像一个智能接待员 :先看你是不是已经有门禁卡(isGranted),有就直接放行;没有就弹出系统对话框请你刷脸(requestPermissionsFromUser),然后告诉你"放行"还是"拒绝"。

下面的流程图直观展示了整个过程:
#mermaid-svg-mhLqXHm3hittVnc2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mhLqXHm3hittVnc2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mhLqXHm3hittVnc2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mhLqXHm3hittVnc2 .error-icon{fill:#552222;}#mermaid-svg-mhLqXHm3hittVnc2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mhLqXHm3hittVnc2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mhLqXHm3hittVnc2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mhLqXHm3hittVnc2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mhLqXHm3hittVnc2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mhLqXHm3hittVnc2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mhLqXHm3hittVnc2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mhLqXHm3hittVnc2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mhLqXHm3hittVnc2 .marker.cross{stroke:#333333;}#mermaid-svg-mhLqXHm3hittVnc2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mhLqXHm3hittVnc2 p{margin:0;}#mermaid-svg-mhLqXHm3hittVnc2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mhLqXHm3hittVnc2 .cluster-label text{fill:#333;}#mermaid-svg-mhLqXHm3hittVnc2 .cluster-label span{color:#333;}#mermaid-svg-mhLqXHm3hittVnc2 .cluster-label span p{background-color:transparent;}#mermaid-svg-mhLqXHm3hittVnc2 .label text,#mermaid-svg-mhLqXHm3hittVnc2 span{fill:#333;color:#333;}#mermaid-svg-mhLqXHm3hittVnc2 .node rect,#mermaid-svg-mhLqXHm3hittVnc2 .node circle,#mermaid-svg-mhLqXHm3hittVnc2 .node ellipse,#mermaid-svg-mhLqXHm3hittVnc2 .node polygon,#mermaid-svg-mhLqXHm3hittVnc2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mhLqXHm3hittVnc2 .rough-node .label text,#mermaid-svg-mhLqXHm3hittVnc2 .node .label text,#mermaid-svg-mhLqXHm3hittVnc2 .image-shape .label,#mermaid-svg-mhLqXHm3hittVnc2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-mhLqXHm3hittVnc2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mhLqXHm3hittVnc2 .rough-node .label,#mermaid-svg-mhLqXHm3hittVnc2 .node .label,#mermaid-svg-mhLqXHm3hittVnc2 .image-shape .label,#mermaid-svg-mhLqXHm3hittVnc2 .icon-shape .label{text-align:center;}#mermaid-svg-mhLqXHm3hittVnc2 .node.clickable{cursor:pointer;}#mermaid-svg-mhLqXHm3hittVnc2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mhLqXHm3hittVnc2 .arrowheadPath{fill:#333333;}#mermaid-svg-mhLqXHm3hittVnc2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mhLqXHm3hittVnc2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mhLqXHm3hittVnc2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mhLqXHm3hittVnc2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mhLqXHm3hittVnc2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mhLqXHm3hittVnc2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mhLqXHm3hittVnc2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mhLqXHm3hittVnc2 .cluster text{fill:#333;}#mermaid-svg-mhLqXHm3hittVnc2 .cluster span{color:#333;}#mermaid-svg-mhLqXHm3hittVnc2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mhLqXHm3hittVnc2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mhLqXHm3hittVnc2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-mhLqXHm3hittVnc2 .icon-shape,#mermaid-svg-mhLqXHm3hittVnc2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mhLqXHm3hittVnc2 .icon-shape p,#mermaid-svg-mhLqXHm3hittVnc2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mhLqXHm3hittVnc2 .icon-shape .label rect,#mermaid-svg-mhLqXHm3hittVnc2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mhLqXHm3hittVnc2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mhLqXHm3hittVnc2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mhLqXHm3hittVnc2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ✅ 已授权
❌ 未授权
允许
拒绝
拒绝且不再询问
调用 checkAndRequest(ctx, 'ohos.permission.CAMERA')
isGranted?
return true
requestPermissionsFromUser()

弹出系统对话框
用户选择
return true
return false
下次 isGranted 仍为 false
调用者判断:引导用户 goToSettings()

2.4 goToSettings ------ 跳转"后勤部"

如果用户曾经拒绝且勾选了"不再询问",后续 checkAndRequest 将直接返回 false,系统不会再弹框。此时我们需要引导用户去系统设置页手动开启权限------就像门禁坏了,你得去物业办公室补办一张卡。

typescript 复制代码
static goToSettings(context: common.UIAbilityContext): void {
  try {
    const want: 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篇食材识别场景)

改造前 ------ 重复、冗余、容易遗漏引导逻辑:

typescript 复制代码
const atManager = abilityAccessCtrl.createAtManager();
const ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
const tokenId = ctx.applicationInfo.accessTokenId;
const granted = atManager.checkAccessTokenSync(tokenId, 'ohos.permission.CAMERA')
  === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
if (!granted) {
  const result = await atManager.requestPermissionsFromUser(ctx, ['ohos.permission.CAMERA']);
  if (result.authResults[0] !== 0) {
    promptAction.showToast({ message: '需要相机权限才能拍照' });
    return;  // 往往忘了引导去设置页
  }
}
// 12 行,而且丢失了引导逻辑

改造后 ------ 3 行搞定,语义清晰:

typescript 复制代码
const ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
const granted = await PermissionHelper.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 学习内容!如果觉得有帮助,请不要吝啬你的点赞 👍、收藏 ⭐ 和评论 💬!

纯血鸿蒙,用心造厨。我们下一篇见!

相关推荐
若兰幽竹1 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(三十二):【数据一致性】个人档案的“三重持久化”修复——让偏好、健康与头像真正同步
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹2 天前
HarmonyOS 6.1 开发者盛宴|《灵犀厨房》实战(三十):【社区分享】本地社区功能——让菜谱从“独享”走向“共享”
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹2 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十九):【偏好持久化】偏好设置与推荐引擎联动——让 App 越用越“懂你”
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹5 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十七):告别 UI 冻结——使用 TaskPool 实现高性能并发图像分析
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹5 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十八):【数据持久化】收藏与浏览历史——让数据在 App 重启后依然“活着”
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹6 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十六):【响应式布局】折叠屏与平板完美适配——一套代码,多端呈现
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹7 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(二十五):【深色模式】一键切换暗色主题——让 App 在深夜也温柔
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹8 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(二十二) | 多媒体 | AVPlayer嵌入教学视频——让智慧屏真正“活”起来
音视频·华为鸿蒙系统·harmonyos6.1.0·灵犀厨房·harmonyos6.1
若兰幽竹8 天前
HarmonyOS 6.1 开发者盛宴|《灵犀厨房》实战(二十三):【交互动效】转场、列表动画与趣味反馈——让每一次点击都有温度
交互·华为鸿蒙系统·harmonyos6.1