ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Flutter鸿蒙开发实战:以连连看游戏跑通跨平台全链路

Flutter鸿蒙开发实战:以连连看游戏跑通跨平台全链路 最近在搞一个 Flutter 跨平台项目目标平台里加上了 HarmonyOS。说实话接到这个需求之前我自己也有点打鼓Flutter 在鸿蒙上的适配到底成不成熟社区资料够不够踩坑后能不能快速爬起来都没底。但项目不等人我干脆选了一个功能完整、规模又可控的小游戏——连连看先用它把整条链路跑通再把经验沉淀下来。现在回头复盘这条路走出来的价值比预期大很多Flutter 框架的跨平台能力在鸿蒙上不但能落地而且完成度相当高一套业务代码在安卓、iOS 和鸿蒙三个平台几乎原样复用真正把“跨平台鸿蒙开发”从概念变成了可交付的工程实践。这篇内容完全围绕实战展开一次性讲清楚为什么敢用 Flutter 去做鸿蒙原生应用连连看这种小游戏在 Flutter 里怎么拆分功能、怎么写核心算法鸿蒙工程怎么对接 Flutter 引擎页面路由、EventChannel、PlatformView 这些能力在鸿蒙端是什么状态以及最后的打包签名和上线准备。整个过程踩过的坑、调试过的细节、排查过的诡异报错我都会写出来。适合三类人看一是准备把 Flutter 技术栈扩展到鸿蒙的跨端开发者二是想找一个麻雀虽小五脏俱全的练手项目来熟悉鸿蒙开发流程的朋友三是纯粹想看 Flutter 游戏逻辑怎么实现的同学。内容比较长建议收藏后按章节看每段都能单独落地。1. 项目全貌与整体设计拆解1.1 为什么用 Flutter 交付鸿蒙应用先聊一个很多人在立项时会纠结的问题鸿蒙自己的开发范式已经起来了ArkTS 加方舟编译器生态文档也不算少为什么还要绕一圈用 Flutter 来做我的判断标准很简单看你的团队技术资产在哪、需要覆盖哪些平台。如果你们只有一个 HarmonyOS 的交付目标那老老实实用鸿蒙原生技术栈没毛病那是它的主场。但绝大多数团队的实际情况是安卓、iOS 已经有一套成熟的 Flutter 业务代码这时候鸿蒙市场也想要难道要再单独养一个 ArkTS 团队人员成本、维护成本、版本同步成本都是实打实的。Flutter 从底层 engine 到上层 framework 都是开源的社区早就有人在做鸿蒙平台的嵌入层适配Dart 代码跑在鸿蒙设备上已经不是理论验证而是有真实产品在走。用 Flutter 交付鸿蒙版业务层代码几乎是零迁移只需要把平台通道、插件依赖、原生入口这几个层面打通就能在鸿蒙上跑起来。另外说一个实在的观察连连看这种游戏类应用逻辑密度高、页面频繁刷新、动画交互多正好是 Flutter 渲染引擎表现力最强的场景。用它来验证 Flutter 在鸿蒙上的 UI 渲染一致性比做一个表单类 CRUD 应用有价值得多。我实测下来Flutter 的布局系统在鸿蒙端跟安卓端的表现差异很小Impeller 渲染引擎在鸿蒙的适配也已经在逐步推进动画帧率基本没有感知层面的落差。1.2 连连看项目的功能模块拆分既然是全流程教程咱们先把项目切成模块后面每一章都对应一部分。连连看看似简单但认真拆起来模块并不少。核心业务层我分成四大块。第一块是棋盘的生成与管理包括二维数组的数据结构、牌面随机打散、保证有解性的初始化策略这是所以逻辑的地基。第二块是连接判定算法也就是连连看最核心的规则两张相同的牌能不能消要看它们之间能不能用最多拐两个弯的路径连接这个算法决定了整个游戏的手感。第三块是交互反馈包括点击牌面的高亮、消除动画、连线路径的可视化绘制、剩余牌数不足时自动洗牌这些细节直接决定玩家体验。第四块是辅助系统包括倒计时、计分规则、失败与胜利的判定、音效播放这些看起来边缘但放到鸿蒙端往往会牵扯到平台通道能力后面第四章会专门讲。技术栈选型上我给这个项目定的调子是“够用就好不炫技”。状态管理用了最简单的 Provider没有上 Bloc 或者 Riverpod原因很直接项目体量不大页面层级也不深Provider 的 notifyListeners 足够覆盖所有状态变更代码清爽还容易调试。有朋友问为什么不用 Cubit其实看个人习惯如果你团队已经熟练 Cubit 那套事件流思维用到这个项目上也没问题核心算法和平台层是跟状态管理方案解耦的。1.3 这套设计的优势与避坑思路跨平台最怕什么最怕在某个平台写一套独有逻辑改了一个平台忘了另一个平台。所以我在设计阶段就定了三条规矩。第一业务逻辑层全部用纯 Dart 实现禁止直接调用 dart:io 里面平台相关的 API文件读写、路径获取这类能力统一封装成接口由各个平台去实现。这样连连看的牌面数据、连线算法、计分规则这些核心代码可以在三个平台完全复用不存在分支差异。第二平台通道集中在专门的 adapter 层管理所有 MethodChannel、EventChannel 定义在一个目录下方法名常量收敛在一起。这样鸿蒙端对接的时候只需要看这一层的代码不会出现到处散落着平台调用的情况。第三UI 组件不做平台判断不搞“如果是鸿蒙就换一套布局”这种写法。Flutter 的组件库本身在鸿蒙上的还原度已经很高强行分平台写反而破坏一致性。唯一的例外是 PlatformView也就是需要嵌入鸿蒙原生控件的场景这个在第四章单独讲。这三条规矩在后面真正对接鸿蒙时帮我省了非常多时间。因为业务层足够干净鸿蒙端要做的事情就变得很聚焦实现 Flutter 引擎的入口适配平台插件处理生命周期和系统事件仅此而已。2. 连连看核心算法与棋盘实现2.1 棋盘数据结构与发牌逻辑连连看的棋盘本质上就是一个二维数组每一项记录当前位置的牌面 ID。我用的数据结构是 ListList 0 表示空格非 0 表示对应 ID 的牌。用 int 做牌面 ID 有几个好处比较相等快洗牌算法好写调试时打印出来一目了然。棋盘尺寸我设计成 8 行乘 10 列总共 80 个格子。因为连连看需要两两配对所以牌面总数必须是偶数80 个格子对应 40 种不同的牌面 ID。为了不让牌面种类多到难得消完我把每种牌的数量固定为 2 张正好铺满棋盘。这里有个细节8 乘 10 是 80但如果你直接生成 40 种 ID、每种 2 张插进去很容易出现连续相同牌面挨在一起玩家一把就能消掉一大片游戏就直接结束了。所以要加一步打散和邻位规避。发牌逻辑我写得比较直白先准备一个列表里面按顺序塞入 40 组“两个相同 ID”然后用洗牌算法乱序逐个填入二维数组。洗牌用的是 Fisher-Yates 算法标准做法时间复杂度 O(n)。不过这里有个实际诉求随机洗牌后的棋盘有可能出现无解局面也就是没有任何一对牌能在三线内连接。为了保证开局一定有解我加了一个校验函数扫描所有牌对只要存在至少一对可消除项就认为棋盘是“活”的如果校验不通过就重新洗牌最多重试 N 次。实测下来8 乘 10 的棋盘规模重试概率很低性能完全没有压力。ListListint generateBoard(int rows, int cols, int pairCount) { Listint pool []; for (int i 0; i pairCount; i) { pool.add(i 1); pool.add(i 1); } // Fisher-Yates shuffle for (int i pool.length - 1; i 0; i--) { int j Random().nextInt(i 1); int tmp pool[i]; pool[i] pool[j]; pool[j] tmp; } ListListint board []; for (int r 0; r rows; r) { board.add(pool.sublist(r * cols, (r 1) * cols)); } return board; }我之前遇到过一个问题洗牌后打印棋盘发现同一种牌刚好出现在相邻位置。这个虽然不违反规则但会让开局白送一对消除体验不太好。后续处理是在洗牌完成后加一个邻位去重的扫描如果检测到相邻相同牌就再做一轮轻量交换。这属于体验细节但用户对连连看的手感敏感这种细节值得做。2.2 连线判定算法原理与实现连线判定是连连看的灵魂。规则是选择两张相同牌如果它们之间能用一条最多拐两次弯、且中间全部是空格的折线连接就可以消除。这里“最多拐两次弯”要拆开看因为实际判定可以分三种场景直线相连、一个拐角相连、两个拐角相连。直线相连最简单行相同或者列相同从 A 到 B 中间所有格子都是空格。这里要注意一种边界情况两张牌相邻那直接就是可消除的因为中间没有任何格子也可以算作直线连接。一个拐角的情况本质上是找矩形的两个对顶点。假设 A 和 B 的坐标分别是 (r1, c1) 和 (r2, c2)第一个拐点要么在 (r1, c2)也就是同行同列的交叉点要么在 (r2, c1)。只要拐点位置是空的并且 A 到拐点、拐点到 B 这两段各自直线连通就判定成功。这个逻辑用代码写就是两次直线判断。两个拐角的情况复杂一些但有个朴素思路在两个拐点的场景里总有一条水平或垂直的扫描线同时经过 A 和 B 的某条行或列。具体做法是枚举 A 点所在行和 B 点所在行之间的每一行或者枚举列之间的每一列在每个候选行上检查是否有空位能同时连通到 A 和 B。这个思路其实就是 BFS 的一种简化形式因为连连看的棋盘规模有限直接枚举的性能开销完全可以接受。bool canConnect(ListListint board, Position a, Position b) { if (board[a.row][a.col] ! board[b.row][b.col]) return false; if (a.row b.row a.col b.col) return false; if (isLineEmpty(board, a, b)) return true; if (isOneCornerConnect(board, a, b)) return true; if (isTwoCornerConnect(board, a, b)) return true; return false; }我实际开发中在 isTwoCornerConnect 里踩过一个坑枚举扫描线的时候很容易漏掉棋盘边界外的虚拟空位。因为连连看是允许路径从棋盘外面绕过去的也就是“边缘连接”比如第一行牌要跟棋盘外的路径相连时路径是贴着棋盘外圈走的。处理办法是扩展一圈虚拟边界把所有越界索引看作空格处理算法逻辑明显简化边界条件统一了。如果你刚开始写建议先直接按“不绕外圈”实现调通了再扩展外圈这样排查问题的位置会清晰很多。2.3 游戏状态机与消除流程有了判定算法游戏的执行流程就是一套状态机等待选取第一张牌、等待选取第二张牌、判定连接、成功则消除并更新分数、失败则清空选中并给一个抖动动画。状态管理用 Provider 的话核心是三个状态变量当前选中牌、已消除的牌面集合、剩余时间。每点击一次方格走一遍状态机方法内部判断下一步是什么。这里有一个交互上的细节玩家连续快速点击时状态机必须保证不会出现重复消除或误消。我的做法是在状态转换期间加一个 isProcessing 标志位动画播放过程中忽略新的点击事件动画结束后再恢复。这是一个很小的保护逻辑但少了它你在真机上狂点屏幕很容易看到两张牌刚被消掉又冒出来的视觉 bug或者计数对不上。另外计分规则也要提前想清楚。我用的方案是基础分 10 分一次消除两张牌如果同一个位置连续消除形成连击每连击一次额外加 5 分。这样玩家会有意识地规划消除顺序而不是盯着牌面乱点。时间方面我用的是 180 秒倒计时时间耗尽游戏失败棋盘被清空则游戏胜利。为了让失败后牌面不全屏保留我加了一个“结束覆盖层”半透明遮罩加上重新开始的按钮这种处理比直接清空棋盘更有仪式感玩家也更容易理解当前状态。3. 从 0 到 1 实操流程环境搭建与项目初始化3.1 Flutter 环境准备与 SDK 版本选择先说环境这是所有工作的起点。我当时在 macOS 上操作Flutter SDK 用的稳定版渠道版本号具体是 3.24 系列这个版本对鸿蒙适配的支持比较稳。鸿蒙侧的开发环境我装了 DevEco Studio它是鸿蒙应用开发的官方 IDE负责创建鸿蒙工程、编译 HAP 包、连接真机调试。注意Flutter SDK 本身不天然带鸿蒙支持需要额外拉取鸿蒙平台的 runner 工程模板这一块后面会说到。这里有个非常容易踩的坑版本匹配问题。Flutter 的版本和鸿蒙适配插件的版本之间存在对应关系如果你用的 Flutter 是最新版但适配插件还没有跟上编译时会直接报类似 “The current configured Flutter SDK is not known to be fully supported” 的警告甚至直接失败。所以我建议不要盲目追新看社区文档里推荐的 Flutter 和鸿蒙引擎版本组合锁定一个稳定组合后再动手。准备工作还包括环境变量确保 flutter 命令、ohpm 命令、java 命令都能在终端直接执行否则后面构建 HAP 包的时候会卡在很莫名其妙的位置。flutter --version ohpm --version java -version3.2 创建 Flutter 项目并配置鸿蒙平台目录环境就绪后创建项目本身不复杂核心是让 Flutter 工程能生成鸿蒙平台目录。标准做法是先建一个常规 Flutter 项目然后在项目根部执行鸿蒙适配脚本生成 harmony 平台目录。完成之后工程结构里会多出一个类似 harmony 的目录里面是完整的鸿蒙工程文件包括 entry 模块、oh-package.json5、module.json5 这些原生应用必备的东西。这里我说说目录映射的逻辑理解了就不会慌。Flutter 项目的 lib 目录是跨平台共享的业务代码这个不动。安卓的 MainActivity 在 android/app/src/main/java 下iOS 的 AppDelegate 在 ios/Runner 下鸿蒙的入口则在 harmony/entry/src/main/ets 下通常是一个 EntryAbility.ets。后两者本质上都是承载 Flutter 引擎的宿主壳职责是创建 FlutterView、注册平台通道、管理页面生命周期。如果项目是全新的直接跑 flutter create 再执行鸿蒙适配命令就行。如果项目是老的里面已经有一堆依赖和平台配置就要小心了适配脚本自动生成的文件可能覆盖你手写的自定义配置。所以老项目迁移前务必先把 android 和 ios 目录备份然后在版本管理里看清楚 diff 再提交。我自己就吃过这个亏格式化后没仔细看某个自定义的 manifest 配置被冲掉了排查了半天。3.3 Flutter 页面 UI 落地布局、动画与导航UI 层我用了一套非常标准的 Flutter 组件组合。游戏主界面用一个 Stack 叠三层底层是棋盘组件中间层是连线绘制画布最上层是操作栏和遮罩层。棋盘组件内部用 GridView.builder 渲染牌面格子每一格是一个自定义的 CardWidget点击事件通过回调抛给状态管理。关于牌面格子建议把 CardWidget 做细一点它既要展示牌面图标又要表达两种选中高亮状态还要播放消除时的一个缩小淡出动画。我用 AnimatedScale 包了一层消除时 scale 从 1 到 0时长 180 毫秒再加一个 Opacity 渐变视觉上很舒服。因为这个动画本身是异步的记得把状态机的 isProcessing 和动画的启动放在同一个流程里避免点击穿透。导航这块我用的是 Navigator 1.0 的普通路由没上 go_router原因还是项目体量小。但从游戏页跳转到结算页时注意一个细节Flutter 页面切换后游戏页的状态不会自动丢失因为 Provider 保存在 MaterialApp 之上但如果你用 push 了一个新页面且没有用透明路由游戏页会被完全遮盖。如果你希望用户切到后台再回来时倒计时不重计那需要结合生命周期处理这个在第四章细讲。有一点特别值得强调在鸿蒙端页面显示和隐藏对应的生命周期事件和安卓是有差异的。Flutter 框架在鸿蒙的适配层会把这些事件映射成 Flutter 的 AppLifecycleState但映射的触发时机可能存在偏差尤其是从桌面回到应用时onResume 对应的状态恢复可能比安卓晚一帧。所以挂钟类、倒计时类的逻辑不要依赖生命周期回调来“补时间”而应该用系统时间差值计算从根上避免平台差异。4. 鸿蒙端适配EventChannel、原生页面跳转与平台视图4.1 MethodChannel 与 EventChannel 的鸿蒙实现跨平台做鸿蒙躲不开的一个话题就是平台通道。连连看这个项目用到的原生能力不算多主要有三个震动反馈、音频播放、后台任务暂停逻辑。这几个能力如果每个平台写一套代码会散得很难看。我的做法是把它们统一到一个 NativeBridge 的 Dart 类里对外暴露方法内部按平台走不同的 MethodChannel 实现。以震动为例安卓端调用的是 Vibrator 服务鸿蒙端对应的能力在 ohos.vibrator 模块里。Flutter 侧发起一次 MethodChannel.invokeMethod(vibrate)鸿蒙侧在 onMethodCall 里分发到 vibrator 的 startVibration 接口。逻辑不复杂但有一点要注意鸿蒙的权限配置比其他平台更严格一些系统能力需要先在 module.json5 里申请权限否则调用时会被拒。震动属于轻量级能力不高危但音频播放如果走系统媒体库权限就敏感一些要提前在文档里确认。EventChannel 的场景在这个项目里也有体现。我当时做了一个小功能游戏倒计时剩余 30 秒时如果玩家在鸿蒙真机上按了返回键退到桌面我希望游戏能自动暂停而不是继续在后头默默倒计时。做法是在鸿蒙侧监听应用前后台切换事件通过 EventChannel 把事件推给 FlutterFlutter 收到后调用状态机的暂停逻辑。这类事件推送用 EventChannel 是非常标准的姿势但放到鸿蒙端有一个特殊性Flutter 在鸿蒙上的 EventChannel 底层链路刚适配时版本差异较大事件流注册时机如果早于 FlutterView 完全初始化会出现事件丢失。稳妥的解法是在 Flutter 侧延迟注册 listeners或者在鸿蒙侧做一个事件缓存等 Flutter 侧注册成功后再补发。static const EventChannel _lifecycleChannel EventChannel(app/lifecycle); StreamSubscription? _sub; void initLifecycleListener() { _sub _lifecycleChannel.receiveBroadcastStream().listen((event) { if (event background) { gameViewModel.pauseTimer(); } else if (event foreground) { gameViewModel.resumeTimer(); } }); }4.2 鸿蒙原生 Ability 跳转与返回连连看做到后期我想加一个“排行榜”入口点击后跳到一个用原生页面写的战绩面板。跨端框架要跳原生页在安卓上是 startActivity在鸿蒙上则是通过 featureAbility 或者最新的 Context 接口去拉起对应的 Ability。Flutter 侧我定义了一个方法 gotoNativeRanking通过 MethodChannel 唤起鸿蒙原生页面。这里有个架构层面的问题要想清楚跳出去之后Flutter 引擎的页面生命周期会怎么走在鸿蒙的实现里拉起原生 Ability 时Flutter 宿主页面默认是退到后台的但并不会销毁。为了不让倒计时在后台继续跑我在跳转前主动暂停了所有游戏计时并在路由回来时用上面的 EventChannel 事件恢复。这种体验细节不处理好用户会感觉从原生排行榜回到游戏倒计时莫名其妙少了一截。回传数据是另一个要点。原生排行榜页面里用户可能刷新了最高分这个数据需要回传给 Flutter 里的 Dart 层。HarmonyOS 上 Ability 之间的数据回传有自己的一套机制但如果你只是从原生页面回 Flutter 宿主页面最直接的方式是在原生页关闭前调用之前保存的 FlutterView 的通道方法把成绩 push 给 Flutter。别在这里绕太多跨 Ability 的复杂通信Flutter 引擎本身就是天然的桥梁你只需要保证通道实例还是同一个。4.3 PlatformView 在鸿蒙端的嵌入实践PlatformView 是 Flutter 用来嵌入原生视图的能力连连看里我用它做了一个小实验把鸿蒙原生的广告条控件嵌到 Flutter 的页面中。为什么不直接用 Flutter 重写广告条因为商业广告 SDK 往往是原生的对接一个完整的原生 SDK 比把一个原生 view 嵌进 Flutter 更复杂PlatformView 是一条成本低很多的路线。鸿蒙端的 PlatforView 适配和安卓的版本差别比较大安卓有完整的 VirtualDisplay 和 Hybrid Composition 两条路径而鸿蒙的 Flutter 嵌入层把平台视图直接作为 FlutterView 的一个子视图叠加。这种实现方式在大多数场景下够用但会遇到一个典型的平台差异问题Flutter 页面里的原生视图不能直接参与 Flutter 的 transform 动画比如你给广告条加了一个缩放或位移动画在安卓上因为走的是 TextureLayer 可以做到部分同步在鸿蒙上就会出现原生视图不动、周围 Flutter 内容在动的撕裂效果。这个不是 bug是实现机制决定的所以设计页面时把 PlatformView 放在不参与动画的位置或者干脆用淡入淡出这类不会改变几何位置的过渡。另外跟我一样的同学如果也把 PlatformView 放进 Stack 里注意它的层级。鸿蒙的原生视图默认可能浮在 Flutter 内容的最上面就算 Flutter 的 Stack 里把其他组件放在它之上也可能遮挡不住。这种情况没有通用完美的解法现实的做法是调整布局把原生视图放在页面底部或独立区域避免重叠。5. 调优、打包与常见问题实录5.1 性能优化棋盘渲染与内存占用我把整个棋盘跑起来后先做了三分钟真机实测。Flutter 在鸿蒙上的渲染整体流畅但在真机上玩到中后期棋盘上只剩十来张牌时点击响应会有一点点迟滞。排查下来发现瓶颈不在 Flutter 渲染层而是我操作二维数组时频繁调用 sublist 复制了太多临时列表。优化方案很简单全部改成直接对二维数组做索引操作减少中间层分配。牌面格子的 Widget 重建也是可以优化的点。GridView.builder 本身已经做了懒加载但我给每个格子用的时 AnimatedScale 包了多层组件不规则动画会比较频繁地在每帧重建整棵子树。优化方式是在牌面 setState 发生时只让受影响的格子 rebuild利用 RepaintBoundary 把每一格的绘制隔离出来。加上之后帧率很稳定。内存方面连连看的图标素材我用的都是矢量 icon 加字体没有任何大图加载所以内存占用很低鸿蒙真机上稳定在 180MB 左右符合预期。另外提一下 Flutter 的微任务模型。我在倒计时里用一个周期性的 Timer 每秒发一次 tic 更新 UI而不是依赖 Future 的 then 回调来手动刷新是刻意选择的。因为 Future 的 then 回调会被安排到微任务队列高频率微任务在游戏事件的密集场景下可能堆积而 Timer 的调度更稳定也更容易被框架优化。5.2 鸿蒙打包 HAP 的配置与常见报错打包之前先看清楚格式差异。安卓产出的是 APK鸿蒙产出的是 HAP两者都是基于签名校验的应用包但 HAP 还有自己的描述文件要求。打包的第一步是在 DevEco Studio 里配置签名信息没有开发者证书时可以先创建一个自动签名配置真机调试就必须用正式或调试证书。签名文件路径等配置一旦错误构建时报的错非常误导人比如 “hap signing config not found”你检查半天权限配置发现都没问题最后才想起来签名没配。说说我实际踩过的一个坑Flutter 打包过程中的 Java 报错。当时跑构建命令报了一个 java.lang.AssertionError后面跟着 could not close 某个临时文件。表面看是完全无关的资源文件问题我一度怀疑是磁盘权限。最后定位下来是 Gradle 和当 JDK 版本不匹配导致的临时文件清理失败。解决办法是统一用 DevEco Studio 内置的 JDK或者设置 JAVA_HOME 指向匹配版本重新构建就过了。这个报错在社区里也有很多开发者遇到和 Flutter 代码本身没关系属于构建环境问题。还有一件事别忽略HAP 包默认只包含鸿蒙原生壳加业务代码但 Flutter 引擎本身是作为共享库打进去的所以打出来的包体积会比纯 ArkTS 应用大不少。连连看项目整体打出来 43MB 左右在可接受范围。如果你要极致压缩可以做动态特性包把引擎层延迟到用户首次启动时拉取但这需要应用市场支持而且要重新设计启动逻辑看自己的交付节奏来决定要不要上。5.3 真机联调与多平台状态一致性项目最终验收时我拿了三台设备做横向对比一台安卓手机、一台 iPhone、一台 HarmonyOS 4 的真机。三台跑同一个 Flutter 工程最让我放心的是连连看的核心算法在三个平台上行为完全一致因为算法层是纯 Dart没有任何平台差异。UI 上鸿蒙和安卓的交互几乎一致iOS 因为在字体渲染上的细微差别牌面文字清晰度稍有不同这个很正常。真机联调时的网络配置也是一个容易卡住的点。如果你要从电脑上热重载到鸿蒙真机需要确保手机和电脑在同一局域网并且鸿蒙侧对 adb 或 hdc 的调试端口开了权限。我当时就遇到 Flutter attach 到鸿蒙真机上热重载一直卡在 “Waiting for connection”最后发现是真机上没有启用开发者模式的网络调试权限跟 Flutter 本身没有关系。另外记一个“页面状态保活”的细节。Flutter Navigator 切换页面后原来的页面状态默认是保留的前提是不要用pushAndRemoveUntil或者手动销毁路由。连连看里我用普通 push 打开结算页返回后游戏界面进度完好。如果你在真机上发现返回后倒计时重新走了多半是状态管理的实例被重建了检查一下 Provider 的作用域是不是挂在页面级别而不是应用级别。5.4 从踩坑中提炼的鸿蒙 Flutter 开发习惯写到这里分享几个我这次全流程下来真心觉得可以通用化的习惯不只是针对连连看项目。第一任何涉及平台的调用都要做好等级隔离。我项目里的 NativeBridge 目录只允许放通道定义和平台实现不允许出现业务逻辑这样鸿蒙适配时是增量接入而不是重构你只需要关注新的平台实现类完全不影响主流程。第二版本锁定比追赶新版本更可靠。鸿蒙的 Flutter 适配还处于活跃演进期经常出现新版本 SDK 发布但适配插件和引擎库还没完全跟上的情况。除非你明确需要新特性否则把一个稳定组合锁死在项目文档里写清楚团队成员也不要随手升级依赖。这比什么技巧都管用。第三日志与崩溃信息要主动收集。鸿蒙的崩溃日志和安卓的 logcat 结构差别不小单纯的 Flutter 报错堆栈有时候会丢失原生侧上下文。建议从一开始就接一个统一日志模块Dart 层的 debugPrint 和原生层的 hilog 做标签关联出问题时能沿着同一串标记找线索。我在调 EventChannel 丢事件的问题时就是这么定位到是原生侧事件先于 Flutter 注册发生的。第四社区资源的价值比预期大。网上关于 Flutter 鸿蒙开发的讨论已经形成了不少解决方案沉淀很多常见的初始化失败、插件不兼容问题都有真实案例。连我这个项目里用到的鸿蒙引擎版本配置、签名报错、PlatformView 层级问题都能在社区找到类似场景的讨论。动手前先搜一搜同行的记录能少走非常多弯路。我做这个项目的体会是跨平台开发选型从来不应该是一道选择题而是一道填空题你的团队有什么技术底座你的交付要覆盖哪些平台你的业务代码能不能做到平台无关。用 Flutter 在鸿蒙上做连连看恰好把这三个问题都验证了一遍。个人的建议是如果你也打算把 Flutter 技术栈延伸到鸿蒙不妨选一个带交互、带动画、带算法但规模可控的项目先跑通全链路跑完之后你对 Flutter 和鸿蒙之间的适配颗粒度会有一个非常具象的认知这时候再投入商业化项目心里就有底了。
返回列表