适合谁看

  • 正在做 Flutter 鸿蒙项目,但不确定原生层应该承担多少的人

  • 已经开始接系统能力,担心后面边界越来越乱的人

  • 想把 Flutter 和鸿蒙原生层分工讲清楚的人

问题背景

在 Flutter 鸿蒙项目里,一个很常见的问题是:

Flutter 和 ArkTS 都能做一点,那到底应该让谁负责什么?

如果这个问题一开始没想清楚,后面通常会出现几种情况:

  • Flutter 里开始塞很多系统能力判断

  • ArkTS 里又慢慢长出一层业务逻辑

  • 页面导航一部分在 Flutter,一部分在原生

  • 权限、状态、入口逻辑互相穿插

最后项目虽然能跑,但维护和讲解都会越来越困难。

先说结论:先按“业务层”和“系统层”分,再按“谁更稳定”决定归属

如果只说一个最简单的判断原则,我会这样归纳:

业务层优先放 Flutter,系统层优先放 ArkTS;边界不清的时候,再看哪一层变化更频繁、哪一层更依赖 HarmonyOS 本身。

食界探味当前就是比较典型的这种分法。

先建立一个最小分层图

如果把这个项目压缩成一个最小分层图,大致可以先这样理解:

Flutter 页面与业务层
    ↓
Flutter 平台边界层(core/platform)
    ↓
ArkTS 原生实现层(plugins)
    ↓
HarmonyOS 系统能力 / 权限 / 生命周期

而在这条主链路之外,还有一条“系统把用户送进来”的入口链路:

HarmonyOS 系统入口
    ↓
entryability / formability
    ↓
ArkTS 插件或入口适配
    ↓
Flutter 路由与页面

这两条链路一旦分清,很多“到底该放哪”的问题就会变得容易很多。

如果再具体一点,这个项目里可以先对上下面几组目录:

  • Flutter 页面与业务层:app/lib/features/app/lib/data/

  • Flutter 平台边界层:app/lib/core/platform/

  • ArkTS 原生实现层:app/ohos/entry/src/main/ets/plugins/

  • HarmonyOS 系统入口层:app/ohos/entry/src/main/ets/entryability/

  • HarmonyOS 卡片触达层:app/ohos/entry/src/main/ets/formability/

先把这五层对上,再去看某个能力该放哪里,判断就会稳很多。

1. Flutter 更适合承担什么

在食界探味里,Flutter 侧主要承担的是:

  • 页面 UI

  • 路由组织

  • 状态管理

  • 数据模型

  • AI 会话与业务编排

  • 网络请求

这些内容主要集中在:

  • app/lib/features/

  • app/lib/core/

  • app/lib/data/

  • app/lib/app.dart

为什么这些东西更适合放 Flutter?

因为它们通常具备几个特点:

  • 和页面交互强相关

  • 变化频率更高

  • 需要跨平台复用

  • 更接近用户真正感知到的产品层

这类内容如果过早下沉到 ArkTS,后面会让业务调整成本上升很多。

Flutter 侧适合做的,通常有一个共同点

这类事情通常都满足至少两条:

  • 页面变化比平台变化更频繁

  • Android / iOS / HarmonyOS 都可能复用

  • 更接近产品交互,而不是系统能力

  • 不依赖 HarmonyOS 独占 API

如果一个逻辑同时满足上面几条,它大概率就更适合留在 Flutter。

在食界探味里,哪些内容明显应该留在 Flutter

可以直接举几个更具体的例子:

  • 搜索结果页怎么排版、怎么筛选,应该留在 Flutter

  • AI 助手页面怎么组织消息列表、输入框和播报按钮,应该留在 Flutter

  • 菜品详情页怎么根据数据状态展示占位、加载和错误,应该留在 Flutter

  • 原生能力返回后,页面是弹提示、改按钮状态还是刷新列表,也应该留在 Flutter

因为这些决定的都是“产品怎么呈现”,而不是“系统怎么工作”。

2. ArkTS 更适合承担什么

在食界探味里,ArkTS 侧主要承担的是:

  • HarmonyOS 系统能力调用

  • 权限申请相关逻辑

  • 系统入口处理

  • Form 卡片相关能力

  • EntryAbility / ExtensionAbility 生命周期承接

这部分内容主要集中在:

  • app/ohos/entry/src/main/ets/plugins/

  • app/ohos/entry/src/main/ets/entryability/

  • app/ohos/entry/src/main/ets/formability/

为什么这些东西更适合放 ArkTS?

