ARTICLE DETAIL

资讯详情

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

Flutter Feature-First架构与鸿蒙化适配实战:CLI工具改造指南

Flutter Feature-First架构与鸿蒙化适配实战:CLI工具改造指南 1. 为什么 Flutter 工程需要 Feature-First 架构鸿蒙化又带来了什么1.1 从目录混乱到Feature-First核心思路拆解先聊一个老生常谈的问题Flutter 工程做到一定规模之后lib目录下面最容易长成什么样子lib/ models/ screens/ widgets/ services/ utils/这个结构看着规整但用上三个月就变味了。登录功能要改你要同时打开models/user.dart、screens/login_screen.dart、widgets/login_button.dart、services/auth_service.dart改完还要检查utils/validators.dart有没有被别的地方引用。功能之间互相穿透时间一长没人说得清哪些文件属于哪个业务域。我接手过不少这种工程最头疼的不是代码难写而是找个文件都费劲。Feature-First 的思路正好反过来先按业务功能切分再在每个功能内部按技术层次组织。同样是登录功能它的一切都收敛在一个login文件夹里页面、状态、数据源、模型全部内聚在一起想散都散不开。lib/ features/ login/ presentation/ application/ data/ domain/ home/ profile/ core/ network/ theme/ router/feature_folder_cli_plus 这个三方 CLI 工具干的就是把上面这套目录结构自动化的事。它通过命令行交互或参数指定自动生成 Feature 骨架、路由注册文件、依赖注入配置甚至帮你把新的 Feature 挂到现有工程里。做鸿蒙化适配时核心任务就是让这套生成逻辑同时认识 Android/iOS 和鸿蒙的工程差异而不是只认android/、ios/两个平台目录。1.2 鸿蒙化适配要解决的三个关键矛盾接触过鸿蒙应用开发的人都知道HarmonyOS NEXT 的工程结构跟 Android 有本质区别。Flutter 官方主分支目前不直接支持鸿蒙跑在鸿蒙设备上的 Flutter 依赖的是 OpenHarmony 生态适配的 Flutter SDK构建产物从apk变成了hap。这意味着 feature_folder_cli_plus 在鸿蒙化适配时至少要解决三件事。第一平台目录识别。原有工具大概率只检查android/和ios/目录是否存在、读取对应配置文件来注入平台相关代码。鸿蒙工程引入了ohos/目录OpenHarmony 侧下面有entry/src/main/module.json5、ohos.log、build-profile.json5这一套完全不同的配置体系。工具不升级它压根不会在生成 Feature 时同步创建鸿蒙侧的资源引用跑出来的工程就是个半残状态。第二资源模型差异。Android 用res/values/strings.xml定义字符串资源鸿蒙用Resources/base/element/string.jsonAndroid 的图片资源叫drawable鸿蒙用media。Feature 内部往往需要独立的资源目录一套模板很难同时吃下两套约定。第三模块化诉求。鸿蒙原生侧推崇 HAPHarmony Ability Package和 HSPHarmony Shared Package拆分一个大型应用的若干 Feature 可以拆成多个模块独立编译。Flutter 鸿蒙化工程虽然大部分逻辑在 Dart 层但每个 Feature 如果真的需要承载平台能力就必须在ohos侧同步生成对应的模块结构。feature_folder_cli_plus 做鸿蒙适配本质是在纯 Dart 层脚手架和鸿蒙原生模块脚手架之间找到合适的平衡点。2. feature_folder_cli_plus 鸿蒙化适配的核心细节2.1 工具工作原理与鸿蒙目录识别机制在动手改代码之前先把这个 CLI 工具的工作机制摸清楚。feature_folder_cli_plus 的典型执行流程分四步参数解析、项目识别、模板渲染、文件写入。参数解析没什么特殊无非是--name、--path、--platform之类的选项。项目识别是关键。工具启动后会读pubspec.yaml确认当前目录是不是 Flutter 工程然后扫描现有目录结构判断工程属于clean architecture组织、mvc组织还是完全裸奔状态。鸿蒙化要动的最重要一环就在这里感知ohos/目录及module.json5。我的做法是在项目识别阶段增加一个OhosProjectInfo探测类做三件事检查ohos/目录是否存在以及ohos/entry/src/main/module.json5能否被解析解析module.json5里的module.name、srcEntry、deviceTypes拿到鸿蒙模块的基本描述检查build-profile.json5里的signingConfigs判断当前工程是否配置了签名——这一步会在后续验证构建时派上用场。class OhosProjectInfo { final String moduleName; final String srcEntry; final ListString deviceTypes; final bool hasSigningConfig; OhosProjectInfo({ required this.moduleName, required this.srcEntry, required this.deviceTypes, required this.hasSigningConfig, }); factory OhosProjectInfo.fromDirectory(Directory ohosRoot) { final moduleFile File( ${ohosRoot.path}/entry/src/main/module.json5, ); if (!moduleFile.existsSync()) { throw FormatException(ohos/entry/src/main/module.json5 not found); } // 实际解析 json5 时建议复用 devtools 的 json5 包 final json json5Decode(moduleFile.readAsStringSync()); return OhosProjectInfo( moduleName: json[module][name] as String, srcEntry: json[module][srcEntry] as String, deviceTypes: (json[module][deviceTypes] as List) .map((e) e.toString()) .toList(), hasSigningConfig: _detectSigning(ohosRoot), ); } }这里有个容易踩的坑module.json5是 JSON5 格式允许注释和尾逗号直接用dart:convert里的jsonDecode解析会直接抛异常。需要引入json5这个 pub 包来做解析。我在第一次适配时就是图省事用了jsonDecode结果解析一个带注释的鸿蒙配置文件直接崩溃排查了半天才发现是解析器的问题。2.2 pubspec.yaml 解析与平台目录映射Flutter 工程的依赖关系最终都会落回pubspec.yaml。feature_folder_cli_plus 生成 Feature 时需要判断当前工程有没有注册路由包比如go_router还是auto_route有没有状态管理库riverpod、bloc还是provider这决定了生成的 Feature 模板长什么样。鸿蒙化适配在这个环节的增量工作是建立 Dart 包与鸿蒙原生模块的映射关系。举个例子你的 Feature 依赖flutter_ohos_platform_channel这类鸿蒙桥接包那么生成 Feature 时就需要在ohos/entry/oh-package.json5中同步加入对应依赖。原有的映射表只有 Android 的gradle依赖和 iOS 的Podfile依赖我们扩展出一张新的映射表能力域pubspec.yaml 依赖ohos/oh-package.json5 对应权限申请permission_handler需在 module.json5 中声明 requestPermissions日志输出flutter_ohos_logohos/log 或 hilog 封装文件路径path_provider需桥接 ohos.file.fs 接口平台通道flutter/services.dart自动生成 entry 侧的 Ability 扩展映射表的具体字段是dartPackageName、ohosPackageName、requiredPermissions、configTemplatePath。工具生成oh-package.json5和module.json5时查这张表补齐鸿蒙侧声明。这个设计原本不是 feature_folder_cli_plus 想要的复杂度但做鸿蒙化适配绕不开因为鸿蒙对权限和模块依赖的管理要比 Android 严格得多漏一项声明跑到真机上就是闪退或者静默失败。2.3 模板引擎的鸿蒙分支处理大部分 Flutter CLI 脚手架工具都是用简单的字符串模板加变量替换实现的feature_folder_cli_plus 也不例外。它有一套templates/目录里面放了feature.dart.tmpl、router.dart.tmpl、widget.dart.tmpl渲染时用{{feature_name}}这类插值占位符做替换。鸿蒙化适配要给模板引擎加一层平台条件分支。我的实现思路是引入template_variant概念同一个模板文件根据platform参数选择不同的片段组合。比如 Feature 的资源目录渲染 Android 变体时生成android/app/src/main/res/values/strings.xml渲染鸿蒙变体时生成ohos/entry/src/main/resources/base/element/string.json我封装了一个renderTemplate方法接收variant参数内部维护MapString, ListTemplateBlock这样的分块结构String renderTemplate({ required String templateName, required MapString, String variables, required String platform, }) { final template _loadTemplate(templateName); final blocks _splitPlatformBlocks(template); var content blocks.base; if (platform ohos) { content blocks.ohosVariant ?? ; } else { content blocks.defaultVariant ?? ; } variables.forEach((key, value) { content content.replaceAll({{$key}}, value); }); return content; }模板分块的判定规则我简化成三种标记// #if ohos、// #else、// #endif。这个方案虽然粗暴但对 Dart 模板文件来说足够直观而且不会引入额外的模板语法解析器维护成本低。实际跑下来的效果是同一个模板文件既能产出 Android 风格代码也能产出鸿蒙风格代码CI 里两个平台并行生成互不干扰。3. 实操全过程从零完成鸿蒙化适配3.1 环境准备与工具链动手前先把环境准备好避免做到一半发现某个环节缺失。我这里列一份实际用到的工具清单Flutter SDK鸿蒙适配分支不是官方 flutter 主分支而是 OpenHarmony 组织维护的 flutter_flutter 仓库的鸿蒙分支。注意版本号要跟 OpenHarmony SDK 配套否则编译出来的flutter命令无法正常构建 hap 包。DevEco Studio鸿蒙原生工程的可视化调试工具用于查看生成的ohos/工程是否能被原生 IDE 识别以及检查 module.json5 配置是否正确。feature_folder_cli_plus 源码直接 clone 三方仓库的源码在本地起一个开发分支做适配。不要直接改已发布版本因为鸿蒙适配需要改项目识别和模板引擎两层逻辑改动面大原仓库合不合并是另一回事你本地得能跑起来。建议初始化一个最小的鸿蒙 Flutter 示例工程专门用来做生成结果的回归验证。我有一个自己的 shell 脚本每次改完工具代码就运行生成 Feature - 静态分析 - hap 构建三步确保没把别的平台搞挂。# 简易回归脚本 flutter create --platforms ohos test_app cd test_app feature_folder_cli_plus create feature login --platform ohos flutter analyze flutter build hap --debug一个容易被忽视的细节鸿蒙适配分支的 Flutter SDK 里flutter build的可用 target 可能不叫hap旧版本叫flutter build ohos新版本统一成了flutter build hap --mode debug。不同 OpenHarmony 发布周期对应的命令不一致你跑脚本报 unrecognized target 时优先去flutter_tools源码里查支持的 target 列表别在网上乱搜浪费时间。3.2 修改配置解析层配置解析层的改造分成两步第一步在ProjectDetector里加入鸿蒙工程识别第二步扩展配置映射模型。先说第一步。原有的ProjectDetector大概是这样的逻辑检查pubspec.yaml- 检查android/和ios/- 返回一个ProjectType枚举。鸿蒙化适配需要把它改成同时检查ohos/目录并返回更丰富的信息enum PlatformSupport { android, ios, ohos, all, } class ProjectMeta { final String projectName; final bool hasAndroid; final bool hasIos; final bool hasOhos; final OhosProjectInfo? ohosInfo; bool get isOhosEnabled hasOhos ohosInfo ! null; }这里有一个实际的判断细节不要因为ohos/目录存在就认为鸿蒙可用。有些工程只是拷了个空壳ohos目录进去里面没有module.json5或者缺少entry模块。所以hasOhos不能只看目录必须要能成功解析module.json5才算真正的鸿蒙工程。我在适配过程中把这个逻辑调整成了三级校验——目录存在、配置文件存在、配置可解析三级全过才把工程标记为支持鸿蒙。第二步扩展配置映射模型改动集中在FeatureConfig这个数据结构上。原本它只有featureName、stateManagement、routerType这些通用字段需要新增platformTargets和ohosConfigclass FeatureConfig { final String featureName; final String stateManagement; final String routerType; final ListString platformTargets; final OhosFeatureConfig? ohosConfig; }有了这个模型后续生成器只管从platformTargets里判断当前 Feature 需不需要生成鸿蒙平台文件配置解析和文件生成两个层级的职责就彻底分开了。3.3 生成器改造与验证生成器是整个适配过程中工作量最大的部分。原来FeatureGenerator的流程是创建lib/features/name目录写入presentation/application/data/domain四层骨架更新路由注册生成测试文件。鸿蒙化适配后我在这个流程里插入了一个新的步骤——在ohos/侧生成配套的模块资源。实际代码改造后的生成流程创建 Dart 层 Feature 目录结构原有逻辑生成feature.dart入口文件导出各层公共接口根据platformTargets判断是否生成鸿蒙资源文件如果包含ohos调用OhosFeatureResourceGenerator生成Resources/base/element/string.json等资源文件更新路由配置允许 Feature 注册新的路由项写入测试文件第 4 步是关键。鸿蒙资源文件的生成不能简单套用 Android 的 XML 模板因为 string.json 的格式长这样{ string: [ { name: feature_login_title, value: 登录 } ] }生成器需要维护一个resourceRegistry记录每个 Feature 往全局资源文件里贡献了哪些字符串项最后合并写入ohos/entry/src/main/resources/base/element/string.json。这里容易踩一个坑多个 Feature 共享同一个资源文件时生成器如果直接把整个文件覆盖写会把之前 Feature 的资源项弄丢。解决办法是先读取已有内容合并新增项再整体写回。我用jsonEncode加排序保证输出稳定避免每次跑工具都产生无意义的 diff。验证环节我推荐做三层静态验证flutter analyze检查 Dart 代码生成结果结构验证用脚本检查ohos目录下的资源文件是否符合 HarmonyOS 资源规范必要时用 DevEco Studio 打开工程看一眼构建验证flutter build hap跑通一次全量构建。其中结构验证最容易漏但恰恰最致命。鸿蒙的资源目录对大小写敏感resources写成Resources在 Windows 上可能没问题在 Linux CI 上直接构建失败。我最后写了一个小的目录树校验函数把所有生成的路径统一走小写映射然后跟规范路径列表比对提前发现问题。4. 常见问题与排查技巧实录4.1 鸿蒙目录生成后 Flutter 构建失败的排查真实场景里最常见的报错是这种新增一个 Feature 之后flutter build hap --debug跑到一半告诉你资源文件格式错误或者是某个模块引用了不存在的路径。我遇到过一次很典型的生成器生成了feature_login的string.json内容本身没有语法问题但构建时鸿蒙编译器报duplicate resource。排查下来发现原来工程里已经有login的字符串资源生成器没做去重直接写了一模一样的项。修复方案就是在资源合并逻辑里加一个resourceKey去重步骤以name字段为唯一键重复项直接跳过。排查思路总结成三个顺序先看错误信息里提示的文件路径在不在八成是生成器写错了相对路径再看资源文件编码鸿蒙要求 UTF-8 无 BOMWindows 下写入容易带 BOM最后看module.json5的模块依赖新增的 Feature 如果引用了平台能力需要在对应oh-package.json5里声明依赖。这里有一个通用技巧鸿蒙的构建日志是分模块输出的报错信息里如果包含hvigor字样基本都是原生侧的编译问题如果包含dart关键字问题出在 Flutter/Dart 侧。先按这个粗分类缩小范围不用一头扎进几千行日志里。4.2 模板变量与路径分隔符的坑模板渲染的适配过程中路径分隔符是个让人很恼火的细节。Dart 的字符串模板替换是在运行时做的如果模板里写的是硬编码正斜杠lib/features/在 Windows 下跑生成器写入的文件路径没问题但模板里给import语句生成的路径如果用了\作为分隔符Dart 编译直接报错。我的处理原则是模板里所有路径统一用正斜杠实际文件系统操作时用Platform.pathSeparator做转换。这样既保证了生成出来的源码跨平台可编译也保证了工具本身在多个开发环境上能跑。另一个坑是 Dart 的模板变量替换容易误伤$字符。你写{{feature_name}}替换没问题但 Dart 源码里大量存在字符串插值比如${controller.text}。如果模板引擎用的是全局replaceAll这些$字符不会受影响但如果引擎内部自己做了四层转义就可能把$变成\$生成的代码面目全非。所以模板引擎设计上要保持简单——只做占位符替换不做任何语法高亮或转义处理越简单越安全。4.3 版本兼容性与升级策略鸿蒙化适配不是一锤子买卖因为 Flutter SDK 的鸿蒙分支本身就是快速迭代中的东西。feature_folder_cli_plus 生成的代码如果用到了某个鸿蒙 Flutter 分支独有的 API一旦上游更新的版本接口变更生成结果就得跟着调整。我的建议是这样的变更来源适配策略备注Flutter 鸿蒙分支 API 变更定义 API 兼容层封装差异调用不要直接调原生接口OpenHarmony SDK 版本升级版本声明变量化构建脚本动态读取配合 DevEco 的 SDK 目录feature_folder_cli_plus 上游更新采用 fork cherry-pick 维护定期同步上游改动版本兼容的底线是不在工具里硬编码鸿蒙 SDK 路径。每次升级 DevEco Studio 或 OpenHarmony SDK 后路径都可能变如果生成器写死了/Applications/DevEco-Studio.app/Contents/sdk这种路径换台机器就跑不了。正确做法是生成一个ohos/env.json配置文件由开发者填写 SDK 路径工具解析这个文件来定位 SDK。5. 适配完成后的工程组织规范与心得5.1 一个可落地的鸿蒙 Feature 目录规范适配做完了最终还是要回到怎么组织工程这个问题上。经过这一段折腾我整理出一套适合 Flutter 鸿蒙双端开发的项目目录规范已经在实际项目里跑了一段时间供参考lib/ features/ login/ presentation/ pages/ widgets/ application/ data/ domain/ core/ di/ network/ router/ theme/ ohos/ entry/ src/main/ module.json5 resources/ base/ element/ media/ oh-package.json5 build-profile.json5关键纪律是三条Feature 之间禁止互相引用需要共享的代码下沉到core每个 Feature 的鸿蒙资源独立命名空间资源名前缀必须带 Feature 名防止全局冲突路由注册统一收敛新增页面不直接改router.dart而是通过工具生成的feature_routes.dart注册。这个规范看起来简单但真正执行起来需要工具配合。feature_folder_cli_plus 的价值恰恰在于把这三条纪律固化到了生成逻辑里开发者不是靠自觉遵守而是生成出来的代码长出来就是符合规范的。5.2 踩坑总结与个人建议适配过程中最深的体会是三方工具做平台适配先改解析层再改生成层最后才是模板层。这三个层级之间有依赖关系顺序乱了会出现模板改好了但解析层不识别新参数这种哭笑不得的局面。还有一点要提醒feature_folder_cli_plus 这类 CLI 工具的测试覆盖通常集中在 Dart 层鸿蒙化适配后建议自己也补上integration_test在鸿蒙模拟器或真机上跑一遍核心流程验证生成的 Feature 能被工程编译、跳转、释放。我最初只做了静态生成验证就发布到团队内部结果第一个同事用的时候就出现了路由注册失败——因为生成器漏掉了go_router新版本要求的 route 参数。这种问题单看生成代码是看不出来的必须跑到集成测试阶段才暴露。最后给正在做同类适配的人一点建议别指望官方支持会很快跟上。鸿蒙生态的 Flutter 工具链更新节奏跟上游并不同步你现在做的适配工作大概率还需要自己维护一段时间。给你的工具代码增加清晰的日志输出、统一的错误处理、合理的配置默认值这些投入会在后续长期维护时成倍地回报你。
返回列表