适合谁看

  • 正在接多种鸿蒙能力,担心平台层越做越大的开发者

  • 想理解为什么这些能力不能共用一套“大平台服务”的人

  • 想让 Flutter / ArkTS 边界更清楚的人

问题背景

很多项目在开始接平台能力时,会下意识想做一个统一抽象:

  • 统一一个平台 service

  • 统一一个 super channel

  • 统一一套调用协议

这个想法本身没问题,但如果不区分能力类型,很快就会遇到一个问题:

表面上都叫“鸿蒙能力”,但它们的工作方式其实根本不是一类东西。

食界探味里,至少有三种能力非常适合拿来说明这个问题:

  • 语音识别

  • 文本转语音

  • Intent 导航

它们看起来都属于“平台层”,但实际上:

  • 触发方式不同

  • 生命周期不同

  • 回调方式不同

  • 对页面层的影响方式不同

这也是为什么它们不应该被揉成一个抽象层。

先说结论:同样都是平台能力,但“输入类”“输出类”“入口类”本来就不是一类设计问题

如果只说一个判断原则,我会这样概括:

语音识别是输入类能力,TTS 是输出类能力,Intent 是入口类能力。虽然都属于平台层,但这三类能力的职责完全不同,所以更适合分开设计。

食界探味里当前的实现,就比较接近这种拆法。

这里最容易被忽略的一点是:

它们虽然都通过 MethodChannel 和原生层交互,但“都用 channel”不代表“就应该共用同一套能力设计”。

MethodChannel 只是边界通信手段,不是能力分层本身。

1. 语音识别为什么是一类独立能力

对应文件主要有:

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

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

语音识别的特点是:

  • 用户主动触发

  • 强依赖权限

  • 有一次性的识别会话

  • 结果通常以文本回传页面

这意味着它的设计重点通常是:

  • 怎么开始

  • 怎么结束

  • 没权限时怎么办

  • 最终文本怎么回到输入层

所以它更像一种“输入能力”。

语音识别真正要解决的,是“把外部声音变成页面输入”

speech_recognition_channel.dartSpeechRecognitionPlugin.ets 这组实现,会发现它最核心的问题其实是这几个:

  • 什么时候开始识别

  • 什么时候结束识别

  • 麦克风权限拿不到怎么办

  • 最终文本什么时候算有效结果

也就是说,语音识别更接近“输入控件的延伸”,只是这个输入不是键盘,而是声音。

所以页面层关心的是:

  • 用户点下按钮后能不能开始

  • 最终回来了什么文本

  • 失败时要不要提示或回退

而原生层关心的是:

  • 权限

  • 引擎

  • 监听器

  • 会话结束

这就是一个非常完整的“输入类能力”结构。

它的生命周期为什么天然独立

语音识别通常有一个很明确的会话周期:

  1. 页面发起开始识别

  2. 原生层申请权限

  3. 创建引擎并启动监听

  4. 在结果完成、报错或主动停止后结束这次会话

这类能力的生命周期很像“一次输入会话”。
它和 TTS 的播报会话、Intent 的系统入口会话,本来就不是一回事。

2. 文本转语音为什么又是另一类能力

对应文件主要有:

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

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

TTS 的特点和语音识别正好相反:

  • 它不是把外部信息变成页面输入

  • 它是把页面结果再输出给用户

  • 它通常需要播报控制、停止、监听完成状态

也就是说,它更像“结果输出层的一部分”,而不是“输入层的镜像”。

如果把它和语音识别强行放在同一个抽象里,后面最容易出现的问题就是:

  • 调用参数越来越不像一类

  • 生命周期越来越不像一类

  • 页面交互意义也越来越不像一类

TTS 真正处理的是“结果如何被用户听见”

TTS 看上去和语音识别很像,都是声音相关能力。
但它们在产品语义上几乎是反过来的:

  • 语音识别是“把用户的声音收进来”

  • TTS 是“把应用的结果送出去”

TextToSpeechPlugin.ets 也能很直观地看到这种差别。
它真正要负责的是:

  • 创建 TTS 引擎

  • 配置播报参数

  • 监听 onStartonCompleteonStoponError

  • 在停止或完成后正确收口

页面层真正想表达的是:

  • 我现在要播报这段文本

  • 如果用户切走或主动停止,要不要中断

  • 播报结束后页面要不要继续下一步