因为它们通常具备几个特点:

  • 明确依赖 HarmonyOS API

  • 明确依赖权限与 Ability 生命周期

  • 很难脱离平台层单独存在

  • 更接近“系统如何工作”,而不是“页面如何呈现”

ArkTS 侧适合做的,通常也有一组共性

这类事情往往都满足至少两条:

  • 直接依赖 HarmonyOS Kit

  • 直接依赖权限申请或系统上下文

  • 直接依赖 Ability / Form 生命周期

  • 不做这些就无法完成系统能力调用

如果一个能力天然具备这些特征,把它继续硬留在 Flutter 往往只会把复杂度藏起来。

在食界探味里,哪些内容明显应该放到 ArkTS

同样也可以举得更落地一些:

  • 麦克风权限申请和语音识别引擎创建,应该放在 SpeechRecognitionPlugin.ets

  • TTS 引擎初始化、监听播报状态、停止播报,应该放在 TextToSpeechPlugin.ets

  • 小艺搜索、意图执行、待处理跳转缓存,应该放在 InsightIntentExecutorImpl.ets 和相关插件里

  • 防窥状态订阅、系统开关检测、蒙层拉起,应该放在 AntiPeepProtectionPlugin.ets

这些逻辑的共同点是:离开 HarmonyOS 上下文之后,它们本身就不成立。

真正难的不是“谁能做”,而是“谁更适合长期负责”

很多边界问题之所以会让人犹豫,是因为 Flutter 和 ArkTS 看起来都“能做一点”。

例如:

  • 跳转,Flutter 能做,原生也能做

  • 权限提示,页面上能提示,原生也能申请

  • 状态监听,Flutter 能监听,原生也能监听

但项目分层时,判断标准不应该只是“谁能做”,而应该是:

  • 谁更接近这个能力的本质

  • 谁更适合长期维护

  • 谁的改动对其他层影响更小

这也是为什么分工这件事,本质上是维护性问题,而不是代码量问题。

3. 食界探味里,边界是怎么落下来的

如果把这个项目的分工压缩成一句话,大概是:

Flutter 负责表达业务需要什么,ArkTS 负责把 HarmonyOS 能力真正调起来。

例子一:语音识别

Flutter 侧:

  • app/lib/core/platform/speech_recognition_channel.dart

这里负责的是:

  • 定义调用入口

  • 暴露 startListening / stopListening

  • 把结果交还给页面层

ArkTS 侧:

  • app/ohos/entry/src/main/ets/plugins/SpeechRecognitionPlugin.ets

这里负责的是:

  • 申请麦克风权限

  • 创建语音识别引擎

  • 监听回调

  • 把识别结果回传到 Flutter

例子二:文本转语音

Flutter 侧:

  • app/lib/core/platform/text_to_speech_channel.dart

ArkTS 侧:

  • app/ohos/entry/src/main/ets/plugins/TextToSpeechPlugin.ets

分工逻辑和语音识别类似:

  • Flutter 层表达“我需要播报”

  • ArkTS 层负责“具体怎么播报”

例子三:意图跳转

Flutter 侧:

  • app/lib/core/platform/intent_navigation_channel.dart

这里主要负责:

  • 接收跳转意图数据

  • pageId 映射到具体路由

  • 与 Flutter 路由体系衔接

ArkTS 侧:

  • app/ohos/entry/src/main/ets/plugins/IntentNavigationPlugin.ets

  • app/ohos/entry/src/main/ets/entryability/InsightIntentExecutorImpl.ets

这里主要负责:

  • 接住系统搜索或意图入口

  • 暂存 pending navigation

  • 把系统入口转成 Flutter 能消费的数据

这一组例子很能说明边界:

  • Flutter 更接近“页面最终去哪”

  • ArkTS 更接近“系统是怎么把用户送进来的”

例子四:防窥保护

这一组也很适合拿来看边界:

Flutter 侧:

  • app/lib/core/platform/anti_peep_protection_channel.dart

这里负责的是:

  • 暴露 activateCollectionProtection / deactivateCollectionProtection

  • 接收原生事件回传

  • 把系统状态映射成 Flutter 可观察状态

ArkTS 侧:

  • app/ohos/entry/src/main/ets/plugins/AntiPeepProtectionPlugin.ets

这里负责的是:

  • DeviceSecurityKit

  • 申请和检查系统状态

  • 订阅防窥状态变化

  • 必要时拉起系统蒙层

这一组边界的意义很明显:

  • Flutter 不直接理解 dlpAntiPeep 的底层接口

  • ArkTS 也不直接决定页面 UI 怎么改

