这段时间几乎每个移动端技术群都在聊“鸿蒙 AI App”。我自己做完一个从零立项到上架的鸿蒙 AI 应用之后,最强烈的感受是:ArkTS、ArkUI 这套开发框架本身没有想象中难,真正麻烦的是怎么把 AI 能力合理嵌进整个应用架构里——既要调得动系统级能力,又要控制端侧推理的功耗,还要保证大模型接口接入后体验流畅。这篇文章不打算挨个讲 API 用法,而是把我在鸿蒙 AI App 技术架构层面的核心决策摊开说,从选型、分层、AI 接入方式,到真机调试踩过的坑,完整过一遍。
如果你是准备做鸿蒙 AI 应用、或者已经写完几个页面想往 AI 方向落地的开发者,这篇文章应该能帮你少走不少弯路。我会尽量按照“为什么这样做”的思路来讲,而不是只列几个步骤。
1. 立项之前:鸿蒙 AI App 的定位和选型判断
1.1 为什么说“AI App”不等于“App + 大模型 API”
很多团队做 AI App 的思路是:找一个大模型 API 接进来,页面堆一个聊天窗口,就算完成 AI 化了。我测试过这类产品,问题非常明显——用户问一句,界面转圈几秒钟,然后吐出一大段通用内容,跟用户当前场景毫无关系。这不是 AI 能力不够,而是架构层面没有把“意图”和“能力”连接起来。
真正的鸿蒙 AI App 应该是一个能够理解用户当前场景、调用合适能力、把结果以低摩擦方式交还给用户的系统。它不只是有一个大模型接口,而是有一个意图处理层:用户说话、拍照、点按钮,系统判断要做什么,再去调度端侧模型、云侧模型、系统能力或本地数据。架构设计的第一步就是先把这件事想清楚。
1.2 HarmonyOS NEXT 为 AI 应用提供了不少系统级条件
在动手画架构图之前,我先梳理了一遍 HarmonyOS NEXT 本身已有的 AI 相关能力,发现它跟 Android、iOS 上做 AI App 的姿势确实不太一样。
- 意图框架是系统级的,应用可以把某些功能声明成意图,其他应用或者系统助手能直接拉起。这意味着 AI 助手的“下一步动作”不一定要在自己 App 内部闭环,可以直接跳转到系统服务或者别的元服务。
- 元服务是鸿蒙特有的轻量形态,适合把 AI 能力做成“即点即用”的原子化入口,不需要用户先下载一个完整 App。
- 端侧 AI 推理有 MindSpore Lite 支撑,训练好的模型可以转成鸿蒙端能跑的格式,离线执行 OCR、分类、嵌入提取这些任务。
- 分布式能力让同一个 AI 服务可以在手机、平板、车机之间流转,比如手机上拍到文档,让平板端继续编辑或做摘要。
这些系统级条件直接影响架构设计:如果目标用户高频使用的功能适合系统意图拉起,就没有必要在应用内部重新造一遍完整的交互链路。
1.3 我先做了哪些场景评估才立项
这不是一个拍脑袋决定的过程。我在立项前把所有候选 AI 场景列了一张表,逐项评估。标准只有三条:
- 用户是不是真的高频、短路径地需要这个能力;
- 是不是端侧实时处理比云侧更合理(隐私、延迟、流量);
- 有没有办法借力元服务或者系统意图,降低用户的使用门槛。
以文档智能处理为例:用户拿手机拍一张表格或者发票,希望立刻得到结构化结果。这个场景路径很短,如果整个识别过程在端侧完成,敏感信息不离开手机,隐私上也能解释。最后我们把核心链路定为“拍摄—端侧 OCR—端侧结构化—AI 摘要(或云侧增强)—导出”。这个决策是后面整个技术架构的源头。
2. 分层架构:Ability、模块边界和一条请求的完整链路
2.1 Stage 模型下的模块拆分:feature、common 与 service
HarmonyOS 应用采用 Stage 模型,我一开始就把模块边界切成三层:feature 层、common 层、service 层。
- feature 层放页面能力和具体业务场景,比如“文档拍摄”“结果编辑”“历史记录”;
- common 层放基础 UI 组件、工具库、网络封装、数据存储封装;
- service 层放 AI 相关服务,比如端侧推理管理、大模型请求通道、意图解析。
分层不只是为了代码整洁。AI App 最怕的是模型能力跟业务页面强耦合,后面换个模型、加个新场景都要伤筋动骨。把 AI 服务全部收敛在 service 层,页面只面向一个统一的接口,替换模型时不用动 UI。
2.2 UIAbility、ExtensionAbility 与后台长时任务怎么选
鸿蒙的页面入口是 UIAbility,但 AI 任务经常需要在后台执行。我踩过的一个典型问题是:用户上传一份十几页文档做摘要,应用退到后台,任务被挂起。
HarmonyOS NEXT 提供了 ExtensionAbility 这类后台任务形态,其中长时任务可以申请在后台继续运行,但前提是任务类型符合系统限制,比如语音播报、导航、后台下载这一类。普通文档摘要不属于系统认可的长时任务类型,强行申请会被系统拒绝。
我的处理方式是把任务拆成两段:端侧能快速完成的部分(比如 OCR 识别)做成前台任务,用户停留在结果页等待;云侧大模型处理的部分走异步通知,任务完成后通过本地通知提醒用户回来查看。这样既符合系统规范,又不会让用户觉得“一锁屏任务就没了”。
2.3 一条完整请求链路拆解:从识别意图到结果上屏
我以“手机拍了一张发票,App 自动提取关键字段并生成摘要”这个功能为例,来说明实际链路:
- 相机或相册触发,图像进入 service 层;
- 端侧模型做 OCR 和版式分析,输出结构化文本;
- 意图解析模块判断用户要的是“字段提取”,而不是“翻译”或“生成周报”;
- 如果是字段提取,直接走本地规则引擎+端侧模型完成;
- 如果涉及摘要、润色等语言生成任务,走云侧大模型;
- 返回结果后,feature 层把结构化字段填进表单,同时展示摘要;
- 生成的内容写入本地历史记录,方便复查。
这条链路有一个关键点:不要把云侧大模型当作所有能力的默认承载。能端侧完成的绝不发到云上。字段提取这种高确定性任务,端侧规则加小模型又快又稳,延迟能控制在几百毫秒内;只有开放生成类任务才值得花一次网络请求的代价。
3. AI 能力接入的三条路线:意图框架、端侧推理与云侧大模型
3.1 系统级能力:意图框架与 HiAI 基础服务
HarmonyOS 提供了一个系统级的意图框架,应用可以对外声明能力。比如把“文档提取”这个动作声明成意图,用户在系统全局搜索里输入“提取发票信息”,系统可以直接拉起我们的元服务;反过来,我们的 App 也能调用系统已经内置的 AI 能力。
HiAI 这类基础服务解决的是“不需要自己训练模型”的问题。文字识别、图像分类、语音识别、文本嵌入这一类通用能力,直接用系统或 HMS 提供的服务接口,比自己部署端侧模型成本低得多。我在早期版本里就把 OCR 识别切换到系统级能力实现,当时的判断是:先把流程跑通,后面出现性能瓶颈再换成自部署模型。这个“先跑通再优化”的思路帮我跳过了一大段早期调模型的阶段。
3.2 端侧推理:MindSpore Lite 与模型转换
如果系统能力满足不了需求,就需要自部署端侧模型。HarmonyOS 上我用的推理框架是 MindSpore Lite。
模型训练通常在 PyTorch 或者其他框架里完成,导出的格式不能直接在鸿蒙端跑,需要先把模型转成 MindSpore Lite 的格式。这一步没有太多玄学,关键是转换工具会自动做算子映射、内存优化和量化。新手最容易忽略的是版本匹配:训练框架版本、转换工具版本、端侧运行时版本必须保持一致,不然经常出现“电脑上转换成功、手机上一跑就崩溃”的问题。
端侧模型推理的典型调用方式大概是下面这样的:
// 示意代码,实际 API 名称以当前版本官方文档为准 const session = new lite.Session(); session.loadModelFromFile(this.modelPath); session.setInputTensor("input", inputBuffer); const outputs = session.predict(); const result = outputs.getTensorData("output");这里有一件事请务必留意:加载模型是一个重操作,不能在每次识别时都重新加载。我在架构里专门维护了一个模型管理单例,进程启动后异步加载模型到内存,所有请求复用同一个推理会话,识别耗时压缩了大概 60%。
3.3 云侧大模型:WebSocket 流式通道与提示词管理
对话生成、摘要生成这一类需要大模型能力的场景,我选择走云侧 API。对于用户体感来说,生成过程必须流式返回,一个字一个字蹦出来,而不是等 5 秒后一次性吐一整段。
实践里我优先用 WebSocket 建立流式通道,而不是普通 HTTP 长连接。鸿蒙的 HTTP 模块对流式响应的支持不如 WebSocket 直接,用 WebSocket 可以天然维护一个持续连接,服务器端增量推送 token,客户端逐帧刷新界面。
// 基于 WebSocket 创建大模型流式通道 const ws = webSocket.createWebSocket(); ws.on("open", () => { ws.send(JSON.stringify({ type: "chat", messages: this.historyMessages, stream: true })); }); ws.on("message", (err, data) => { const chunk = JSON.parse(data); if (chunk.done) { ws.close(); return; } // 增量追加文本并触发 UI 刷新 this.aiResponse += chunk.text; }); ws.on("error", (err) => { // 断线重连或降级为全量请求 this.handleStreamError(err); });提示词管理也是一个需要架构化的东西。不要把一大段提示词硬编码在页面里。我单独维护了一套提示词模板,每个 AI 场景对应一个模板 ID,模板内容支持远程下发。这样后面调整模型回复风格、约束输出格式,不需要发版,服务端改一下模板就好。
3.4 三条路线怎么组合:一张选型对照表
我整理了一张表,方便你根据场景快速判断该走哪条路线。
| 能力需求 | 推荐路线 | 核心理由 | 延迟表现 |
|---|---|---|---|
| OCR、语音转写、图像分类等通用识别 | 系统级能力或 HiAI 服务 | 无需自训练,集成成本低 | 端侧 200ms 以内 |
| 高定制、离线场景、隐私敏感 | MindSpore Lite 端侧推理 | 数据不出设备,支持离线 | 端侧 300ms 以内 |
| 对话、摘要、创作类生成任务 | 云侧大模型 | 模型大、语义理解强,支持流式输出 | 受网络影响,通常 1-3s 首字 |
| 跨应用动作执行、全局搜索直达 | 意图框架+元服务 | 借系统入口,降低使用路径 | 视拉起目标而定 |
实际项目中这三条路线不是互斥的。我最后采用的组合方式是:通用能力走系统级,确定性任务走端侧,开放生成任务走云侧,意图框架负责把能力暴露给系统和用户。
4. 对话与 Agent 功能的工程化:流式输出、工具调用与上下文管理
4.1 流式响应上屏与取消/重试机制
流式输出看起来简单,实际工程上有三个很容易翻车的地方:UI 刷新频率、取消操作、断线重连。UI 刷新如果每收到一个 token 就 setState 一次,界面会卡顿。我做了缓冲,每 30 毫秒做一次批量刷新,把多个 token 合并成一段文本再上屏,视觉上依旧流畅,渲染压力小很多。
取消操作同样需要考虑。如果用户点了停止生成,不能只是 UI 停止刷新,底层 WebSocket 必须主动关闭,服务端才会停止计费生成。这个坑我一开始没注意,用户点了几次停止,后台账单还在涨。
重试机制也要分情况。网络超时,可以自动重试一次;模型返回内容异常,比如触发了内容安全拦截,重试没有意义,应该降级提示用户换一种问法。我会根据错误码把重试策略区别出来,而不是一刀切。
4.2 Agent 工具调用:从模型输出到本地动作执行
“AI Agent”是最近的热词,但工程上它的底层逻辑并不神秘:模型在对话中决定调用哪个工具、传什么参数,应用解析这个意图后去执行真实的系统操作。比如用户说“把刚才那张发票的金额记到账本里”,模型返回一个工具调用指令,App 解析后生成账本记录,再向用户确认。
模型返回的如果是函数调用格式,我会做一个统一的 JSON 解析层:
// 伪代码:工具调用解析派发 interface ToolCall { name: string; parameters: Record<string, unknown>; } function dispatchToolCall(call: ToolCall): ToolResult { switch (call.name) { case "save_to_ledger": return ledgerService.save(call.parameters); case "create_reminder": return reminderService.create(call.parameters); default: return { error: "unknown_tool" }; } }工具调用最容易出问题的是参数格式错误。模型有时候会返回一个不存在的字段名,或者日期格式不对。在派发之前必须做参数校验,校验失败就回传给模型,让它重新组织参数,而不是直接让用户看到一个报错。
4.3 上下文的滑动窗口与长期记忆
对话场景里,模型输入的 token 数量影响成本和响应速度。我早期直接把所有历史消息堆给模型,聊到二十轮以后,用户量稍一上来,成本肉眼可见地涨。
架构上的解决办法是分层记忆:短期上下文保留最近几轮对话,直接放进请求;长期记忆把关键信息抽取出来,用户确认后存到本地数据库,后续对话只注入这些经提炼的摘要和关键字段。这里有一个细节,如果同时存在本地记忆和远程上下文,两者拼接顺序要固定,否则模型理解会出现混乱。
4.4 端侧模型的版本管理与增量更新
端侧模型的更新是一个隐蔽但重要的问题。模型文件动辄几十上百 MB,如果每次版本更新都让用户重新下载,体验很差。
我采用的方案是新模型文件先下载到沙盒临时目录,校验 MD5,确认下载完整后原子替换正式目录里的模型文件。切换时机放在用户空闲时,比如应用进入后台,而不是正在推理时替换。同时保留旧版本文件,新模型推理效果被用户反馈异常时,有回退手段。
5. 数据合规与上架审核:权限、隐私和内容安全不能踩的线
5.1 权限申请:不碰用不到的能力
AI 应用经常被诟病权限申请过度。我见过一些同类产品,打开就直接要求相机、相册、麦克风、定位,用户一看就警惕。鸿蒙的权限体系要求运行时动态申请,而且用户拒绝一次之后,策略收紧,第二次申请需要引导用户去设置页开启。
我的原则是权限跟着功能走。只有用户真正进入“拍摄识别”页面时才申请相机权限,相册选择用系统 Picker 能力,不直接申请整个相册的读取权限。这样权限弹窗出现的时机跟用户预期一致,通过率反而更高。
5.2 隐私保护与数据加密:别把敏感信息放明文
AI 应用处理的往往是文档、发票、聊天记录这类敏感数据。在端侧,我用了安全沙盒内的应用目录存储原始文件,结构化结果写入数据库时做字段级加密。特别注意:不要把大模型的请求日志存在客户端明文里。排查问题时我习惯开本地日志,但发布版本里必须把日志级别调高,避免敏感内容被带出。
如果要把用户数据上传到云侧做增强处理,必须在界面明确告知用户,而不是藏在用户协议里。做“文档摘要”时,我默认只上传提取后的纯文本,原图那份只在端侧解析,结构化之后立刻释放原图缓存。
5.3 AI 生成内容的合规过滤与标识
AI 生成内容这块,平台和监管的要求越来越明确。我在服务端接入大模型的同时,接了一层内容安全检测,任何模型输出先过一道过滤,命中风险内容就替换成安全话术。客户端这边也要做兜底,如果服务端返回了异常内容,本地正则规则再拦截一次,不能把原始输出直接上屏。
合规标识也不能漏。用户聊天、生成文本的界面,我保留了“AI 生成”提示,做法是在结果底部固定显示一个低调的标识,既满足要求,又不破坏整体视觉。
5.4 上架审核中常见的驳回点
上架华为应用市场时,我遇到过的驳回点有几个比较典型。第一是隐私政策里没有明确列出用到的 AI 能力和数据使用范围,必须一项项写清楚,比如“端侧 OCR 识别,数据不出设备”“云侧摘要功能会上传文本内容,用于生成摘要”。第二是权限描述与实际功能不符,申请权限的理由文本会被审核人员逐一核对。第三是元服务入口的跳转目标不明确,如果声明了系统意图,实际跳转必须跟声明一致,不能打着入口的名义跳去广告页。
审核这块没有捷径,提前把隐私协议写得像“写给用户看的说明文档”,而不是法务免责声明,通过率会高很多。
6. 真机上的性能与稳定性:推理功耗、内存和调试工具的实测心得
6.1 端侧推理的 CPU 占用与发热问题
端侧模型跑起来最容易遇到的问题是发热。我早期版本的 OCR 推理在主线程附近跑,一次识别 CPU 直接拉满,手机背面明显烫手。后来把推理任务全部丢到独立 TaskPool,并且把输入图片在送入模型之前做尺寸压缩,分辨率从 4000 像素压缩到 1600 像素,识别准确率几乎没降,但推理耗时降了 70%,发热问题基本消失。
功耗问题需要真机反复测。不要只看跑分,要看连续 10 次识别过程的电量曲线。如果发现电池温度持续上升,优先检查是不是有隐形的重复加载模型或者不必要的内存拷贝。
6.2 内存抖动与崩溃定位
AI 应用另一个高频崩溃原因来自内存。端侧模型加载到内存后占用不小,图片对象如果没及时释放,来回切换页面就会出现内存峰值。
我用 DevEco Profiler 追踪内存分配,发现主要问题集中在图片矩阵和临时 tensor 没有复用。修复方式是把输入图片统一转成一个标准尺寸数组,重复使用同一块内存,避免每次生成新数组。还有一个容易忽略的保护点:推理前检查当前内存水位,如果低于阈值,先降级处理,比如提示用户关闭其他应用,而不是硬跑导致被系统杀掉。
崩溃定位我主要靠 HiLog 日志。发布版会开启崩溃日志回传,但回传日志里绝不能包含用户文档内容。我做了脱敏,路径、文件名、ID 都替换成占位符。
6.3 DevEco Profiler、HiLog 和 WiFi 调试
调试这块,很多新手会忽略真机 WiFi 调试。不是所有场景都要插线,尤其是测试多设备流转和分布式能力时,无线调试更方便。在 DevEco Studio 里连接过一次之后,同一网络下后续基本是秒连。
性能分析工具里,我用得最多的是帧率检测和任务耗时分析。AI 应用页面不能因为后台推理而掉帧。原则是推理放到后台任务池后,UI 线程只做结果渲染。保持 UI 线程每帧耗时在 10ms 以内,体感就很流畅。
6.4 性能基线:我记录的一组实测数据
最后分享一组我整理过的实测数据,设备用的是当时主流的旗舰机,模型是自训练的轻量化 OCR 模型,版本不含云侧网络延迟:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 单张图片 OCR 耗时 | 约 1.8s | 约 420ms |
| 推理过程 CPU 占用峰值 | 84% | 41% |
| 模型加载耗时(仅首次) | 约 3.2s | 约 2.1s |
| UI 线程平均帧耗时 | 22ms | 9ms |
| 连续 10 次推理电池温升 | 5.1℃ | 1.8℃ |
这个数据不是要说明我做得有多好,而是想给你一个参考坐标系:如果你的指标明显比这个差,大概率不是手机的问题,而是架构上某个环节存在不必要的开销。
如果只让我留一句经验,那就是:鸿蒙 AI App 的架构没有一劳永逸的正确答案,但有一条主线不会变——把系统能力、端侧能力、云侧能力分清楚,让每种任务用最适合它的资源完成。下次你拿到一个新 AI 需求,可以先问自己是哪类任务、该走哪条链路,再动手写代码。这样折腾出来的架构,后面扩展和上架都会顺畅很多。