开学第三周,我站在学校打印店门口,手里攥着取号小票,上面写着“当前: 18号,前面还有: 10人”。这家店打印一份材料要五到十分钟,也就是说我还要等一个多小时。但问题是,我不敢走。万一过号了,要重新排队。于是我只能蹲在门口刷手机,一边焦虑一边耗时间。那一次之后我就想,能不能做一个应用,让打印店的排队状态实时显示在手机上,人可以先离开,快轮到的时候再回来?
这就是这篇教程的项目背景。我最终做出来的东西是一个基于 Flutter 框架的跨平台应用,前端主要跑在鸿蒙系统上,同时也能覆盖 Android 等平台,用于查询校园打印店的实时排队情况。整个项目我用了大概两周的业余时间完成,中间踩了不少 Flutter 与鸿蒙适配相关的坑,所以这篇文章不只是给一个能跑的 Demo,更想把从环境搭建到真机运行的完整链路讲清楚,让同学们在实验室或宿舍里也能自己复现。
1. 为什么校园打印店需要排队查询应用——从实际痛点聊项目选型
1.1 打印店排队场景中的真实痛点
先说说这个应用解决的到底是什么问题。高校打印店在开学季、期末季、论文季经常排长队,尤其是理工科院校,打印图纸、实验报告的人一拨接一拨。传统排队方式是线下取号,小票上有编号,但店里只有一个窗口,学生看不到当前叫到几号,也不敢离开,因为过号基本等于重新排。
从我调研的情况看,这类场景有几个共性问题:
- 排队状态不透明:用户无法远程获知当前叫号进度,只能到现场看
- 等待时间难估计:打印店人流波动大,平均等待时间无法提前预知
- 空间被迫占用:等待人群聚集在店面附近,越到高峰期店门口越乱
- 店铺经营数据缺失:店主不清楚高峰期分布,无法合理安排人手
所以应用想要解决的核心问题就是“让排队状态透明化”。用户在手机上随时能看到每家打印店的当前叫号、排队人数、预计等待时间,取号之后可以先去做别的事,系统会在临近叫号时 push 通知。
1.2 技术选型:为什么是 Flutter,为什么是鸿蒙
这个项目被命名为“Flutter 框架跨平台鸿蒙开发”,并不是随便把几个关键词拼在一起,而是针对当前校园开发环境的现实选择。
第一,学校里的后端和客户端团队经常是分开的,打印店的信息化系统通常需要同时覆盖 Windows 收银端、Android 用户端、未来可能还有 Web 管理后台。如果每个端都单独开发,一个三人小团队根本忙不过来。Flutter 一套 Dart 代码出多端应用,正好契合这种“小而全”的团队结构。
第二,为什么考虑鸿蒙?因为近两年校园里使用国产操作系统的设备越来越多,鸿蒙生态的设备占有率在逐步提升。尤其在 HarmonyOS NEXT 全面转向后,原来直接跑 Android APK 的方案已经行不通了,应用必须原生适配鸿蒙。用 Flutter 的 OpenHarmony 适配分支,可以把原本写好的 Flutter 代码直接编译到鸿蒙平台上,避免重复开发。
第三,Flutter 的 UI 渲染性能足够支撑这种中等复杂度的业务应用。打印店排队查询本质上是一个列表 + 详情 + 实时状态页面的结构,没有高帧率渲染、重型 3D 等需求,Flutter 的 Skia 渲染引擎在这种场景下绰绰有余。
提示:如果你的项目只需要支持某一个单一平台,不一定非要上 Flutter。跨平台框架的价值主要体现在多端维护、统一逻辑、快速迭代上;如果明确只有一个鸿蒙端,直接用 ArkTS 开发反而更省事。这个项目的核心是“跨平台”,所以 Flutter 才是一个合理选项。
下面这个表格是我在选型阶段做的一个对比:
| 方案 | 多端覆盖 | 学习成本 | 与鸿蒙融合度 | 适合场景 |
|---|---|---|---|---|
| Flutter + OpenHarmony 分支 | Android/iOS/Web/鸿蒙 | 中 | 中等,需一定适配 | 团队已有 Flutter 经验,需要多端覆盖 |
| ArkTS 原生开发 | 只有鸿蒙 | 低 | 高 | 只面向鸿蒙用户 |
| uni-app | App/小程序/Web | 中低 | 一般 | 偏小程序为主的场景 |
| React Native | Android/iOS,鸿蒙支持有限 | 中 | 较低 | 已有 RN 技术栈的团队 |
1.3 功能列表与产品边界
在真正动手写代码之前,我先明确了项目的功能边界,避免做到一半失控。这个应用的 MVP 版本包含以下功能:
- 打印店门店列表:展示当前营业状态的打印店、位置、当前排队人数
- 排队状态页:某家门店的实时叫号进度、预计等待时间
- 取号/排队登记:学生可在线取号,记录取号时间
- 排队进度刷新:每 30 秒自动拉取一次最新队列状态
- 通知提醒:排队即将轮到前推送系统通知
- 个人排队记录:查看历史排队记录和平均等待时长
不做的功能(至少第一版不做):在线支付、文件传输、评论系统。这些功能会显著放大项目复杂度,但和“排队查询”这个核心价值关系不大。
边界确定后,开发工作量就变得可控了。前端核心页面 3 个,后端接口 8 个,数据库表 5 张,一个两周内可完成的体量。
2. 把 Flutter 跑在鸿蒙上:适配原理、环境搭建与典型报错排查
2.1 先搞清楚 Flutter 鸿蒙版本兼容现状
很多人第一次接触“Flutter 开发鸿蒙应用”会有一个疑问:Flutter 不是 Google 的东西吗,鸿蒙不是华为的东西吗,这两个怎么能搞到一起?
答案是:Flutter 的底层渲染引擎是 C++ 写的,Dart 虚拟机也可以编译成多种目标平台的原生代码。只要有人把 Flutter 引擎的 Platform Channel 和渲染后端适配到某个操作系统上,Flutter 就能跑在那个系统上。OpenHarmony 社区维护了一个 Flutter 的适配分支,叫 flutter_flutter,它会把 Flutter 引擎编译成 OpenHarmony 的动态库(.so),再通过 Platform Channel 把系统能力桥接到 Dart 层。
我在实际开发中把整个适配路径梳理成了下面几个环节:
- Dart 代码被编译成机器相关代码(AOT)或由 Dart VM 解释执行
- Flutter 引擎负责 UI 布局、渲染、事件处理
- 引擎通过 FlutterOpenHarmony 插件调用 OpenHarmony 的 API
- 系统能力(通知、网络、存储)通过 Channel 暴露给 Dart 层调用
这也就解释了为什么 Flutter 支持鸿蒙的性能表现并不差——核心渲染逻辑仍然在 Flutter 引擎内部完成,鸿蒙只是提供了一个寄生的宿主环境。这一点和 Flutter 在 Android 上的运行逻辑高度一致,理解了它之后,后续遇到启动黑屏、插件不生效之类的问题时,就比较容易定位是不是引擎层出了问题,而不是业务代码的问题。
2.2 环境搭建:从 DevEco Studio 到 flutter_flutter 分支
环境搭建是新手最容易卡住的环节,我把我最终的配置完整列出来:
- 安装 DevEco Studio(HarmonyOS 官方 IDE),版本建议 4.x 以上,安装时勾选 HarmonyOS SDK 和 OpenHarmony SDK
- 下载 OpenHarmony 社区维护的 Flutter SDK 分支,不要用官方 Flutter SDK
- 配置环境变量
- 在 DevEco Studio 中新建一个空工程,作为 Flutter 插件的宿主工程
- 通过 ohpm 安装依赖
# 1. 克隆 OpenHarmony 分支(以 gitee 上的仓库为例) git clone -b OpenHarmony https://gitee.com/openharmony-sig/flutter_flutter.git # 或者用镜像地址 git clone -b OpenHarmony https://github.com/openharmony-sig/flutter_flutter.git # 2. 配置环境变量 export FLUTTER_ROOT=/path/to/flutter_flutter export PATH=$FLUTTER_ROOT/bin:$PATH export PUB_HOSTED_URL=https://pub.flutter-io.cn export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn这里有一个很关键的坑要提醒:一定不要用官方 Flutter SDK 去创建项目,然后再想通过某种方式导入鸿蒙工程。我最初就是这样做的,结果在运行 flutter build 的时候直接报模块找不到的错误。原因是官方 Flutter SDK 里没有包含 OpenHarmony 平台的编译目标,只有 flutter_flutter 这个分支才有。
安装完后,可以用flutter doctor检查环境是否正常。如果本地还没有配置好 Android 工具链,doctor 会报一些警告,但只要 Flutter 本身能运行即可,因为我们最终是走 DevEco Studio 的鸿蒙编译链路。
2.3 创建第一个 Flutter 鸿蒙工程
环境准备完成后,创建工程和普通 Flutter 项目略有不同。最佳实践是:
- 先在 DevEco Studio 中新建一个 Empty Ability 工程,得到鸿蒙的宿主壳
- 在命令行用 flutter create 创建 Flutter 模块
- 把 Flutter 模块导入到鸿蒙工程中
- 在 build-profile.json5 中配置 Flutter 插件的依赖
我实际操作时的命令大致如下:
flutter create --org com.example --project-name print_queue_app print_queue_flutter然后在 DevEco Studio 中配置模块依赖。这一步在社区里有几种做法,不同版本间略有差异。我当时采用的方案是:在鸿蒙工程的 entry/oh-package.json5 中添加对 Flutter har 包的依赖。
如果你发现跑起来后页面全黑,很可能是 Flutter 引擎没有正确加载。这种情况优先检查 build-profile.json5 中是否配置了 Flutter 的 C++ 共享库路径,以及 entry 模块的 so 目录是否正确。我把这个问题列在这里,是因为它几乎是我所有同学复现时遇到最多的问题。
2.4 “运行设备不兼容”类报错的排查思路
做鸿蒙开发的人对模拟器的限制应该不陌生。官方模拟器目前只支持 ARM64 架构,所以我第一次在模拟器上跑 Flutter 鸿蒙应用时就遇到了“运行设备不兼容鸿蒙模拟器目前只能在 arm64 平台运行 jsvm”的报错。
这个报错的本质是:你的 Flutter 鸿蒙应用被编译成了 x86_64 架构的产物,但模拟器是一个 ARM64 环境,加载不了 x86_64 的 .so 动态库。
解决方案其实很朴素:要么换一台 ARM64 的机器跑模拟器(比如一些 ARM 架构的开发机),要么直接用 HarmonyOS 真机调试。我后来一直用的是真机,把手机开启开发者模式,通过 USB 连接 DevEco Studio,编译产物直接部署到真机上。真机调试不仅绕开了模拟器的架构问题,在验证通知、桌面卡片等系统能力时也更准确,因为模拟器对部分系统 API 的支持并不完整。
注意:排查这类报错时不要盲目尝试“换一个 SDK 版本”或者“重装 DevEco Studio”。先确认报错里点名的设备架构、编译架构和运行时环境三者是否匹配,比什么都重要。我见过有人在模拟器问题上折腾了一整天,最后只是换了个真机,十分钟就通过了。
3. 排队查询的核心业务:数据模型、状态管理与实时刷新逻辑
3.1 后端接口设计与数据模型
这个应用的业务逻辑其实不复杂,但数据模型的设计会直接影响后续开发效率。我最终确定了三张核心表对应的 Dart 模型:门店模型、队列条目模型、用户排队记录模型。
// 打印店门店信息 class PrintShop { final String shopId; final String name; final String location; final bool isOpen; // 是否营业 final int currentNumber; // 当前叫到的号码 final int waitingCount; // 正在等待的人数 final int estimatedWaitMinutes; // 预计等待分钟数 final double rating; // 门店评分(为后续扩展留的字段) } // 单个排队条目 class QueueEntry { final String ticketId; final String shopId; final int queueNumber; // 取到的排队号码 final DateTime issuedAt; // 取号时间 final QueueStatus status; // 排队中/已叫号/已过号/已完成 } enum QueueStatus { waiting, called, missed, finished }后端接口我按 RESTful 风格设计了 8 个接口,这里列几个核心的:
| 接口 | 方法 | 说明 |
|---|---|---|
| /api/shops | GET | 获取门店列表 |
| /api/shops/{id}/queue | GET | 获取指定门店的排队状态 |
| /api/queue/ticket | POST | 在线取号 |
| /api/queue/ticket/{id} | GET | 查询单个排队进度 |
这里有一个关键决策:排队状态的统计尽量由服务端完成,客户端只做展示。比如“预计等待时间”由服务端根据最近两小时的平均服务时长实时计算,而不是让客户端自行估算。这样当用户在多个客户端切换时,看到的数据是一致的,不会出现 A 手机上写着等 10 分钟、B 手机上却显示等 30 分钟的情况。
网络请求层我用的是 dio 库,封装了一个简单的 ApiClient,统一处理超时、错误码和 token。这个项目没有做登录注册,而是用设备号生成一个匿名用户 ID,这样用户换手机后排队记录会丢,但对于打印店排队这种低敏场景已经够用了。
3.2 用 Provider 管理“当前排队状态”
状态管理我选了 Provider,没有上 Bloc,也没有用 Riverpod。原因很简单:这个项目体量中等,主要共享状态只有两个——当前用户信息和当前选中门店的排队状态。Provider 用最小的心智负担解决了问题,对刚接触 Flutter 的读者也友好。
根组件这样组织:
void main() { runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => QueueStore()), ChangeNotifierProvider(create: (_) => UserStore()), ], child: PrintQueueApp(), ), ); }QueueStore 的核心职责是:持有当前门店的排队列表、维护轮询定时器、向 UI 层暴露“正在加载/加载成功/加载失败”的状态。每次收到新的队列数据时,它会和旧数据做一次差异比较,如果当前叫到的号码发生了变化,就触发一个事件,告诉通知服务去推送提醒。
这里要特别提醒一点:不要把网络请求直接写在 Widget 的 build 方法里。这不是某个框架的强制要求,而是 Flutter 的 Widget 在状态变化时可能被反复重建,写在那里会导致请求风暴。我见过不少初学者的代码有这个问题——页面看起来正常,打开流量监控才发现每秒钟发了十几条请求,后台打印店老板的服务器差点被打挂。
3.3 门店列表与排队状态页面
页面结构上,我设计了三个核心页面:
门店列表页:从上往下是一列门店卡片,每张卡片显示门店名称、位置、营业状态、当前排队人数和预计等待时间。列表用 ListView.builder 构建,下拉刷新触发一次手动拉取。
门店详情/排队页:这是整个应用的核心页面。顶部大字显示当前叫到的号码,中间是取号按钮,下方是一个排队状态的时间轴,展示从取号到叫号到完成的几个阶段。当用户已经取号时,页面上会有一个“当前前面还有 X 人”的提示,这个数字会随着轮询数据更新。
我的排队记录页:展示历史排队记录,按时间倒序排列,每条记录显示门店名、取号时间、最终状态。这个页面主要是为了让学生可以回顾自己经常在什么时间段去打印,顺便也方便后续接店铺优惠推送。
UI 细节上,我把“当前叫号”的字体设成了 48 号加粗,颜色用了深色背景上的高亮色。因为用户打开这个页面的核心诉求就是“我现在前面还有多少人”,这个信息必须一眼能看到。其他信息都做成次要层级,避免干扰。
3.4 实时刷新:轮询策略与缓存兜底
排队场景的实时刷新有两条典型技术路线:客户端轮询和 WebSocket 长连接。
我最终选择的是每 30 秒轮询一次排队状态接口。原因有两点:
第一,打印店叫号的频率并不高。高峰期大约每 2~5 分钟叫一个号,30 秒轮询完全能保证用户体验不差。第二,轮询在服务端实现和部署上要简单得多,一个普通的 REST API 即可,不需要维护 WebSocket 会话池,也不需要考虑断线重连的心跳机制。
代码上用 Timer.periodic 即可:
Timer.periodic(Duration(seconds: 30), (timer) async { await _fetchQueueStatus(); });需要留意的点:App 退到后台后,Dart 层 Timer 可能会被系统挂起。这时候如果想让用户仍然收到叫号提醒,不能依赖前台轮询,必须依赖推送通道。这是我在实现通知功能时踩到的一个关键问题,下面会详细展开。
另外,轮询接口要加缓存兜底。我用了一个简单的内存缓存,每次拿到新数据后和上次数据比对,如果数据没变化就不通知 UI 刷新,这样能减少无谓的页面重建,屏幕上的数字也不会“闪烁”。实测下来,一个 30 秒轮询的页面,在数据不变的情况下几乎不会触发 rebuild,只有叫号真正变化时页面才会动效更新。
4. 鸿蒙系统能力集成:通知推送、桌面卡片与权限管理
4.1 把“快轮到你”的状态推到通知栏
排队查询应用最有价值的功能不是看列表,而是在用户不需要盯着屏幕时主动告知排队进度。这就必须接入鸿蒙的系统通知。
鸿蒙的通知服务在 API 层面和其他平台差异不大,都是先申请权限,再构造通知内容,然后由系统统一展示。我用的是 Flutter 侧通过 Platform Channel 调起鸿蒙原生通知接口的方式,大致流程如下:
- 在 module.json5 中声明通知权限
- 用户首次进入应用时,通过原生代码请求通知授权
- 当 QueueStore 检测到当前叫号即将到达用户号码时,调用通知接口
Dart 侧定义一个通知管理器:
class NotificationBridge { static const platform = MethodChannel('com.example.printqueue/notification'); static Future<void> notifyQueueComing(int currentNumber) async { await platform.invokeMethod('showQueueNotification', { 'title': '打印店叫号提醒', 'content': '当前已叫到 $currentNumber 号,请准备前往打印店', 'importance': 'high', }); } }原生侧用鸿蒙的 NotificationManager 实现 showQueueNotification 方法。这块代码在 DevEco Studio 的 stage 模型下写在 EntryAbility 或对应的 Service 组件中,关键代码就是用 NotificationRequest 构建一条通知,然后调用 NotificationManager.publish 发布。
我实际运行中发现,通知功能在模拟器上偶尔会出现不弹窗的问题,但真机上表现稳定。所以如果你在模拟器上调试通知失败,不要先怀疑代码写错了,优先检查设备是否开启了通知权限、免打扰模式是否关闭。
另外有一个体验细节值得做:通知点击之后应该跳转到对应门店的排队详情页,而不是只打开应用首页。这个跳转逻辑需要用 DeepLink 或者自定义 Scheme 来实现。我在 notificationRequest 的 clickAction 里填了一个带门店 ID 的 uri,Flutter 侧再解析参数完成页面跳转。一开始我偷懒没有做,被室友吐槽“点了通知还要自己找店铺”,后来补上了,体验差距确实很大。
4.2 桌面卡片:把“当前排队人数”放到桌面上
鸿蒙系统有一个很实用的特性:原子化服务卡片,也就是桌面卡片。用户可以把卡片添加到桌面,不用打开应用就能看到当前排队人数。
这个功能在 Flutter 侧不能直接实现,因为桌面卡片是在应用外渲染的,必须用 ArkTS 卡片框架来做。我的做法是:
- 在鸿蒙工程中创建一个 ArkTS 卡片,卡片布局里放两个文本组件,分别展示门店名和当前排队人数
- 卡片的数据通过 FormExtensionAbility 从服务端拉取
- 当排队状态变化时,通过 FormProvider 更新卡片数据
- Flutter 侧只负责触发“数据更新”事件,实际更新动作由原生卡片框架完成
整个过程的技术难度并不高,但需要把“Flutter 页面”和“ArkTS 卡片”两个工程部分联合起来调试,新手经常在两个工程之间切来切去就懵了。我的建议是先分别调通两条链路——Flutter 侧能拿到数据、卡片侧能显示固定数据——再做联合调试。
实际使用中,桌面卡片比通知提醒更受欢迎。因为用户打开手机第一眼看到的就是桌面,卡片上直接显示“当前叫号:18,前方 3 人”,根本不需要解锁进入应用。我做了一个简单的 2x2 卡片,表格式布局,左侧门店名,右侧排队号码,底部还有一条细进度条表示大概还要等多久。虽然只是很小的一个组件,但它把“查询”这个动作的成本降到了零。
4.3 权限管理:鸿蒙与其他平台的差异
跨平台开发中最麻烦的事,除了 UI 适配,就是权限管理。鸿蒙的通知权限、后台运行权限和 Android 的逻辑有明显的差异。
我在这个项目中实际遇到的一个问题是:用户如果拒绝了通知权限,之后在应用内再次请求授权时,鸿蒙系统会直接静默失败,不会弹出任何提示。这和 Android 的行为一致,但和 iOS 有明显差异,iOS 会提示用户去设置页手动开启。
所以我在应用里做了一个引导页:如果检测到通知权限未开启,就展示一个“去开启权限”的卡片,用户点击后通过鸿蒙的 Intent 跳到系统设置页。这样即使用户第一次拒绝了权限,后续也有办法解锁。
权限这一点太容易被忽视了。很多跨平台开发者习惯了某个系统的权限行为,换到鸿蒙上就默认一致,结果上线后接了一堆差评。建议你在做任何跨平台鸿蒙应用之前,先把权限相关的系统行为差异列一张表,挨个验证。我自己的测试表大概是这样的:
| 权限项 | Android 行为 | 鸿蒙行为 | 需要特殊处理 |
|---|---|---|---|
| 通知权限 | 拒绝后可再次请求 | 拒绝后静默失败 | 是,需引导去设置页 |
| 位置权限 | 允许/拒绝/仅一次 | 支持类似粒度 | 否 |
| 后台运行 | 受电池优化限制 | 后台任务需申请 | 是,需申请长时任务 |
| 网络权限 | 声明即可 | 声明即可 | 否 |
5. 真机调试与发布上架:从 HAP 包到应用市场
5.1 真机调试与日志分析
开发过程中我几乎全程使用真机调试。HarmonyOS 真机开启开发者模式后,通过 USB 连接 DevEco Studio,可以直接部署调试包。
真机调试要留意一个点:如果 Flutter 侧代码改动后,直接点击 DevEco Studio 的运行按钮是够用的;但如果改了原生侧代码,尤其是 module.json5 或 build-profile.json5,需要先做一次 Clean 再重新构建。我一开始不知道这个规律,改了权限配置后总是出现“安装失败”的报错,其实就是增量编译缓存没有刷新导致的。
日志排查方面,Flutter 侧的 debugPrint 输出可以在 DevEco Studio 的 Log 窗口看到,但原生侧的日志要用 hilog 命令过滤。我常用的排查命令是下面这组:
hdc shell hilog | grep -i flutter hdc shell hilog | grep -i printqueuehdc 相当于鸿蒙版的 adb,用法也差不多。如果你对 adb 熟悉,上手 hdc 基本是无痛的。我在调试通知功能时,就是靠 hilog 看到了 “publish notification failed, permission denied” 才定位到是权限配置问题,而不是通知代码本身的逻辑问题。
5.2 一套代码出双端版本
这个项目最有成就感的时刻,是同一份 Flutter 代码同时打包出了 Android 版本和鸿蒙版本。具体来说:
- Android 版本:用官方 Flutter SDK 构建出 APK
- 鸿蒙版本:用 flutter_flutter 分支构建出 HAR 或集成到 HAP 中
这里有一个需要提前规划的点:pubspec.yaml中第三方依赖要尽量选择底层不依赖特定平台能力的库,否则鸿蒙端可能编译不过。我在项目中遇到了一个问题:某个图像处理库在 Android 上编译正常,但在鸿蒙上因为缺少原生实现而编译失败。换用另一个纯 Dart 实现的库后,问题才解决。
所以我的建议是:跨平台到鸿蒙时,尽量优先选“纯 Dart 实现”的包,其次是官方维护的支持多端包,最后才考虑那些深度依赖平台特定 API 的包。判断方法很简单,去 pub.dev 的仓库详情页看它的原生代码目录里是否包含 ohos 或 openharmony 相关文件夹。没有的话,风险就比较高,需要看它的作者是否有适配计划。
还有一点,不同平台的应用图标、启动页、权限说明文案都需要分别配置。虽然 Flutter 项目有统一的 assets 目录,但鸿蒙的 resources 目录结构跟 Android 不一样,图标文件名和尺寸规范也不一样。我一开始直接复制 Android 的图标资源到鸿蒙工程,结果启动时图标显示异常,后来按照 DevEco Studio 的资源目录规范重新导了一次才正常。
5.3 上架前必须检查的细节
应用开发完成后,上架前需要处理几个非常琐碎但重要的事项:
- 应用图标和名称:鸿蒙应用市场对图标尺寸和名称有明确要求,建议在设计阶段就按市场规范出图
- 隐私协议:应用涉及用户位置(门店位置)、设备信息,需要补充隐私协议
- 权限声明:在应用市场提交材料时,需要逐项说明每个权限的用途
- 签名证书:发布包必须使用正式的发布证书签名,测试证书构建出来的包无法上架
- 版本号和构建号:在 app.json5 中确认版本号规范,避免与已上架的旧版本冲突
这些内容看起来都是行政性工作,但它们往往决定应用能不能顺利通过审核。我个人对发布流程最大的感受是:技术上的适配只是第一步,上架前的合规准备一定要提前做,不要等开发完才开始整理材料。我们团队当时就是在提交应用市场时发现隐私协议链接还没部署,硬生生推迟了一周上架时间。
到这里,整个 Flutter + 鸿蒙的校园打印店排队查询应用就已经完整呈现了。从选题、技术选型、环境搭建、业务功能实现到系统能力集成,每一步都有明确的决策依据。尤其是 Flutter 和鸿蒙的适配部分,网上资料比主流跨平台方案少很多,真正跑通一遍之后收获是很大的。
我在做这个项目的过程中最大的体会是:跨平台方案的价值永远不在“技术酷炫”,而是用合理的成本解决真实的业务问题。一个打印店排队查询应用,如果用原生开发要维护 Android、鸿蒙、iOS 三套代码,一个小团队根本吃不消;而 Flutter 能让逻辑层和 UI 层收敛到一套代码里,把多端差异控制在壳工程层面,这才是它真正的竞争力。
如果你也想复现,建议按文章里的顺序一步步来:先把环境跑通,再写业务,最后集成系统能力。这个顺序能让你在遇到问题时更容易定位是环境问题、业务问题还是系统能力适配问题。另外,开学季的打印店排队数据高峰,正是测试这个应用并发能力和通知可靠性的好时机,我就是在九月初完成了压测和上线,效果比预期好不少。