两边各自只负责自己那一层该做的事情。

看一个能力该放哪,最实用的其实是“决策顺序”

如果你每次都临时判断“这个东西放 Flutter 还是 ArkTS”,项目会很快不统一。

更稳的方式,是每次都按同一套顺序问自己:

第一步:这个能力是不是明显依赖 HarmonyOS 独占 API

如果是,先偏向 ArkTS。

第二步:它是不是和权限、Ability、Form 生命周期直接相关

如果是,继续偏向 ArkTS。

第三步:它是不是跨平台复用的页面或业务逻辑

如果是,优先留在 Flutter。

第四步:它是“能力本体”,还是“能力结果的页面消费”

  • 能力本体,偏原生

  • 页面消费,偏 Flutter

只要这套决策顺序稳定下来,团队就不容易每次都做出不一样的分工。

再补一层:分工前先问的 4 个问题

上面那套决策顺序适合快速判断。
如果你正准备新接一个能力,我更建议再多问四个问题:

1. 这个能力离开 HarmonyOS 以后还剩下什么

如果离开 HarmonyOS 以后,剩下的只是一句“我要开始识别”“我要开始播报”,那说明真正复杂的部分不在 Flutter,而在原生层。

2. 这个能力未来更可能改“页面表现”,还是改“系统接法”

如果以后大概率会经常改交互、状态、按钮、引导文案,那部分优先留 Flutter。
如果以后大概率会改权限、订阅、系统参数、原生回调,那部分优先留 ArkTS。

3. 这个能力失败时,哪一层更适合解释错误

例如:

  • permission denied

  • engine create failed

  • ability context unavailable

这类错误更适合先在 ArkTS 层被归一化,再交给 Flutter 做页面表达。
如果让页面层直接理解原生错误细节,后面会越来越耦合。

4. 这个能力有没有系统主动推送回来的状态

像防窥保护这种能力,不只是“调用一次返回一次”,而是系统可能不断推送状态变化。
这类能力通常更适合:

  • ArkTS 负责订阅和清理

  • Flutter 边界层负责转成可观察状态

  • 页面层只负责消费状态

这一问很重要,因为它能帮你分清“命令型能力”和“事件型能力”。

什么叫“边界层”,为什么它不能省

很多人理解分工时,会只想到两层:

  • Flutter

  • ArkTS

但真实项目里,最容易被忽略、也最关键的一层,其实是:

Flutter 平台边界层

在食界探味里,它主要就是:

  • app/lib/core/platform/

这一层的作用不是重复写原生代码,而是把两边隔开:

  • 对 Flutter 页面来说,它隐藏原生细节

  • 对 ArkTS 原生层来说,它收敛调用协议

如果没有这一层,最容易发生的事就是:

  • 页面直接耦合原生回调细节

  • 某个能力的参数结构散落在各个页面里

  • 后面想统一错误处理或返回风格时非常困难

所以从工程角度看,边界层并不是“可选优化”,而是分工真正能成立的前提。

边界层具体负责什么

很多人第一次看到 app/lib/core/platform/,会以为这里就是简单包一层 MethodChannel
但它真正应该承担的事情,通常比“转发一下调用”多一点:

  • 统一 channel 名称和方法名

  • 统一参数结构和返回结构

  • 把原生事件改造成 Flutter 好消费的状态

  • 屏蔽平台不存在时的异常,比如 MissingPluginException

  • 让页面层看不到 ArkTS 细节

anti_peep_protection_channel.dart 为例,它做的不只是转调方法,还承担了两件很关键的事:

  • setMethodCallHandler 接住原生事件

  • ValueNotifier<AntiPeepVisibilityState> 把原生状态变成页面能直接监听的状态

这就是边界层的价值。
它不是业务层,但它决定了业务层能不能保持干净。

一条完整链路该怎么走

只讲“谁放哪”还不够,真正有帮助的是把一条链路走完整。

这里可以拿防窥保护举例,因为它同时包含了:

  • Flutter 主动发起调用

  • ArkTS 调系统能力

  • 系统再异步回推状态

  • Flutter 页面最终消费状态

第 1 步:页面表达业务意图

页面层不会直接碰 DeviceSecurityKit,它只表达一件事:

  • 某个页面进入敏感展示状态,需要开启防窥保护

  • 离开这个页面时,需要关闭保护

这类表达应该留在 Flutter 页面或页面控制器里。

第 2 步:边界层发出平台调用

