去年年底接了一个挺有意思的需求:用 Flutter 做一款演讲稿生成器 APP,并且要跑在鸿蒙设备上。当时团队里对鸿蒙原生开发熟悉的人基本没有,时间又紧,唯一能快速想到的跨端方案就是 Flutter。做之前查了一圈资料,发现 Flutter 对鸿蒙的支持确实已经能落地了,只是资料散、坑不少,很多经验要靠自己趟。这篇文章把整个开发流程从头到尾捋一遍,重点放在“鸿蒙适配会遇到哪些跟 Android、iOS 完全不一样的问题”上,也把演讲稿生成器核心逻辑怎么设计讲清楚,希望能给准备踩这条路的同学省点时间。
项目本身不复杂,但涉及到的边界问题不少:跨端框架选型、鸿蒙工程接入、文本生成逻辑、离线兜底、多端打包发布。我会按实际推进顺序来写,从选型一直讲到上架之前的注意事项,里面所有参数和配置都是我在真实项目里验证过的,可以直接参考。
1. 为什么把演讲稿生成器放在 Flutter + 鸿蒙的跨界组合里
1.1 选型时纠结过的几个方案
接到需求的第一反应不是写代码,而是先确认技术路线。当时有三条路摆在面前:鸿蒙原生开发、Web 套壳、Flutter 跨端。鸿蒙原生方案最正统,但团队里没人写过 ArkTS,学习成本和排期风险不可控;Web 套壳开发速度最快,可演讲稿生成器要处理大量文本编辑、模板渲染、本地存储,纯 Web 实现这些交互,在低端鸿蒙设备上的流畅度会很难看。
最后锁定了 Flutter。原因很直接:我们团队对 Flutter 和 Dart 熟悉,一份代码能同时覆盖 iOS、Android 和鸿蒙三端;演讲稿生成器这种工具型应用,UI 组件复杂度不高,也没有特别底层的硬件调用需求,属于 Flutter 跨端能力的舒适区。后来的实践证明,这个判断没有错,但中途也遇到了不少“只有鸿蒙才有”的适配问题,后面单独讲。
1.2 跨平台框架在鸿蒙生态里的现状
说到鸿蒙支持 Flutter,这里要先澄清一个概念。目前跑在鸿蒙设备上的 Flutter 应用,本质上是通过社区维护的 Flutter 引擎 SDK 把 Dart 代码跑在 OpenHarmony 内核之上,跟 iOS、Android 上直接使用官方 Flutter 引擎的路径不完全一样。这个 SDK 的成熟度在过去一年里提升很快,常见 Widget、路由、网络请求都没问题,但一些依赖原生能力的插件,比如分享、定位、传感器,可能需要自己写平台通道。
这带来两个直接影响。第一,项目里能用到的第三方 Flutter 插件,需要逐个确认是否支持鸿蒙;第二,使用到的原生能力要提前梳理,统一做好平台差异化处理。我在项目启动第一周就做了一张插件支持清单,把演讲稿生成器可能用到的功能全部列出来,逐项标注三端支持情况。这张清单后来帮了大忙,避免了开发到一半发现插件跑不起来的尴尬。
1.3 演讲稿生成器这类工具型 App 特别适合跨端的点在哪
演讲稿生成器本质上是一个“表单收集 + 文本模板 + 文案渲染”的组合。用户输入主题、场合、时长和个人偏好,应用根据规则库生成结构化的演讲稿。这类应用对操作系统的依赖非常浅:不需要蓝牙、NFC、后台定位等特殊权限,主要交互集中在 TextField、Editor、ListView 和分享按钮上,这些恰恰是 Flutter 跨端一致性做得最好的部分。
三端的表现虽然在小细节上有差异,但核心逻辑可以高度的复用。我在项目里把模板引擎、规则解析、生成历史和收藏管理全部放在独立的 Dart 包中,完全脱离 UI 层。这个包在 Android 和鸿蒙上几乎没有任何改动,唯一要处理的平台差异就是文件存储路径和分享方式。整个项目从启动到可用版本只用了不到三周,其中一大半时间其实花在了鸿蒙适配和联调上。
2. 环境和工程搭建:从零到多端构建产物
2.1 工具链配置与鸿蒙 SDK 接入
先讲环境准备。因为 Flutter 本身对鸿蒙的支持并不是官方内置的,所以第一步是把社区维护的 Flutter SDK 分支下下来,配置好对应的 OpenHarmony SDK。这一步有两个很容易出错的地方。
第一,Flutter 版本必须跟 SDK 分支匹配。不是随便拉一个最新版 Flutter 就能直接编译鸿蒙目标,我一开始就用错了版本,编译时提示缺少若干引擎 API,折腾了半天,最后老老实实按文档里的版本组合重新装了环境才通过。这里建议直接用项目文档推荐的组合,不要追新。
第二,OpenHarmony SDK 的建议 API 级别也很重要,级别太旧会导致部分新接口不可用,但级别太新又可能跟社区 Flutter 引擎的预编译产物冲突。我的项目最终选用的是当时兼容表中标定为“推荐”的 SDK 版本,整个过程没有升级也没有降级。老实说,鸿蒙生态迭代速度比 Android 快得多,版本兼容表必须每次构建前都重新看一眼,这是环境层面最重要的经验。
2.2 工程结构:共享业务层与平台分层
工程组织上,我采用了标准的 monorepo 加模块化拆分。dart 代码分成了两个部分:
- 共享业务层:包含演讲稿生成核心逻辑、模板数据、历史记录存储模型、用户偏好设置;
- 平台适配层:包含文件读写、分享调用、剪贴板操作、系统主题检测。
这两个划分的逻辑很简单:业务层必须做到零平台依赖,理论上可以经受任何 Flutter 版本的编译;适配层则通过抽象接口定义,在具体实现里分别调用不同的插件或平台通道。演讲稿生成器的所有核心逻辑都跑在业务层,所以当华为手机上出现某个插件不可用时,我只需要替换对应适配实现,不用动核心代码,这个收益在后期的 bug 修复里非常明显。
2.3 按目标平台拆分构建配置的要点
多端构建配置的麻烦点在于:同一个工程,iOS、Android、鸿蒙三个平台的构建配置是独立的,但又共享 Dart 代码和资源文件。我用了一个比较朴素的办法:在构建脚本里分别调用 flutter build apk、flutter build ios,以及鸿蒙使用的构建命令,三个产物输出到同一目录,再用脚本统一修改版本号。
这里要注意的坑是资源文件。鸿蒙应用对启动图、图标尺寸的要求跟 Android 有差异,如果共用同一套 drawable 资源,会出现在 Android 上显示正常、在鸿蒙上图标被拉伸的情况。我的做法是在鸿蒙的 har 包里单独放一份适配后的图标资源,大小和命名遵循它自己的约束,这样至少能保证上架审核不会被图标问题卡住。另外,鸿蒙的打包产物后缀是 hap,不是 apk,构建产物复制和上传的逻辑要对应调整,这个细节很容易被忘记。
3. 演讲稿生成的核心实现:从参数收集到文案产出的链路
3.1 生成器核心:模板引擎、规则库、AI 提示词的组合
演讲稿生成器最核心的问题是:怎么把一句话主题变成一篇结构完整的演讲稿。我没有依赖单一的方案,而是做了三层叠加。
第一层是模板引擎。预置了开场、主题论证、案例引用、结尾升华等段落结构,每种结构下有多个不同的句式变体。模板不写死具体文字,而是保留槽位,由后面的规则引擎填充。
第二层是规则库。维护了一套基础的语言规则,比如根据演讲时长估算段落数,根据体裁(会议发言、婚礼致辞、学生比赛)切换语气用词,根据听众身份调整称呼和案例类型。这套规则库我用 JSON 维护,每条规则有独立 id,方便后面做 A/B 调整。
第三层才是调用大模型生成润色。在实际开发中,我选择了把模板引擎结果作为提示词的一部分传上去,让模型在已有框架的基础上生成更有血有肉的内容。如果没有这一步,纯模板产出的文字很生硬;只有模型没有模板,生成结果的稳定性又难以控制。两者的结合,在实测中效果最好。
3.2 把用户输入转换成结构化演讲稿的流程
具体交互流程是这样的:用户进入生成页,填写几个关键字段——演讲主题、演讲场合、预计时长、听众身份、是否加入数据案例。这些字段会被转换成一份结构化的请求参数,传给生成引擎。
引擎的处理分四个步骤:
- 根据时长计算出期望段落数,比如 5 分钟演讲,按每分钟 200 字估算,总字数约 1000 字,拆成开场、三个论点、总结五个部分;
- 根据场合关键词匹配语气模板,比如“商务汇报”匹配正式、克制的语气,“婚礼致辞”匹配感性、幽默的语气;
- 把用户输入和匹配到的模板合成一份“骨架演讲稿”,此时内容完整但文字比较干瘪;
- 调用大模型润色,生成最终稿,同时保留一份未经润色的版本,供用户对比选择。
这四步的先后顺序很重要。第 1、2 步是纯本地计算,速度极快;第 3 步产生的骨架稿即使不调用大模型,也能拿来应急使用;只有第 4 步才需要网络。这样设计让离线场景也具备了基础可用性,后文会再展开。
3.3 离线兜底方案:不联网时也能生成像样的演讲稿
AI 生成接口不是时刻都可靠的,网络超时、接口限流、内容审核失败,任何一种情况都会让用户直接面对一个白屏错误。所以从一开始,我就把“离线兜底”当作必须的功能而不是锦上添花。
离线模式的实现方案是:在应用内置一个精简版模板库,覆盖最常见的 20 种演讲场景。当网络请求失败时,应用自动切换为纯模板模式,基于规则库中的句式变体,随机组合出完整演讲稿。虽然文字表现没有模型润色那么自然,但结构完整、用词得体,作为应急方案完全够用。
这个方案在开发初期看起来浪费了不少时间,后来评估发现它其实救了很多次场。尤其是鸿蒙设备在弱网环境下的表现比 iOS 和 Android 更不稳定,用户在实际使用中触达到离线模式的频率相当高。如果当时没有做兜底,这个 APP 的第一印象会很差。
4. 跨端适配踩坑记录:鸿蒙是最意料之外的一环
4.1 三个平台渲染差异:文本溢出、字体、键盘弹出
表面上看,Flutter 的渲染引擎在各平台都差不多,但实际跑起来,细节差异一个接一个。我自己踩得最多的坑有三个。
文本溢出的表现在鸿蒙上跟 Android 不一样。同样一段演讲稿,在 Android 上正常收起,在鸿蒙上却会多出半行,导致底部按钮被顶出屏幕。追根溯源,是不同平台默认字体的行高指标不同。解决办法是统一设置文本组件的 style,显式指定 height 值,而不是依赖默认行高,同时在所有长文本区域增加溢出保护逻辑。
字体问题更隐蔽。鸿蒙自带字体跟 Android 的思源黑体、iOS 的苹方在字形细节上有差异,尤其是标点符号和数字的宽度。演讲稿生成器里的段落编号、时间估算、字数统计大量用到数字,如果不统一指定字体,在不同设备上会看到数字宽度忽宽忽窄,界面整体显得不齐整。后来我在应用级主题中指定了字体族和数字字体回退表,才算把这个问题解决干净。
键盘弹出是最折腾的。Android 上键盘弹出默认调整 resize,iOS 上默认是遮盖式,而鸿蒙的表现介于两者之间但又不完全一致,导致输入框被键盘遮挡的概率比另两端都高。我的处理办法是启用 Scaffold 的 resizeToAvoidBottomInset 并在 Android 和鸿蒙上统一配置 WindowManager 的软键盘模式,实测基本能达到一致表现。
4.2 平台通道调用统一封装的思路
演讲稿生成器需要对剪贴板、文件分享、震动反馈这几个原生能力做调用。社区插件对 Android 和 iOS 支持都很好,鸿蒙上有的能跑、有的跑不了,于是我做了一层平台通道的抽象封装。
具体做法是定义三个 Dart 接口:ClipboardService、ShareService、HapticService。在 Android 和 iOS 上直接调用现有插件实现;在鸿蒙上则通过 MethodChannel 跟原生侧通信,原生侧用 ArkTS 的对应接口做实现。对业务层来说,完全感知不到某个能力是从插件来的还是从原生通道来的。这个抽象带来的另一个好处是,将来如果有某个端的新系统版本不支持某插件,只需要替换这一个实现类,不用改任何业务代码。
4.3 存储与分享:各平台路径差异和分享能力适配
演讲稿生成器会把用户生成过的历史稿件保存在本地,还支持导出为 TXT 文件分享。这个功能简单,但平台差异很大。
Android 上文件路径要使用应用私有目录,分享时通过 FileProvider 提供 URI;iOS 上则是存到临时目录,通过 UIActivityViewController 分享;鸿蒙的存储路径跟 Android 有类似之处,但分享接口调用方式完全不同。我的做法是定义统一的 ExportService,内部根据 Platform 分发到不同实现。鸿蒙侧实现分享时,用的是模块化的数据分享能力,跟 Android 的 Intent 机制并不相同,需要对照原生接口文档一个个试。
这里要提醒一点:不要试图让三端的文件导出逻辑完全一致,那是在浪费精力。每个端只要能满足“生成一份 TXT 并且可以分享出去”这个业务目标就行,内部实现细节各做各的,效率反而更高。
5. 性能、体积与多端发版中的实际操作
5.1 启动速度与打包体积的控制
工具型应用对包体积的敏感度其实很高,用户不太愿意为了一个生成文本的 APP 下载两三百兆的安装包。Flutter 应用本身就自带引擎,基础体积比原生要大,再塞入大模型 SDK、模板资源和字体文件,很容易膨胀。
我的优化手段主要有三个。第一,使用 Flutter 的构建产物拆分功能,把不常用的页面和功能拆到独立产物中,按需加载。演讲稿生成器里,使用频率最低的是“演讲技巧大全”,把它拆出去之后,首包体积下降了约 18%。第二,字体文件压缩,只保留用到的字符集子集,把中文字体文件从十几兆压到两兆以内。第三,资源做按需拉取,模板库首先打一个精简版进包里,完整版首次联网时再全量拉取。
启动速度上,鸿蒙端相对 Android 要稍微慢一点,主要是因为引擎初始化走的是独立的加载链路。我的优化办法是做了一个极简启动屏,保证用户能在最快时间看到应用首帧,真正的业务数据加载放到首页渲染之后。体感上从点击图标到可用状态大概提升了 25% 的感知速度。
5.2 网络请求超时与接口降级策略
演讲稿生成器最依赖大模型接口的地方,就是文案润色阶段。模型接口的特点是响应时间不稳定,正常 3 秒返回结果,高峰时可能 30 秒才响应。再加上鸿蒙设备的网络栈在某些场景下表现不如 Android,超时是必然会发生的事情。
我给网络请求设置了多级超时策略。连接超时 8 秒,读取超时 20 秒,总响应超过 30 秒强制中断。中断后不直接报错,而是分三个降级档位处理:第一档,重试一次接口;第二档,使用本地模板引擎直接生成完整稿;第三档,展示缓存的相似演讲稿。这样设计避免了用户在最需要内容的时候看到一幕错误对话框。
另外我建议把提示词和生成参数做严格的长度限制,演讲稿生成模型的输入最长控制在 1500 字之内,防止因参数过长导致接口响应变慢。实测发现,合理控制提示词长度不仅能降低超时率,生成的文本质量也更稳定,因为模型注意力更集中了。
5.3 多端上架与自动构建的一点经验
多端发版是跨平台 APP 绕不开的最后一公里。iOS 走 App Store 审核,Android 走应用商店审核,鸿蒙走的是自家的应用市场审核,三个渠道的审核规则各有侧重。
我遇到的典型问题是隐私权限说明。Android 和 iOS 侧只需要声明文件读取和网络权限,鸿蒙侧对剪贴板读取有额外的隐私提示要求,如果不写明用途直接调用 getClipboardData,会在审核环节被拒。后来我在鸿蒙的应用配置文件里补充了剪贴板使用声明,并在 UI 层首次调用时弹出说明弹窗,审核才顺利通过。
自动化构建方面,我用脚本把三个平台打成一个发布压缩包,但警告一点:鸿蒙构建流程跟 Android 不是同一个工具链,不要在 CI 脚本里试图拿一套命令打通三端。分开调用、统一收集产物是最稳的,集成过程中任何一次升级导致的命令参数变化,都能在这个结构里快速定位。
多端发版还有个容易忽视的点:版本号同步。iOS 和 Android 的版本号格式是点分三段式,鸿蒙也有相似的语义版本要求,但三个平台的上传后台对版本号的递增规则有细微差别。我的办法是维护一份发布版本配置文件,用脚本统一从这份配置生成三端各自的版本信息,从源头避免“端间版本对不上”的问题。
写在最后的一点体会
这个项目做完,我最大的感受是:跨平台开发在鸿蒙生态里已经不是一个“能不能做”的问题,而是“怎么做更稳”的问题。Flutter 提供的跨端一致性让演讲稿生成器这种工具型 APP 能在三端复用绝大多数字代码,真正需要单独处理的,只剩平台通道、存储路径、分享接口和审核规则这些边界点。只要在项目启动初期就规划好抽象层,后面遇到的坑基本都可以控制在可预期范围内。
如果后面有人要复刻这个项目,我的建议很简单:先花一天时间把要做功能涉及的所有平台差异列成表,再决定哪些走公共插件、哪些自己封装;不要高估多端一致性的自动化能力,也不要低估每个平台特有规则对体验的影响。剩下的,靠动手踩坑就能解决了。