这和“拿到一段输入文本”完全不是同一个问题。

它的复杂度和语音识别不是镜像关系

很多人会下意识认为:

  • 有语音识别

  • 有 TTS

  • 那不如做成一个语音服务

这就是最容易把平台层做重的地方。
因为它们虽然都和声音有关,但复杂度不在同一个方向上:

  • 语音识别的复杂度在权限、识别过程、最终结果

  • TTS 的复杂度在播报控制、引擎状态、结束回调

它们不是“同一套逻辑的两个方向”,而是“两个完全不同的能力模型”。

3. Intent 为什么更不是同一类能力

对应文件主要有:

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

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

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

Intent 的本质和前两者又不一样。

它不是输入,也不是输出,而是:

  • 系统入口

  • 页面调度

  • 应用启动状态协调

Intent 设计里要考虑的重点通常是:

  • 谁先接住系统调用

  • Flutter 引擎没 ready 时怎么办

  • pageId 怎么映射成页面

  • 系统入口和应用内路由怎么衔接

这和语音识别、TTS 根本不是同一类问题。

Intent 的核心不是“执行一个动作”,而是“接住系统入口”

InsightIntentExecutorImpl.etsIntentNavigationPlugin.ets 这一组实现,会发现 Intent 的第一职责甚至不是导航本身,而是:

  • 从系统入口先把参数接住

  • 校验参数是否合法

  • 判断 Flutter 当前有没有 ready

  • 没 ready 时先缓存待处理导航

到了 Flutter 侧的 intent_navigation_channel.dart,才开始做:

  • 解析 payload

  • pageId 转成应用路由

  • 决定是 go 还是 push

这说明 Intent 更像一种“应用外部入口协调能力”。
它最复杂的不是页面怎么跳,而是系统入口和应用内部状态怎么接上。

它为什么绝对不适合和语音、TTS 混成一个服务

因为它连触发源都不一样:

  • 语音识别通常是用户在页面里主动点的

  • TTS 通常是页面里某个结果需要被播报

  • Intent 可能是系统先把用户送进来,页面甚至还没 ready

一旦把这三类能力硬塞进一个 service,那个 service 很快就会同时负责:

  • 用户输入

  • 输出播报

  • 系统入口协调

这时它就不再是“统一抽象”,而是“不同问题的收纳箱”。

4. 如果把这三类能力揉到一起,会出什么问题

这也是很多平台层后面越来越重的原因。

问题 1:接口风格混乱

语音识别需要:

  • 开始

  • 停止

  • 权限判断

  • 文本结果

TTS 需要:

  • 播报

  • 停止

  • 完成回调

  • 出错处理

Intent 需要:

  • 缓存 pending navigation

  • 页面映射

  • 系统入口触发

如果它们被硬塞进一个“大平台服务”,接口很快就会越来越怪。

问题 2:页面语义混乱

页面层真正想表达的是:

  • 我要拿一段语音输入

  • 我要播报一段文本

  • 我要响应一个外部入口跳转

这三个意图本来就不同。
如果平台抽象层不区分,页面层也会慢慢变得不清楚。

问题 3:后续扩展越来越困难

比如:

  • 语音识别后面可能接连续识别

  • TTS 后面可能接更多播报控制

  • Intent 后面可能接更多 pageId

它们的演进方向也完全不同,所以一开始就不应该绑死在一个通用抽象里。

问题 4:状态管理会越来越奇怪

这三类能力如果放到一个“大平台服务”里,通常会出现一种很别扭的状态设计:

  • 既要表示“是否正在识别”

  • 又要表示“是否正在播报”

  • 还要表示“是否有待处理跳转”

这些状态本来就不是一个维度。
把它们收在一起,最后要么状态字段越来越多,要么命名越来越抽象,页面层反而更难用。

问题 5:错误处理会失去语义

例如:

  • 语音识别的错误,重点在权限和识别失败

  • TTS 的错误,重点在引擎和播报失败

  • Intent 的错误,重点在入口参数和时机问题

如果统一成一类“平台错误”,表面上是收口了,实际上页面更难知道该怎么处理。

真正稳的做法通常是:

  • 能力内部各自处理自己的错误语义

  • 边界层再统一错误风格,而不是统一错误本体

5. 食界探味现在这套拆法,核心好处是什么

