这个系列是记录我把一套 Flutter 应用从普通 Android/iOS 目标扩展到 OpenHarmony 平台的过程。DAY 1 我把开发环境跑通、让空白项目在模拟器里亮了起来,DAY 2 反倒没急着写界面,而是先把代码托管、分支策略和备份机制定下来。原因很简单:适配 OpenHarmony 的过程中,你会反复改引擎版本、换插件、动原生桥接代码,没有一套干净的版本管理,后面每个 bug 都会变成灾难。这篇就来聊聊我最终选定 AtomGit 来做项目托管的具体理由,以及从零到一的操作过程。
我一开始天真地以为 Flutter 写鸿蒙就是换个编译目标,真正动起手来才发现,Flutter 系统架构里的渲染引擎、平台通道、插件注册方式都要重新审视。所以 DAY 2 这个选择不是偷懒,恰恰是给后面所有"折腾"铺路。
1. 先建仓再写码:为什么 DAY 2 要先折腾版本管理
1.1 适配 OpenHarmony 是个高试错过程
写普通 Flutter 应用,你大概率不需要那么严谨的版本策略:flutter create 以后一路写业务,最多隔几天 commit 一次。但一旦涉及 OpenHarmony 适配,情况就变了。你会遇到 Flutter 引擎对鸿蒙的兼容性、平台通道的重新封装、第三方插件缺失导致要在原生侧重新实现等一堆问题。我实测一个上午能改出七八个不同的"崩溃版本",如果没有 git,你想回退到半小时前那个还能跑的状态,基本只能靠记忆,非常痛苦。
所以 DAY 2 的核心目标不是学会 git 的全部命令,而是建立一套"想怎么改就怎么改、随时能回退"的安全网。这对我来说是继续往下做适配的前提。后面真正进入业务代码阶段,这套安全网会帮我把"尝试新方案"和"保住稳定成果"这两件事彻底分开。
1.2 为什么我选了 AtomGit
说实话,一开始我考虑过继续用 GitHub。项目已经在上面,团队习惯也都在。但这次的目标平台是 OpenHarmony,相关插件、引擎分支、鸿蒙侧 SDK 的适配代码,很多都发布在开源生态平台或开放原子基金会周边。AtomGit 是开放原子开源基金会旗下的代码托管平台,OpenHarmony 生态相关的项目、样例代码和 CI 模板在那边找起来很顺手,社区里经常能刷到其他团队做的鸿蒙适配经验。
另一个很现实的因素是,我平时实际网络环境中访问 GitHub 的连接稳定性不太理想,频繁拉取依赖、推送大文件时尤其明显。AtomGit 的访问速度明显更稳。这种"平台选型"没有绝对的好坏,适合才是关键。如果项目主要在 Windows 和 macOS 之间来回切换,一个连接稳定、push 不卡顿的远程仓库能省掉大量无效等待。
1.3 对比 GitHub / Gitee 后的选择逻辑
我简单列一下几个平台的使用体会,表格不涉及任何立场,纯粹是个人场景下的感受:
| 平台 | 优势 | 不太顺手的地方 | 我的场景 |
|---|---|---|---|
| GitHub | 生态最大、模板多 | 实际网络连接不稳定、Actions 速度看脸 | 保留为主,适合做镜像备份 |
| Gitee | 访问快、用户量大 | 部分功能限制、平台风格偏传统 | 可以做远程备用镜像 |
| AtomGit | 开源基金会背景、OpenHarmony 生态集中 | 社区规模还在增长 | 作为本轮适配的主仓 |
这个表格可以作为参考。核心逻辑是:高频 push、需要频繁跑 CI、依赖鸿蒙生态样例的地方,放在 AtomGit 最顺;GitHub 保留一份镜像用于备份,不影响日常开发。如果说得再直白一点,就是别把鸡蛋放在一个篮子里,但主力仓库一定要放在自己操作最顺手的地方。
2. 环境准备:AtomGit 账号、SSH 密钥和本地 Git 配置
2.1 注册账号与个人主页
注册过程本身没啥特别的,浏览器打开 atomgit.com,按提示用手机号或者邮箱注册,完成平台要求的验证就行。注册完之后建议做两件事:一是把用户名固定下来,后面仓库地址里会反复出现,别取那种又长又难记的;二是顺手在个人设置里补充一下公开资料,因为你会发现 Issue、PR、评论里都会显示这个名字。
如果你打算做开源,个人主页上放上项目列表和联系方式会很加分。如果只是自己管理私有项目,那就无所谓了,怎么方便怎么来。我自己的习惯是主页保持简洁,只放项目链接和一个能联系到我的邮箱,避免无关信息干扰。
2.2 本地 Git 的基本配置
接下来是本地环境。老生常谈,但经常有人漏:git 安装完以后第一件事是设置 user.name 和 user.email,而且要用你 AtomGit 账号绑定的邮箱。这一步漏了会导致提交记录里显示一串乱乱的 unknown 用户,后续核对 commit 完全对不上人。
git config --global user.name "你的名字" git config --global user.email "你的邮箱" git config --global credential.helper store最后一行 credential.helper store 是让 Windows 或部分 Linux 环境记住凭据,省得每次 push 都要重新输密码。注意这是全局配置,如果在公司机器上想隔离权限,也可以去掉 --global,单独给某个仓库设置。设置完之后可以执行git config --list检查一下有没有写对。
2.3 SSH Key 的生成与绑定
连接 AtomGit 我强烈建议走 SSH 而不是 HTTPS。生成密钥很简单,在终端里执行:
ssh-keygen -t ed25519 -C "你的邮箱"一路回车就行,默认会在 ~/.ssh/id_ed25519 下生成私钥和 id_ed25519.pub 公钥。用编辑器打开 .pub 文件,把内容整行复制,然后去 AtomGit 的设置页面找到 SSH Keys 入口,粘贴保存。生成 SSH Key 时如果设置了 passphrase,每次拉取推送会要求输入,个人开发图省事可以留空,但团队环境建议设置并配合 ssh-agent 使用。
绑定以后测试一下:
ssh -T git@atomgit.com看到类似成功提示,说明 SSH 通道已经通了。这一步非常值得花两分钟验证。我见过不少同事复制公钥时把换行符也复制进去,导致验证失败,所以粘贴时最好用纯文本模式,确认首尾没有多余空格。
2.4 SSH 和 HTTPS 怎么选
很多人习惯用 HTTPS 克隆。HTTPS 的优点是企业代理环境下容易放行,端口 443 基本不会被特殊处理,缺点是凭据管理相对麻烦。SSH 的好处是配置一次之后手工 push 不需要再输用户名密码,缺点是部分公司网络环境可能不放行 22 端口。
我个人的建议是:本地开发优先 SSH;如果公司网络不允许,再退化到 HTTPS 并使用访问令牌。AtomGit 的令牌在个人设置里的访问令牌入口可以生成,clone 时仍需按提示输入。从使用成本看,SSH 是"一次性配置、长期受益",HTTPS 是"网络受限时的备选方案",两者不冲突,甚至可以在同一台机器上并存。
3. 在 AtomGit 上创建 Flutter for OpenHarmony 仓库
3.1 新建仓库的关键选项怎么填
进入 AtomGit 首页,点"新建仓库"就会进入表单。这一页有几个字段需要注意:
- 仓库名:这个会出现在 clone 地址里。用 flutter-ohos-demo 这种带连字符的名字比 my_project 清晰得多。
- 描述:填上"Flutter for OpenHarmony 适配实验仓库"这种一句话说明,以后自己翻仓库列表时一眼能认出来。
- 可见性:刚开始建议私有。适配过程中会涉及平台通道代码、签名配置、密钥文件,虽然最终可能开源,但开发阶段少给自己惹麻烦。
- 初始化选项:我建议在创建时勾选生成 README、.gitignore、许可证。不一定每个都需要,但 README 和 .gitignore 在后续 push 时能减少很多冲突。
私有可见性这个决策很实用。鸿蒙适配过程中会临时存放一些 keystore、签名配置、甚至本地的 debug 证书,虽然最终可能都不需要提交,但私有仓库能避免不小心外泄。等适配稳定、确定哪些文件可以公开后,再一键切换成公开也不迟。
3.2 本地项目初始化与首次推送
如果本地已经有一个 Flutter 项目,最稳妥的做法是:在项目根目录打开终端,先初始化本地仓库,然后关联到 AtomGit 远程。
git init git add . git commit -m "chore: init flutter project for openharmony adaptation" git branch -M main git remote add origin git@atomgit.com:{用户名}/{仓库名}.git git push -u origin main注意:如果你在网页端勾选了 README 初始化,远程仓库已经有了一次提交,这时本地直接 push 会报 non-fast-forward。解决办法是先拉取合并:
git pull origin main --rebase git push -u origin main用 --rebase 而不是常规 merge,是为了让本地提交接到远程提交后面,历史更线性,后面做 code review 时每次 PR 的改动会清楚很多。这个细节新手容易忽略,但养成习惯后收益很大。
3.3 一个可直接抄的 Flutter .gitignore
Flutter 项目里最容易被不小心提交进去的文件,我踩过的坑包括 build 目录、.dart_tool、.idea、.vscode 下的用户配置,还有 Android 和 iOS 生成的临时产物。下面这份是我实际在用的模板:
# Flutter/Dart .dart_tool/ build/ .flutter-plugins* .packages *.iml # IDE .idea/ .vscode/ *.swp # 系统文件 .DS_Store Thumbs.db # 签名与本地配置 *.keystore *.jks *.p12 key.properties local.properties # 鸿蒙侧构建产物 oh_modules/ *.hap *.har务必注意:key.properties、keystore 这类文件一旦提交到公开仓库,签名密钥就泄露了。别问我怎么知道的。如果你尝试过把已有的 Android 工程嵌入 Flutter 页面,或者反过来做,还会多出一堆 Gradle 缓存和中间产物,建议同样忽略。.gitignore 不是写一次就完事,项目后期加了新类型文件,要及时补进去。
3.4 推完之后先做一次全流程验证
仓库推送成功后别急着关终端,顺手把流程完整跑一遍:换一个目录重新 clone 一份,打开项目确认能正常运行flutter pub get,拿页面上公开的 README 渲染确认远程内容没问题。有些开发者只在 src 里工作,从没验证过 clone 出来的"干净副本",结果同事或另一台设备 clone 完直接编译失败,这种问题越早发现越好。
我在 macOS 上 clone 到另一个目录跑过一次模拟器,确认 Dart 依赖都能拿到,才算真正把"仓库可用"这件事落实。这个验证过程花不了十分钟,但能把"本地能跑"和"仓库能复用"这两件事彻底对齐,避免后续陷入"我本地明明没问题"的尴尬。
4. 日常协作里真正高频的 Git 操作与分支流
4.1 分支策略:让 main 永远能编译
适配 OpenHarmony 很容易让 main 分支变成一锅粥。我的建议是最小分支策略:main 分支只保持"最近一个能正常编译、能跑起来的版本"。任何新实验,无论是换 Flutter 引擎分支、写新的平台通道、还是升级鸿蒙 SDK,都开一个 feature 分支去试:
git checkout -b feature/ohos-eventchannel # 写代码、测试 git commit -m "feat: add eventchannel for ohos native event" git checkout main git merge feature/ohos-eventchannel这样做的理由很朴素:HAP 构建产物经常因为一个原生依赖版本更新就整个挂掉,如果你直接在 main 上横冲直撞,想回退时可能连上一次能跑的提交都找不到。开分支的成本极低,但保命效果极好。我自己在适配阶段几乎每动一个原生模块就开一个分支,试完合并,不行就删分支重来。
4.2 提交信息别糊弄:规范化写法
我见过太多"update"、"fix bug"这种提交信息。每次看 log 都像在猜谜。建议使用统一的类型前缀:
- feat:新功能
- fix:修复问题
- chore:构建、工具链等杂事
- docs:文档
- refactor:重构,不改行为
- perf:性能优化
正文里一定要写清楚"为什么"。比如,我提交 "fix: adapt CustomEventChannel for OpenHarmony" 时,会顺便在提交信息里注明:之前的注册入口依赖了 iOS 侧的 MainViewController 时序,鸿蒙侧需要在 EntryAbility 的 onCreate 之后才能注册。这样几周后再回看提交历史,一眼就能知道改动背景。
不要小看这个习惯。适配项目最痛苦的往往不是代码本身,而是"当初为什么要这么改"。提交信息就是给未来的自己留的便条。尤其是涉及 Flutter 组件通信、Navigator 生命周期这类容易埋雷的地方,记录下当时的判断依据,排查问题时能少走很多弯路。
4.3 小步提交与 Tag 打点
我给自己定的规矩是:每完成一个可编译的小里程碑就提交一次,每完成一个阶段就打个 tag。比如适配初期可以这样打点:
git tag v0.1-ohos-bootstrap git push origin v0.1-ohos-bootstrap为什么要打 tag?因为 Flutter 版本升级、OpenHarmony SDK 版本升级经常带来不可控的连锁反应。有了 tag,任何时候想回到某个已知稳定的状态,一条命令就能完成。你不需要记住复杂的 commit 哈希,tag 就是给版本起的名字。特别是当"安卓原生项目嵌入 Flutter 页面"这个需求加入后,原生和 Flutter 的耦合变深,回滚点必须非常明确。
我习惯把 tag 命名为 v0.1-ohos-xxx 这种可读性强的格式,一眼就能看出这是鸿蒙适配初期的哪个阶段。等整个适配闭环跑通,再回过头整理正式版本号也不迟。
4.4 多设备同步的正确姿势
很多 Flutter 开发者手上有不止一台机器:公司在 Windows,家里是 macOS。AtomGit 作为远程仓库天然解决同步问题。但有个细节值得注意:不要在 push 前不看状态就盲目执行。稳妥的流程是:
git status # 先看有哪些改动 git pull --rebase # 同步远程改动 git push # 推送自己的改动如果你忘了 pull 直接 push,大概率会撞上 rejected 提示。面对这类提示别慌,先查一下远程是不是有提交。如果确实只是自己两台机器之间的同步,git pull --rebase基本能解决一切。在 Windows 和 macOS 之间来回切换时,我还会留意行尾符差异,虽然 Git 通常能自动处理,但偶尔也会出现整文件 diff 的情况,这时候先检查 .gitattributes 配置比手动改代码高效得多。
5. 从个人项目走向协作:Issue、PR 和 CI 接入
5.1 用 Issue 管理适配任务清单
OpenHarmony 适配往往不是单线程任务,我把手头的待办全挂在 Issue 里:字体渲染待验证、事件通道待重构、推送插件缺鸿蒙实现。这样做有几个好处:每件事的上下文都被记录下来;哪些完成哪些没有,一眼可知;如果之后有人加入协作,Issue 就是最自然的任务交接列表。
写 Issue 时我会按"复现步骤 + 期望行为 + 实际行为 + 环境信息"来写。特别是环境信息,Flutter 版本、OpenHarmony SDK 版本、引擎分支版本都必须写清楚,因为同样的代码在不同版本上的表现可能完全不同。我见过最离谱的 Issue 就是只说"跑不起来"三个字,这种信息对排查毫无帮助。
5.2 Pull Request 到底是给谁看的
一个人开发时,PR 看起来有点多余,但我的经验是:哪怕是自己合并自己的 PR,也要走一遍完整流程。原因是 PR 页面会展示 diff,你能以"审查者"的身份重新审视每一行改动。尤其对于平台桥接代码,这种二次审视能抓住不少低级问题。比如 EventChannel 方法的命名、日志级别、生命周期钩子放的位置,都很容易在自审时发现问题。
如果团队协作,PR 描述要写清楚三件事:这个 PR 解决什么问题、改动了哪些文件、如何验证。Code Review 时重点看的是原生侧代码和 Flutter 侧注册时机,这些地方出了问题通常不会第一时间报错,而是表现为偶发异常。我自己在 review 时经常盯着生命周期相关的改动不放手,因为这类问题在鸿蒙适配里出现频率实在太高了。
5.3 接一个最简单的 CI 工作流
版本管理最终要服务于自动化。AtomGit 提供了基于 YAML 配置的流水线功能,可以在推送时自动执行flutter analyze和flutter test,至少在合并前把静态问题挡在门外。我没有在这里贴一份固定模板,因为不同分支的 Flutter 环境差异很大,特别是 OpenHarmony 引擎分支,每个人的 SDK 路径和依赖源可能都不一样。
建议的接入顺序是:第一步只跑 Dart 层的分析和单测,不涉及鸿蒙构建,因为这一步对环境要求最低;第二步再尝试在流水线里执行鸿蒙侧构建,此时需要提前在平台侧配置好 SDK 路径、环境变量和签名文件。优先级一定是"先保证 analyze 和 test 通过",鸿蒙构建放到后面再说。跑了 CI 之后,你会发现很多低级错误在推送阶段就被拦住了,而不是等到合并后才爆出来。
5.4 把适配代码回馈给上游插件
一旦你在自己仓库里把某个 Flutter 插件的 OpenHarmony 适配跑通了,可以考虑把适配代码以 PR 形式回馈给上游。这不只是为爱发电,现实中这些插件很可能就是你以后要持续维护的。把适配过程挂在 Issue 里,把 commit 记录整理清楚,上游合并时你的工作量也能得到体现。
对接插件时,最常见的坑是事件通道(EventChannel)在鸿蒙侧的注册时机,以及方法通道(MethodChannel)在页面生命周期重启时恢复。这些内容在主干代码里往往没有专门文档,需要靠你自己 debug 后沉淀成注释,或者写成文档提交上去。我在适配过程中遇到的 Flutter 页面切换后状态丢失问题,最后就是在这些插件代码里找到了根因——原生容器在重建时没有重新注册通道。
6. 常见问题与排查技巧实录
6.1 推送失败类问题速查
下面这张表是我在实际使用 AtomGit 过程中遇到的推送相关问题的汇总,按出现频率排序:
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| Permission denied (publickey) | SSH 密钥没绑定,或绑错机器 | 重新查看 ~/.ssh/id_ed25519.pub,到平台重新添加 |
| non-fast-forward 推送被拒 | 远程有本地不知道的提交 | 先 git pull --rebase,解决冲突后再 push |
| 卡在输入密码 | 用了 HTTPS 且未存 token | 改用 SSH,或配置 credential helper 保存 token |
| 大文件 push 超时 | 误提交了 build 目录或 HAP 包 | 删除大文件,重写 commit 历史或用 LFS |
推送失败时第一反应不要想着强制覆盖,尤其是协作仓库,先确认远程状态、看看最近几条提交是谁写的,再决定下一步。强制 push 会把别人的工作推没,这种事干一次团队信任就没了。就算只是个人仓库,我也建议养成先 pull 再 push 的习惯,避免养成依赖强推的坏毛病。
6.2 Flutter SDK 版本与提示不匹配
很多人在配置 Flutter 开发环境时见过这句提示:the current configured flutter sdk is not known to be fully supported。尤其是当你为了适配 OpenHarmony 换了引擎分支时,IDE 里 Flutter 版本识别就会出现这种警告。
我的处理思路是:不忽略警告,但也不必被它吓住。先去确认当前 Flutter 版本是否满足项目依赖要求。如果项目里既有 Android 构建又有鸿蒙构建,建议用 fvm(Flutter Version Management)锁定每个项目需要的 Flutter 版本,避免不同机器上环境漂移。最简单的做法是在仓库根目录写清楚版本要求,或者在 README 的环境说明里注明 Flutter 版本和鸿蒙 SDK 套件版本。另外,如果你同时维护着 iOS 构建,Xcode 升级后经常出现"很多 Flutter 包报版本低"的现象,那是另一个独立的兼容性问题,排查时别和鸿蒙适配混在一起。
6.3 Gradle 插件配置方式迁移
用 Flutter 创建的项目默认在 android/settings.gradle 里会有一段插件声明。但如果你把项目升级到比较新的 Gradle 版本,经常会在构建时看到类似 "you are applying flutter's main gradle plugin imperatively using the apply script" 的提示。这其实是构建脚本在呼吁你改用 plugins DSL 的方式去声明 Flutter Gradle 插件,而不是用 apply from 的旧方式。
处理方法并不难:在 android/settings.gradle 里加上 Flutter 插件声明,并移除 build.gradle 里对应的 apply 脚本。由于 Flutter 版本不同,具体写法也会有差异,最稳的方案是让 flutter create 重新生成一个同版本的新项目,然后把新项目里的 android 构建脚本搬过来做对照,别凭记忆手抄。这种问题在鸿蒙适配过程中很容易被忽略,因为主要精力都在原生侧,但一旦真的踩中,排查起来非常费时间。
6.4 文件名大小写与缓存问题
在 Windows 上开发 Flutter,如果代码里引用了 EventChannel.dart,但磁盘上实际文件名是 eventChannel.dart,拉取到 macOS 或 Linux 上就会编译失败。Windows 默认不区分大小写,所以这种问题在自己机器上根本发现不了。
处理办法:修改文件名后执行 git mv 确保 Git 记录变更,同时在仓库根目录设置git config core.ignorecase false,让 Git 开始跟踪大小写变化。同样的道理,改完依赖、升级版本后,如果 Flutter 命令表现异常,优先执行flutter clean并删除 build、.dart_tool 目录后再尝试,很多诡异问题其实是缓存作的妖。实践里这个排查思路能解决 70% 的"我改了代码但行为没变"类问题。
DAY 2 的内容说实话没有写任何一行业务页面,但它给后面几天的适配工作省了太多事。我个人实际操作下来的感受是:版本管理这件事,配置成本远没有我们想象中那么高,反而是出了问题以后想补救的成本高得吓人。如果你也在做 Flutter 往 OpenHarmony 迁移,别管项目多小,先把仓库、分支、SSH、.gitignore 这几件事弄利索。后面每一次崩溃的时候,你都会感谢昨天把安全网搭好的自己。最后再分享一个小技巧:给每次能正常跑通的版本打上 tag,哪怕 tag 名字很丑。等 Flutter 引擎或鸿蒙 SDK 升级出问题的时候,你会明白一个能一键回去的稳定版本,比任何花哨的文档都管用。