app/lib/core/platform/anti_peep_protection_channel.dart 负责暴露:

  • activateCollectionProtection()

  • deactivateCollectionProtection()

页面调用的是这两个语义化方法,而不是某个原生 Kit 的底层接口名。

第 3 步:ArkTS 插件接住调用并进入系统上下文

app/ohos/entry/src/main/ets/plugins/AntiPeepProtectionPlugin.ets 接到调用后,会开始处理真正的平台动作:

  • 检查是否已经激活

  • 检查系统防窥开关状态

  • 必要时请求开启选项

  • 订阅 dlpAntiPeep 状态变化

  • 在需要时设置系统蒙层

这些都明显属于 HarmonyOS 原生层职责。

第 4 步:系统状态变化回推到 Flutter

插件里不是只 result.success(null) 就结束了。
它还会通过 channel.invokeMethod('onAntiPeepEvent', args) 主动把事件推回 Flutter。

像这些事件:

  • HIDE

  • PASS

  • DEACTIVATE

  • MASK_SHOWN

都说明这不是一次性的“命令调用”,而是一条持续的事件链路。

第 5 步:边界层把原生事件翻译成页面状态

Flutter 侧收到 onAntiPeepEvent 后,没有把原生事件字符串直接丢给页面,而是先做了一层转换:

  • HIDE 对应 AntiPeepVisibilityState.hidden

  • PASS / DEACTIVATE 对应 AntiPeepVisibilityState.visible

这样页面层消费的就是稳定的 Flutter 状态,而不是一组平台事件字符串。

第 6 步:页面层只根据状态改变展示

到页面这一层,只需要回答:

  • 当前内容该不该隐藏

  • 是否需要停留在安全占位态

  • 是否要提示用户当前不可见

页面不用知道:

  • dlpAntiPeep 是什么

  • 订阅是怎么注册的

  • 蒙层是怎么拉起的

这就说明分工是成立的。
每一层都只承担自己最应该承担的那部分复杂度。

命令型能力和事件型能力,分工要稍微不同

并不是所有能力都像语音识别或 TTS 那样“调用一下,得到结果”。
在食界探味里,至少可以粗分成两类:

命令型能力

例如:

  • 开始播报

  • 停止播报

  • 发起一次导航

这类能力的特点是:

  • 页面发出动作

  • 原生侧执行

  • 返回成功或失败

这时边界层更像一个“语义化调用入口”。

事件型能力

例如:

  • 防窥状态变化

  • 语音识别过程中的中间回调和最终结果

  • 系统入口带来的待处理跳转

这类能力的特点是:

  • 系统可能持续推送状态

  • 原生层需要负责订阅和释放

  • Flutter 边界层需要负责把事件转成页面能消费的状态

如果把事件型能力也当成最简单的同步调用去设计,后面大概率会返工。

4. 哪些东西最好不要放错层

这是很多项目后面变乱的根源。

不建议大量放到 ArkTS 的东西

  • 复杂业务判断

  • 页面级状态管理

  • 大量与平台无关的数据处理

  • 本来就应该由 Flutter 页面控制的导航流程

  • 按产品需求频繁变化的展示逻辑

不建议硬留在 Flutter 的东西

  • 依赖 HarmonyOS 权限的能力调用

  • Speech Kit / TTS / 防窥这类系统 API

  • Form 卡片生命周期

  • EntryAbility / Intent 执行入口

  • 依赖 Ability 上下文和系统窗口对象的能力

还有一类东西,最容易被两边都误收

这一类通常是:

  • 页面跳转映射

  • 原生事件到 Flutter 状态的转换

  • 平台错误到业务错误的转换

它们不完全属于 Flutter 页面层,也不应该塞进 ArkTS 业务层。
更合适的落点通常就是前面说的边界层。

换句话说:

  • 业务层不要被原生层吞掉

  • 系统层也不要被 Flutter 假装包住

  • 中间协议层也不要凭空消失

最常见的错误分工长什么样

如果想更直观一点,可以直接看几个反例。

反例 1:ArkTS 决定最终跳哪个业务页

短期看很省事,但后面只要 Flutter 路由结构改了,原生层也要跟着一起改。

更稳的做法是:

  • ArkTS 只负责把系统入口整理成 pageId 或待处理导航数据

  • Flutter 边界层或路由层负责把它映射成具体页面

反例 2:Flutter 页面自己处理所有权限分支

短期看逻辑都在页面里,但后面每个页面都可能写出一套不同的权限处理方式。