这个项目当前的拆法并不复杂,但比较清楚:

  • 每种能力有自己独立的 channel

  • 每种能力有自己独立的 ArkTS 插件

  • 页面层按“输入 / 输出 / 导航”分别消费

这样做的好处主要有三点:

1. 每个能力的职责更清楚

看到文件名就知道它主要负责什么。

2. 页面层调用更贴近业务语义

页面不需要理解“一个超级平台层”里到底装了什么。

3. 后面单独扩展某一类能力更容易

比如只改语音识别,不会连带改 TTS 或 Intent 抽象。

4. 平台层更容易讲清楚,也更容易测试

拆开之后,每一组能力都能单独回答这几个问题:

  • 它由谁触发

  • 它依赖什么系统能力

  • 它的结果怎么返回

  • 它失败时页面怎么处理

这不只是写文章更顺。
从工程角度看,这也意味着每组能力更容易做边界测试和问题排查。

可以直接看出差异的一张对照表

如果把这三类能力压缩成一张表,它们的差异会很直观:

能力

本质类型

常见触发源

核心复杂度

结果如何返回

语音识别

输入类

页面主动触发

权限、引擎、识别会话

文本结果回到 Flutter

TTS

输出类

页面主动触发

播报控制、引擎状态、结束回调

完成、停止或错误回到 Flutter

Intent

入口类

HarmonyOS 系统入口

参数校验、时机、pending navigation

导航数据进入 Flutter 路由层

只要看清这张表,其实就很难再把它们理解成“同一种平台能力”。

6. 那什么东西才适合统一

说到这里,也要说清楚另一面。

不是所有东西都要拆得特别碎。
真正适合统一的,通常是这些:

  • channel 命名规则

  • 错误处理风格

  • 返回值风格

  • 页面侧调用习惯

也就是说,可以统一“边界风格”,但不一定要统一“能力本体”。

这是两件不同的事。

一个更稳的统一方式是什么

与其做一个“超级平台服务”,更稳的统一方式通常是:

  • 每种能力独立 channel

  • 每种能力独立原生插件

  • Flutter 侧保持相似的调用习惯

  • 错误码、日志风格、参数命名尽量一致

这样做统一的是“工程风格”,不是硬把能力本身揉成一团。

一个很实用的判断法:看它们是不是同一类生命周期

以后遇到新的鸿蒙能力时,可以先不要急着问“能不能并进现有服务”,先问:

1. 它的触发源和现有能力一样吗

如果不一样,就要小心。

2. 它的会话生命周期一样吗

如果一个是“开始-等待结果-结束”,另一个是“系统随时推送-页面被动消费”,那通常就不该放在同一抽象里。

3. 它的结果语义一样吗

一个返回文本,一个返回播报完成,一个返回导航意图,这三者显然不是一类结果。

4. 它未来的扩展方向一样吗

如果未来一个会往识别细节扩,一个会往播报控制扩,一个会往系统入口扩,那最好一开始就拆开。

常见坑

坑 1:一开始就先做一个超级平台服务

这样短期看着整齐,长期很容易变成混杂层。

坑 2:把“都是平台能力”误当成“就是同一类能力”

平台归平台,输入、输出、入口依然是完全不同的三类问题。

坑 3:页面层为了复用,反而失去语义清晰度

页面最怕的不是多几个 channel,而是不知道自己到底在调什么能力。

坑 4:统一得太早

在还没摸清能力差异之前,过早统一抽象,后面通常要返工。

可复用模板

以后碰到新的鸿蒙能力时,可以先问两个问题:

  1. 它属于输入、输出,还是入口

  2. 它和已有能力的生命周期是不是同一类

如果答案都不是一类,那更适合独立设计。

如果你想把它再写得更可执行一点,也可以直接套这套规则:

  • 输入类能力单独设计

  • 输出类能力单独设计

  • 系统入口类能力单独设计

  • 只统一边界层风格,不强行统一能力本体

本篇总结

语音识别、TTS 和 Intent 虽然都属于平台层能力,但它们本来就是三类不同问题:

  • 语音识别是输入

  • TTS 是输出

  • Intent 是入口

食界探味当前把它们分开设计,不只是代码组织问题,更是在保护后面的边界和扩展空间。
对于真实项目来说,这种拆法通常会比“一层大而全的系统服务”更稳。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