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 行"散装代码":
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 做一次深度体检。不见不散!
📦 获取基线版本源码包 :包括第1-15篇所有代码 + 架构文档 + Flask 后端
**如果你发现本文还有任何不严谨之处,欢迎随时指出,我们一起共建最优质的 HarmonyOS 6.1 学习内容!如果觉得有帮助,请不要吝啬你的点赞 👍、收藏 ⭐ 和评论 💬!
纯血鸿蒙,用心造厨。我们下一篇见!