更稳的做法是:

  • 权限申请和底层失败原因由 ArkTS 收口

  • Flutter 只决定“怎么提示用户”和“失败后页面怎么退让”

反例 3:每个能力都自创一套参数和错误返回格式

短期能跑,长期平台层会越来越碎,教程也会越来越难写。

更稳的做法是:

  • core/platform/ 里统一命名风格

  • 统一参数结构

  • 统一错误兜底方式

反例 4:系统入口直接穿透到页面层

这样页面会越来越知道系统协议细节,最终项目会越来越难重构。

更稳的做法是:

  • entryability/ 负责接系统入口

  • 插件或入口适配层负责整理数据

  • Flutter 路由层只消费已经约定好的导航信息

可以直接套用的一套分工表

如果你正在搭自己的项目,下面这张分工表基本可以直接拿去用:

层级

建议放什么

不建议放什么

Flutter 页面层

UI、交互、状态消费、路由跳转

HarmonyOS Kit 细节、权限底层分支

Flutter 边界层

Channel、事件翻译、平台错误收口、语义化方法

大量页面状态、复杂业务规则

ArkTS 插件层

Kit 调用、权限、订阅、上下文、窗口、引擎管理

页面跳转细节、业务编排

entryability / formability

系统入口、卡片触达、Ability 生命周期

页面展示逻辑、产品判断

如果你发现某段代码同时想做两行甚至三行的事,通常就说明这段代码已经跨层了。

5. 为什么这件事对技术教程也特别重要

如果边界不清,不只是代码会乱,文章也会很难写。

因为你会发现自己讲不清:

  • 这个能力到底在哪一层实现

  • 这一段逻辑为什么放在这里

  • Flutter 代码和 ArkTS 代码分别解决什么问题

而食界探味现在这套结构之所以适合拆成教程,就是因为它的边界相对容易讲:

  • core/platform/ 是 Flutter 的能力边界

  • plugins/ 是 ArkTS 的能力实现层

  • entryability/ 是系统入口层

  • formability/ 是系统触达层

这会让文章更容易拆成:

  • 一篇讲分工原则

  • 一篇讲某个 channel

  • 一篇讲某个插件

  • 一篇讲某个系统入口

常见坑

坑 1:ArkTS 写着写着变成第二套业务层

一旦原生层开始承担太多页面判断和业务拼装,后面会非常难维护。

坑 2:Flutter 假装什么都能做

有些能力本身就强依赖 HarmonyOS 系统层,硬留在 Flutter 只会让边界更脆。

坑 3:导航边界不清

系统入口带来的跳转和 Flutter 内部路由混在一起时,问题会特别多。

坑 4:写教程时只讲某一边

只讲 Flutter 或只讲 ArkTS,都容易把实际链路讲断。

坑 5:一开始没定规则,后面每个能力都单独拍板

这种项目最容易在半年后出现“每个能力风格都不一样”的情况。

可复用模板

如果你也在做 Flutter 鸿蒙项目,可以先这样划分:

放 Flutter

  • 页面

  • 路由

  • 状态

  • 数据模型

  • 业务编排

放 ArkTS

  • 系统能力

  • 权限

  • 系统入口

  • 卡片

  • Ability 生命周期

放边界层

  • Channel 定义

  • 平台数据协议

  • 页面跳转映射

  • 原生事件到 Flutter 状态的转换

  • 平台异常兜底和兼容处理

这套分法不是唯一答案,但对大多数项目来说,是一个比较稳的起点。

如果你想再进一步把它落成可执行规则,也可以补一句:

  • 页面层不直接调系统细节

  • 原生层不直接做业务编排

  • 平台边界层统一协议与事件

这样团队在实际开发时会更容易达成一致。

本篇总结

Flutter 和 ArkTS 在一个鸿蒙项目里该怎么分工,核心不在“哪边更强”,而在“哪边更适合承担哪一层职责”。

食界探味当前的结构比较清楚地说明了这件事:

  • Flutter 负责业务和页面

  • ArkTS 负责系统能力和系统入口

  • 平台边界层负责把两者连起来

真正好用的分工,不只是能解释现状,还应该能指导下一个能力怎么接。
如果你后面继续接语音、TTS、Intent、卡片或者防窥能力,都可以先按本文这套判断顺序过一遍:

  • 先看是不是 HarmonyOS 独占能力

  • 再看是不是系统上下文问题

  • 再看是不是页面消费问题

  • 最后把协议和状态翻译收口到边界层

只要这个边界一开始就划清,后面无论是继续开发还是继续拆成教程,都会顺很多。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