ARTICLE DETAIL

资讯详情

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

OpenHarmony上Flutter底部导航实现:从环境搭建到状态保持与避坑

OpenHarmony上Flutter底部导航实现:从环境搭建到状态保持与避坑 先说明一下这篇文章不是为了介绍“怎么调用一个控件”而是站在“我要在 OpenHarmony 设备上真正跑起来一个 Flutter App”的角度把从环境准备到底部导航实现的完整链路捋一遍。你在 OpenHarmony 设备上做 Flutter 开发最常碰到的其实就是底部导航这类基础框架但恰恰是这种基础功能在跨平台适配时容易出各种幺蛾子。这篇文章会把底部导航的实现方案、状态保持、页面切换、路由组织以及 OpenHarmony 带来的特殊限制和坑全部过一遍适合刚接触 Flutter for OpenHarmony 的开发者也适合已经跑过 Demo 但准备认真做一个“软件开发助手”这类工具型 App 的人。1. 项目背景与方案选型1.1 为什么要在 OpenHarmony 上用 FlutterOpenHarmony 作为一个面向全场景的分布式操作系统这几年设备覆盖范围越来越广从手机、平板到开发板、智能家居都在用。但生态建设不是一朝一夕的事很多开发者面临的现实问题是团队里已经用 Flutter 写了一套业务代码不可能为了 OpenHarmony 单独用 ArkUI 再写一遍这时候“Flutter for OpenHarmony”的价值就体现出来了它是把 Flutter 引擎移植到了 OpenHarmony 标准系统上让 Flutter 开发者能用同一套 Dart 代码直接跑在 OpenHarmony 设备上。这个方案不是官方文档里随便提一嘴的实验室项目OpenHarmony 社区和厂商一直在推进这件事Flutter 的 OpenHarmony 适配版本已经能支持相当一部分 Flutter 框架能力。我在实际项目中验证过基础的 Widget 渲染、动画、路由、平台通道这些都能正常工作性能也基本可用。这意味着如果一个团队已经沉淀了 Flutter 组件库和业务模块迁移到 OpenHarmony 的成本远低于从零写一套原生应用。还有一个很重要的点OpenHarmony 的生态还在快速变化中用 Flutter 做跨端可以避免被某个特定系统版本绑定太死。你写的底部导航、列表页、表单页这些通用组件未来不管是回到 Android、iOS还是继续留在 OpenHarmony代码都是同一份团队不需要维护多套 UI 框架。1.2 软件开发助手 App 的定位与核心功能这篇文章里要实现的“软件开发助手”定位是一个给开发者在日常调试、开发过程中提供便捷工具的应用。核心功能包括查看设备日志、常用调试命令一键执行、检查应用包信息、网络接口快速测试等等。这类工具型 App 有一个共同特点页面层级不深但页面之间切换频繁而且每个页面内部都有自己的状态比如日志页面要持续刷新、命令页面要保持输入历史。这就决定了底部导航是 App 的主骨架因为四个主入口日志、工具、包管理、设置之间是平级关系用户需要在它们之间快速切换。底部导航的实现在 Flutter 里看起来很简单无非是BottomNavigationBar或NavigationBar加一个页面索引切换但真正放到 OpenHarmony 上跑的时候会遇到一些纯粹的 Flutter 开发中不会注意的问题比如页面切换时状态丢失、渲染性能下降、字体异常、平台通道调用失败等等。所以我更愿意把这篇文章理解成“一个工具型 App 的底部导航是怎么做扎实的”而不是“底部导航控件怎么用”。核心思路是先确定整体架构再逐层实现每一步都为后续的业务功能预留扩展空间。2. 环境搭建与工程初始化2.1 安装 OpenHarmony SDK 与 DevEco Studio在 OpenHarmony 上跑 Flutter第一步是把 OpenHarmony 的开发环境准备好。这里说的不是装一个模拟器就完事而是要能连上真实设备或官方模拟器进行调试。我推荐用 DevEco Studio 来管理 OpenHarmony SDK因为它集成了 SDK 管理、签名配置、设备连接和日志查看功能能少踩很多坑。安装完成之后你要在 DevEco Studio 的 SDK Manager 里确认安装了OpenHarmony SDK并且记下 SDK 的 API 版本。这里有个容易被忽略的点Flutter for OpenHarmony 的适配版本对 API 版本有要求不是说你装了最新版 SDK 就一定支持而是要匹配 Flutter 适配版本对应的 API level。我在项目里用的是 API 9 和 API 10 之间的一个版本实测 Flutter 适配版本与 API 10 配合得比较好。具体操作路径是打开 DevEco Studio进入Tools SDK Manager在HarmonyOS SDK界面勾选需要的 API 版本同时安装Previewer和Toolchains。如果你有真实的 OpenHarmony 开发板或者手机还需要在Project Structure Signing Configs里配置自动签名否则无法在设备上安装调试包。2.2 下载并配置 Flutter for OpenHarmony SDK截至我写这篇文章的时间点Flutter for OpenHarmony 的 SDK 并不是从flutter.dev官方渠道直接下载的而是需要从 OpenHarmony 社区或相关代码仓库获取适配后的版本。你要注意这里下载的 Flutter SDK 和普通的 Flutter SDK 是两套虽然 API 基本一致但底层引擎的适配代码不同。下载完成后把压缩包解压到一个固定目录然后在系统环境变量里配置FLUTTER_HOME或者直接把 bin 目录加入PATH。配置完之后打开终端输入flutter doctor如果看到Flutter for OpenHarmony相关的识别信息就说明环境基本通了。这里有一个很容易踩的坑如果你之前已经装过官方 Flutter SDK千万不要让两个 SDK 的 bin 目录同时出现在 PATH 里否则flutter命令会冲突。我自己的做法是把 OpenHarmony 版 Flutter SDK 解压到D:\ohos-flutter,然后用一个自定义终端脚本切换 PATH 环境变量。另外你还需要在工程里配置pubspec.yaml确保依赖源能访问到 OpenHarmony 对应的版本。最稳妥的方式是新建工程时选择 Flutter for OpenHarmony 自带的模板模板里已经帮你配好了ohos平台目录和依赖版本。2.3 创建 Flutter for OpenHarmony 工程并运行到设备环境准备完毕接下来就是新建工程。执行flutter create --platforms ohos my_dev_assistant这样的命令注意--platforms参数里要传ohos而不是android,ios。生成之后工程目录里会多出一个名为ohos的目录里面是 OpenHarmony 的工程壳子类似 Android 里的android目录。打开工程里的ohos目录你会发现它本质上是一个 DevEco Studio 工程包含entry/src/main/module.json5和MainAbility这些典型的 OpenHarmony 文件。我们现在要做的是确认 Flutter 的入口能被 OpenHarmony 的 Ability 启动起来。在模板默认配置下module.json5里的mainElement会指向一个继承自FlutterAbility的类这个类会在页面加载时启动 Flutter 引擎并加载 Dart 入口。接着用 USB 连接 OpenHarmony 开发板或手机确保开启了 USB 调试模式。在 DevEco Studio 的设备列表里能看到设备后先不要直接在 DevEco 里 build而是回到终端执行flutter run -d device-id。这一步会先编译 Dart 代码再通过 OpenHarmony 的构建工具打成可安装的hap包最后安装到设备上并启动。第一次运行时间会比较长因为要构建整个 Flutter 引擎的宿主壳工程后面增量编译会快很多。注意如果你发现flutter devices识别不到 OpenHarmony 设备检查一下设备端有没有安装hdc驱动以及 DevEco Studio 里的hdc是否加入了系统 PATH。hdc对应 Android 里的adb没有它Flutter 工具链无法和 OpenHarmony 设备通信。3. 底部导航实现的全过程3.1 底部导航的四种实现方案对比在 Flutter 里做底部导航不是只有一种写法。如果不提前规划到了业务复杂之后会非常痛苦。我基于实际项目经验整理出四种常见方案的对比方案核心思路优点缺点适用场景BottomNavigationBar 当前页替换点击时 setState 切换 body实现最简单页面状态彻底丢失每次切换都会重建临时 Demo、无状态页面NavigationBar IndexedStack用 IndexedStack 保持所有页面状态状态不会丢切换流畅所有页面常驻内存工具类 App 主骨架最推荐BottomNavigationBar PageView用 PageView 支持左右滑动切换顺手可以做滑动动画和 TabBarView 一样需要处理预加载和手势冲突有翻页动画需求时自绘底部导航 自定义路由完全自己控制切换逻辑最灵活可以做动效开发量大维护成本高对 UI 定制要求极高的场景在“软件开发助手”这个项目里我选择的是NavigationBar IndexedStack方案。原因很简单日志页面需要持续输出内容工具页面需要保留用户输入的命令历史包管理页面可能需要展开列表项这些状态都希望用户在四个 Tab 之间来回切换时保持不变。如果用“当前页替换”方案切走再切回来日志列表重新拉取、输入框内容清空体验非常差。3.2 HomeShell 壳页面的代码实现底部导航的骨架我习惯称为HomeShell它负责承载导航栏和页面容器。下面是核心代码结构import package:flutter/material.dart; import log/log_page.dart; import tools/tools_page.dart; import package/packages_page.dart; import settings/settings_page.dart; class HomeShell extends StatefulWidget { const HomeShell({super.key}); override StateHomeShell createState() _HomeShellState(); } class _HomeShellState extends StateHomeShell { int _currentIndex 0; static const _pages Widget[ LogPage(), ToolsPage(), PackagesPage(), SettingsPage(), ]; override Widget build(BuildContext context) { return Scaffold( body: IndexedStack( index: _currentIndex, children: _pages, ), bottomNavigationBar: NavigationBar( selectedIndex: _currentIndex, onDestinationSelected: (index) { setState(() { _currentIndex index; }); }, destinations: const [ NavigationDestination( icon: Icon(Icons.terminal_outlined), selectedIcon: Icon(Icons.terminal), label: 日志, ), NavigationDestination( icon: Icon(Icons.build_outlined), selectedIcon: Icon(Icons.build), label: 工具, ), NavigationDestination( icon: Icon(Icons.inventory_2_outlined), selectedIcon: Icon(Icons.inventory_2), label: 包管理, ), NavigationDestination( icon: Icon(Icons.settings_outlined), selectedIcon: Icon(Icons.settings), label: 设置, ), ], ), ); } }几个细节值得说一下。_pages被定义成static final或static const这样每次setState的时候四个页面实例是同一个对象配合IndexedStack能最大限度保证子页面状态不丢失。IndexedStack会一次性构建所有子页面并全部挂在 widget 树上只有 index 对应的页面可见但其他页面只是不显示它们的 State 依然保留。NavigationBar是 Material 3 的组件比老的BottomNavigationBar有更好的默认触感反馈和自适应宽度。如果你对 Material 2 的风格有执念也可以换回BottomNavigationBar逻辑完全一样。但建议新项目直接上 Material 3OpenHarmony 上的字体渲染和主题适配在 Material 3 下更自然一些。3.3 每个 Tab 页面的状态保持技巧有了IndexedStack之后页面的State默认就能保持。但要注意IndexedStack的“保持”不是无敌的当你把整个HomeShell从路由栈里 push 走再 pop 回来HomeShell的 State 依然在但如果你把 App 切到后台系统内存紧张时把页面回收了那状态就丢了。对“软件开发助手”这种工具类 App我建议对关键页面做持久化缓存比如日志页面的列表数据存在本地数据库或文件里启动时恢复。另外一个实用技巧是让每个 Tab 页自己管理加载和初始化而不是在initState里做太重的操作。因为IndexedStack会一次性构建所有子页面如果每个页面initState都去请求网络或读取大文件四个页面会同时抢占启动资源导致 App 首帧变慢。我的做法是定义一个统一的LazyLoadStatefulWidget抽象类通过isLoaded标记控制真正的数据加载逻辑只在首次可见才执行。具体实现可以这样abstract class LazyLoadStateT extends StatefulWidget extends StateT { bool _loaded false; void load(); override void didChangeDependencies() { super.didChangeDependencies(); if (!_loaded) { _loaded true; load(); } } }这里的逻辑是在didChangeDependencies里只触发一次load()因为IndexedStack构建子页面时didChangeDependencies会调用但真正重数据的逻辑放到load()里由子类实现。你可以根据业务继续扩展比如监听ModalRoute.of(context).isCurrent来判断页面真正可见时再加载但对多数工具型 App首次进入时懒加载已经足够了。实操心得不要为了“省性能”就把所有 Tab 页都放在同一个IndexedStack里因为IndexedStack一旦构建所有子页面的State都会常驻。如果一个页面里有视频播放或者高频率动画需要自己管理资源的释放。在 OpenHarmony 设备上系统内存管理和 Android 不完全一样后台页面的资源回收更激进所以日志页面这种高频刷新的页面要小心泄漏。3.4 用路由管理二级页面的跳转底部导航解决的是四个一级页面的切换但工具型 App 肯定有二级页面比如从“工具”页面进入“网络请求工具”的详情页从“包管理”页面进入“应用详情”页。这些二级页面不能直接塞到IndexedStack里而是要使用 Navigator 路由栈来管理。我的做法是把Navigator放在HomeShell的上层让每个 Tab 页内部通过Navigator.push打开二级页面。这样二级页面会覆盖整个HomeShell包括底部导航栏。如果你希望二级页面依然显示底部导航那就需要嵌套路由每个 Tab 页都包一个自己的Navigator这种结构在“首页 Tab 内嵌 WebView 子页面”时比较常见但对工具类 App 来说全屏覆盖二级页面是更干净的做法。在 Flutter for OpenHarmony 上路由跳转涉及页面转场动画我发现默认的 Material 转场在部分设备上会有轻微掉帧。优化办法是在ThemeData.pageTransitionsTheme里统一设置更轻量的过渡或者在跳转时指定PageRouteBuilder自定义一个透明度加位移的转场避免同时做缩放、模糊等复杂效果。下面是自定义转场的示例Navigator.of(context).push( PageRouteBuilder( transitionDuration: const Duration(milliseconds: 200), pageBuilder: (context, animation, secondaryAnimation) { return const NetToolPage(); }, transitionsBuilder: (context, animation, secondaryAnimation, child) { final tween Tween(begin: const Offset(1, 0), end: Offset.zero) .chain(CurveTween(curve: Curves.easeOutCubic)); return SlideTransition( position: animation.drive(tween), child: child, ); }, ), );二级页面返回时默认返回到底部导航对应的 Tab。由于HomeShell一直在导航栈的底部所以Navigator.pop之后自然回到原来的 Tab并且该 Tab 的State还在体验上非常连贯。4. 常见问题排查与避坑实录4.1 页面切换时底部导航闪烁的问题很多人在 Flutter 页面切换时遇到过底部导航闪烁的情况尤其是从二级页面 pop 回来的时候导航栏会闪一下或者整个页面重绘。这个问题的根源往往不在底部导航本身而是页面切换时整个Scaffold被重建了。如果你看到这个现象先检查是不是在build里创建了新的Scaffold或NavigationBar实例。最常见的错误是把NavigationBar放在body里而不是放在bottomNavigationBar里另一个错误是在IndexedStack的外层包了一个AnimatedBuilder或者用StreamBuilder导致每次状态变化都重建整个HomeShell。我的排查路径是先打开 Flutter inspector看切换页面时对应 widget 有没有出现REBUILD标志。如果有说明 build 的粒度太大。修复思路是把状态变化限制在局部比如_currentIndex的变化用ValueListenableBuilder圈住只让IndexedStack和NavigationBar刷新父级 widget 保持稳定。注意在 OpenHarmony 设备上这个问题会比 Android 更明显因为适配的渲染引擎对重绘区域的优化可能不如原生平台成熟。遇到闪烁不要急着换引擎先排查是不是自己代码里 widget 重建范围过大。4.2 IndexedStack 导致启动时间变长前面提到IndexedStack会一次性构建所有子页面。如果你四个 Tab 页的initState里都有耗时操作比如读取数据库、解析 JSON、初始化 SDK那么 App 启动首帧会明显变慢。在“软件开发助手”这个项目里日志页面要初始化日志读取通道包管理页面要遍历应用列表这两个操作如果同时执行启动时间能差出一倍。解决思路有两个层面。第一层是懒加载把耗时操作放到页面第一次可见时再触发第二层是异步化把耗时操作放到compute或者Isolate里避免阻塞 UI 线程。比如包管理页面遍历应用列表完全可以异步处理Dart 的compute函数开一个 isolate 就能搞定。另外业务代码里不要用Future.delayed(Duration.zero)这种“下一帧再执行”的技巧来延迟初始化这类写法在 OpenHarmony 适配版上时序不稳定有时候会提前执行导致卡顿最好直接使用WidgetsBinding.instance.addPostFrameCallback确保首帧渲染完成后再做耗时操作。4.3 字体异常与页面显示不全在 OpenHarmony 设备上跑 Flutter App字体是个高发问题。症状有两种一种是中文显示成方框另一种是字体大小和 Android 设备上明显不同。第一种通常是因为系统字体文件中缺少对应字重或者字符集第二种是因为 OpenHarmony 的fontScale默认值和 Android 不一样。字体问题的应急方案是在 MaterialApp 的theme里显式设置fontFamily,比如使用系统自带的“HarmonyOS Sans”或者直接把开源中文字体打包进 assets。如果只是个别页面显示不全优先检查是否在Text组件里使用了固定的文字缩放textScaleFactor在 OpenHarmony 上系统字体缩放比例和 Material 默认值不一致最好统一用MediaQuery.of(context).textScaler控制。我在项目里是把字体方案包了一层所有文本组件都走项目里的AppText组件底层统一读取一个字体配置常量这样后续调整字体风格时不用全局替换。对“软件开发助手”来说日志等宽字体很重要因为日志对齐需要等宽字符我额外引入了一款等宽字体作为日志页面的专属字体。4.4 网络请求与平台通道异常工具类 App 经常要做网络接口测试这时候 Flutter 的网络库在 OpenHarmony 上可能会有一些小坑。最典型的是SocketException: Connection failed原因是 OpenHarmony 的网络权限在module.json5里默认没有开启即使你 Flutter 代码里能正常发起请求底层网络 socket 也会被系统拦截。解决办法不是去 Flutter 代码里加权限而是修改ohos/entry/src/main/module.json5在requestPermissions里加入{ name: ohos.permission.INTERNET }如果你还需要访问本地网络设备还要加{ name: ohos.permission.GET_NETWORK_INFO }权限加完记得重新 build 整个 hap 包甚至要卸载重装因为 OpenHarmony 对权限变更的处理有时候不会自动热更新。平台通道的坑也类似。Flutter 代码通过MethodChannel调用 OpenHarmony 原生能力如果调用一次报MissingPluginException不一定是你 channel 名字写错也可能是原生侧这个 Ability 没有在onCreate里正确注册 channel 处理器。建议所有平台通道的调用都做 try-catch并在异常时输出 channel 名称方便定位。4.5 常见问题速查表为了方便你排查问题我把实际开发中遇到的典型问题和处理方式整理成表格现象可能原因解决方式页面切换时导航栏闪烁widget 重建范围过大用局部刷新缩小 setState 影响范围中文显示方框字体缺失或字重不支持显式设置 fontFamily或打包字体启动首帧非常慢IndexedStack 中多个页面同时初始化懒加载耗时操作异步化网络请求 socket 异常缺少 INTERNET 权限在 module.json5 里补充权限调用原生能力时报 MissingPluginchannel 未注册或名字不匹配检查 Ability 生命周期中的注册逻辑从二级页面返回时状态丢失路由栈被重置确认 HomeShell 在路由栈底部未被销毁这里面每一个问题我在实际调试时都花了不少时间。尤其是权限和字体问题Flutter 工具链给出的报错信息并不直观需要根据现象反推。5. 性能优化与体验细节打磨5.1 保证 60fps 流畅度在 OpenHarmony 设备上跑 Flutter流畅度是很多人关心的指标。底部导航页面的流畅度主要受两个因素影响一是列表页面的渲染效率二是页面切换时的动画。对于工具类 App 的日志页面如果日志量很大用ListView.builder是必须的不要用Column包一堆子项。ListView.builder会按需构建可见区域的 item配合itemExtent固定行高可以进一步减少 layout 计算。另外一个容易被忽略的是图片和图标。OpenHarmony 设备屏幕密度各异如果你在页面里用了大量图片要确保加载时指定合适的缓存尺寸。Flutter 的Image.network默认是按原图解码的建议封装一个网络图片组件用cacheWidth参数限制解码宽度。在日志页面尽量避免在列表项里放图片组件否则滚动时 GPU 和内存压力会同时上来了。动画方面底部导航切换时NavigationBar默认带指示器动画。如果觉得掉帧可以检查动画期间的setState有没有带动整个页面树重建。我在项目里发现一个规律当IndexedStack的子页面里面有一个AnimatedList或者AnimationController一直在跑底部导航切换的帧率就会被拖累。解决办法是在页面不可见时暂停动画可以用TickerMode包裹IndexedStack让不可见页面的动画 ticker 不工作这是一个非常实用的优化技巧。body: TickerMode( enabled: true, child: IndexedStack( index: _currentIndex, children: _pages, ), ),TickerMode默认是继承的你可以按照_currentIndex动态设置enabled这样只有当前展示页面的动画会执行其他后台页面的动画直接暂停能省下不少 GPU 资源。在 Flutter for OpenHarmony 的适配版本里这个优化效果比 Android 上明显因为 OpenHarmony 的后台页面调度策略更保守。5.2 包体积与启动加载优化工具型 App 讲究轻量而 Flutter 应用在 OpenHarmony 上的包体积会比 Android 略大因为需要携带 Flutter 引擎的适配库。我做“软件开发助手”时hap 包最开始有 80 多 MB后来通过以下手段压到了 50MB 以内第一拆分应用和引擎包。OpenHarmony 支持共用发行包让多个应用共享一套 Flutter 引擎如果你的设备上已经有其他 Flutter 应用系统不会重复加载引擎。这个需要构建时配置为 shared 模式我会在build-profile.json5里把依赖的 Flutter 引擎模块标记为 shared。第二裁剪未使用的字体和资源只保留中英文常用字体。第三开启混淆和压缩。pubspec.yaml里我建议开启--tree-shake-icons这个参数能移除未用到的 Material 图标图标对 APK 体积的影响在 OpenHarmony 上同样明显。启动加载的优化上main()里只做最少的初始化不要直接创建数据库连接或者初始化日志系统。像日志系统这类基础能力可以用延迟初始化或者单例懒加载。如果你用了fluro或go_router这类路由库也要确认它们在首帧之前不会去扫描全部路由表否则每次启动耗时都会偏高。5.3 与原生能力交互的扩展思路底部导航只是 App 的骨架真正的价值在于每个 Tab 页面能调用 OpenHarmony 的系统能力。“软件开发助手”需要读取设备日志、获取应用列表、执行调试命令这些在 Flutter 层做不到必须通过 MethodChannel 调用 OpenHarmony 原生代码。这里给一个简单的通道封装思路class DeviceChannel { static const MethodChannel _channel MethodChannel(dev_assistant/device); static FutureString? get DeviceModel async { try { final String? result await _channel.invokeMethod(getDeviceModel); return result; } on PlatformException catch (e) { debugPrint(getDeviceModel failed: ${e.message}); return null; } } }在 OpenHarmony 原生侧对应的MethodChannel处理器需要在 MainAbility 中注册import ohos.hiviewdfx.HiLog; import ohos.rpc.IRemoteObject; import ohos.abilityshell.AbilityShell; import ohos.abilityshell.IAbilityShell; // 具体 API 以你的 SDK 版本为准 // 核心思路在 Ability 内注册 method call handler // 处理 Dart 侧抛过来的方法名并返回结果。这里要特别提醒一点不要在 UI 线程里执行耗时的原生操作比如遍历应用列表或者读取系统日志这类操作要用任务线程跑然后把结果回传到主线程再返回给 Flutter。我在实现包管理页时踩过这个坑一开始直接在 method call handler 里同步反射获取应用列表结果调用一次卡了 3 秒多页面直接 ANR。改成异步处理之后耗时降到了 300 毫秒左右。平台通道的链路在 OpenHarmony 上比 Android 多一层而且不同设备厂商对系统能力的开放程度不一样。你在开发“软件开发助手”这类应用时一定要做好通道调用的降级比如设备日志读取失败就提示用户通过 USB 连接 DevEco 查看不要把崩溃错误直接抛给用户。5.4 启动图与应用基础配置MaterialApp的onGenerateTitle和原生启动图是两码事。OpenHarmony 的原生启动图是在module.json5和resources里配置的很多开发者会忽略这一点导致 Flutter 页面加载前出现几秒钟白屏或黑屏。建议把启动图背景色和应用主色调保持一致并且加上应用 Logo视觉上感觉启动更快。Fluent 的 Flutter 端可以在main()里通过WidgetsFlutterBinding.ensureInitialized()设置全局的屏幕方向、系统 UI 风格等参数。比如“软件开发助手”更适合竖屏使用可以在原生侧或者 Flutter 侧固定竖屏避免在平板设备上出现宽屏布局异常。字体设置、深浅色主题适配也建议在项目初期就确定。OpenHarmony 上切换浅色深色主题时Material 默认主题色可能显示不符合预期我为“软件开发助手”定义一套独立的主题文件色值采用 Harmony 设计规范里的推荐色板这样应用整体风格能和系统自带软件保持一致用户视觉上不会有明显的割裂感。6. 构建发布与未来迭代建议6.1 构建 hap 包与签名发布开发调试完成后要把应用发布给其他人测试或上架需要构建正式的hap包。构建方式一般有两种一种是用 DevEco Studio 直接 Build另一种是命令行构建。命令行方式更适合集成到 CI/CD 中cd ohos hvigorw assembleHap --mode module -p productdefault构建成功后产物位于entry/build/default/outputs/default/entry-default-signed.hap。这个 hap 包就可以安装到 OpenHarmony 设备上了通过 hdc 命令分发hdc install entry-default-signed.hap签名配置比较麻烦需要创建 OpenHarmony 应用签名证书包括 Profile 文件和 P12 证书。个人开发者可以通过 DevEco Studio 的自动签名能力申请企业开发者需要走应用市场发布流程。看这篇文章的读者如果只想在设备上自测可以先生成调试证书后续正式发布时再换正式证书。6.2 从轻量工具到完整产品的成本思考很多朋友在评论区或私信里问“开发一个 App 并上架大概要多少钱”这个问题其实没法给一个固定数字因为成本跨度非常大。如果你只做一个像“软件开发助手”这样的轻量工具没有复杂后端一个人从 0 到 1 大概一到两个月业余时间就能完成主要的成本是个人开发账号的认证费用、服务器费用如果涉及云端功能、以及可能购买的字体版权。但如果要做成完整的产品成本会指数级上升。多端适配OpenHarmony、Android、iOS意味着每个平台都需要维护构建和测试设备兼容性测试尤其关键OpenHarmony 设备覆盖了从轻量系统到标准系统的多种形态不是所有设备都能正常跑 Flutter 应用特别是使用 LiteOS-M 的轻量设备内存和算力根本带不动 Flutter 引擎你在选型时必须先确认目标设备属于标准系统。这方面的测试工作量不小建议至少准备 3 到 5 台不同厂商的设备做回归。6.3 后续迭代多线程、状态管理、动态化底部导航做完了后续的功能迭代有几个方向值得投入。第一个是耗时任务的优化“软件开发助手”会涉及日志文件解析、大文本搜索、包信息批量处理这些任务一定要放到Isolate里处理。Flutter 的compute函数非常方便但它每次都会创建新的 isolate如果任务频繁建议自己维护一个常驻 isolate 接收消息。第二个是状态管理的选型。项目变复杂以后四个 Tab 页面之间需要共享一些数据比如“日志页面过滤条件”要能被“设置页面”修改。这时候再用setState传递数据就很痛苦了。我用的是ProviderChangeNotifier的组合因为它在 Flutter for OpenHarmony 上依赖少、性能可控。Riverpod和Bloc也可以用但要注意它们的版本与 Flutter 适配版的兼容性我在测试Bloc时发现有几个版本的StreamBuilder在页面切换热重载时偶尔会 crash后来换回Provider才消停。第三个方向是代码组织的优化。如果你发现home_shell.dart文件越来越长应该按功能拆文件比如把每个 Tab 页单独放到对应目录公共组件抽到widgets目录。Dart 的part指令在拆分单个类时很有用但新的项目规范更推荐用import 独立文件的方式因为part会让代码审查和 IDE 分析变得混乱。我在“软件开发助手”项目里严格遵循“一个 feature 一个目录”底部导航骨架只是入口具体业务都在各自的 feature 目录里这样后续加新功能不会动到骨架代码。从我实际测试的情况看Flutter for OpenHarmony 的底部导航在主流开发板上已经很流畅跟跑在 Android 上的差距很小。这个过程中绕过的坑主要集中在环境配置、权限声明和原生交互上一旦把这些打磨好后面写业务功能就顺了。如果你正准备把现有 Flutter 项目迁移到 OpenHarmony建议从底部导航加一个最简单的 Tab 页起步跑通整条链路后再逐步扩大范围不要试图一次把整个项目搬过去那样出了问题很难定位。最后分享一个小技巧每天开发结束前把ohos目录下自动生成的build目录清理掉第二天启动构建会快很多这个习惯帮我省了不少时间。
返回列表