资讯动态

Flutter鸿蒙实战:汇率查询器开发与适配全记录

发布时间:2026/10/21 6:04:27 来源:尧图企业网站定制
最近做了个汇率查询器的小项目正好用上了鸿蒙加 Flutter 这套跨平台方案。整个过程从技术选型到最终跑在鸿蒙真机上走了不少弯路也踩了不少坑。今天把这套实战经验整理出来重点讲跨平台工程是怎么搭的、汇率功能怎么实现、以及鸿蒙适配那些很容易被忽略的细节。如果你正准备尝试 Flutter 开发鸿蒙应用或者想找一个轻量项目练手这篇应该能帮你省下不少排查时间。1. 项目整体设计与技术选型思路1.1 为什么用 Flutter 做鸿蒙跨平台先说结论如果目标是同时覆盖安卓、iOS 和鸿蒙Flutter 是目前比较务实的跨平台方案。核心原因是 Flutter 使用自绘引擎渲染 UI不依赖系统原生控件这一点对鸿蒙非常重要。传统跨端方案比如 WebView 容器在鸿蒙 Next 上的兼容性并不稳定而 Flutter 的渲染层本身就是自带完整实现只要底层 SDK 能运行 Dart 引擎界面就可以正常工作。鸿蒙系统目前已经不支持直接安装安卓 APK对开发者来说要么直接用原生 ArkTS 重写要么寻找跨端框架的鸿蒙适配版本。Flutter 这边已经有了面向 OpenHarmony 的社区版本虽然不像官方分支那样直接flutter create全平台但流程已经跑通。汇率查询器属于典型的中小型工具类应用页面复杂度不高但涉及网络请求、状态管理、本地存储和定时刷新正好能把 Flutter 的核心能力完整验证一遍。如果只用 ArkTS 原生开发本身问题也不大。但考虑到项目后续还要出 iOS 版本用 Flutter 写一遍就能同时交付三端维护成本明显更低。我们当时的判断是与其维护两套原生代码不如接受 Flutter 在鸿蒙上的一些适配成本把业务逻辑全部收敛到 Dart 层。1.2 汇率查询器的功能拆解这个项目叫汇率查询器听起来简单但实际功能拆开来看有六个核心模块汇率列表展示展示常用货币对当前牌价。基准币种切换用户选择一种货币作为基准比如人民币。金额换算输入一个金额按当前基准汇率计算目标货币金额。下拉刷新和定时自动刷新保证汇率不过期。本地缓存离线状态展示上次成功拉取的数据。多币种管理支持添加、删除和排序常用货币。我们没有一上来就做特别花哨的历史走势图和实时图表。原因是汇率类数据源本身都有访问频率限制实时行情图对数据维度和刷新策略要求更高。作为跨平台实战项目先把核心链路做扎实后面再扩展图表模块会顺利很多。1.3 技术栈与工程结构项目技术栈可以概括为“Flutter 为主鸿蒙壳承载”。实际工程里包含两个部分Flutter 工程负责全部业务代码、UI 和逻辑。鸿蒙工程负责生成 HAP 包、配置权限、签名和真机安装。依赖方面网络请求用的是http包本地缓存用shared_preferences状态管理直接用ChangeNotifier加setState没有引入重量级框架。汇率数据源选择的是一个免费开放的汇率 API返回 JSON 格式包含货币代码和对应汇率。工程目录结构大致如下lib/ main.dart models/currency_rate.dart services/exchange_rate_api.dart services/cache_service.dart pages/home_page.dart widgets/rate_list_tile.dart这个结构够用没有过度分层。如果是大项目可能要考虑provider或riverpod但汇率查询器的状态量很少自己管理反而更直观。2. 核心逻辑与关键实现细节2.1 汇率接口接入与 JSON 数据解析汇率接口返回的数据结构一般是这样的{ success: true, source: USD, timestamp: 1732000000, rates: { USD: 1, CNY: 7.1992, EUR: 0.9234, JPY: 154.32 } }这里的source是基准货币rates是相对于基准货币的汇率。比如基准是美元那么人民币汇率 7.1992 就意味着 1 美元等于 7.1992 人民币。在 Dart 里对应模型可以这样写class CurrencyRate { final String source; final int timestamp; final MapString, double rates; CurrencyRate({required this.source, required this.timestamp, required this.rates}); factory CurrencyRate.fromJson(MapString, dynamic json) { final rawRates json[rates] as MapString, dynamic; final rates String, double{}; rawRates.forEach((key, value) { rates[key] (value as num).toDouble(); }); return CurrencyRate( source: json[source] as String, timestamp: (json[timestamp] as num).toInt(), rates: rates, ); } }这里有个容易踩的细节json[rates]里的 value 类型在解码后可能是整数也可能是浮点数。如果直接强转as double遇到整数就会崩溃。我一开始就是图省事写成as double结果接口里某些汇率恰好是整数立刻抛类型转换异常。后来统一改成(value as num).toDouble()才彻底解决。请求逻辑也不复杂但要注意超时和异常捕获FutureCurrencyRate fetchRates(String baseCurrency) async { final uri Uri.parse(https://api.example.com/v1/rates?base$baseCurrency); final response await http.get(uri).timeout(const Duration(seconds: 8)); if (response.statusCode ! 200) { throw Exception(API request failed: ${response.statusCode}); } final json jsonDecode(response.body) as MapString, dynamic; if (json[success] ! true) { throw Exception(API returns failure); } return CurrencyRate.fromJson(json); }超时时间设置为 8 秒不能太短否则弱网环境下容易误判。免费 API 的服务质量不太稳定经常有 5 秒到 12 秒的延迟实测 8 秒是比较折中的方案。2.2 基准货币切换与换算逻辑汇率换算的核心公式不复杂目标金额 源金额 / 源币种相对基准汇率 * 目标币种相对基准汇率举个例子当前基准货币是 USDUSD/CNY 7.2USD/JPY 154。如果把 100 人民币换算成日元要先拿 100 / 7.2 得出美元数量再乘 154得到大约 2138 日元。代码里我单独抽了一个工具方法避免页面里到处散落换算逻辑double convertCurrency({ required double amount, required String fromCurrency, required String toCurrency, required CurrencyRate rate, }) { if (amount 0) return 0; final fromRate rate.rates[fromCurrency] ?? 1.0; final toRate rate.rates[toCurrency] ?? 1.0; return amount / fromRate * toRate; }当fromCurrency等于基准货币时fromRate是 1逻辑天然成立。但要注意如果接口返回的 rates 里缺少某个货币直接用 null 参与运算会异常。所以在换算前要对所有参与计算的货币做存在性判断缺了就用一个占位默认值并在界面上提示“该币种暂无牌价”。在实际使用中用户可能经常输入零、负数或超大数字。我在输入框做了过滤只允许输入数字和一个小数点并且在金额为负时直接显示 0。换算结果统一保留两位小数界面用toStringAsFixed(2)搞定。超过万亿级别的数值会出现精度问题但汇率查询器通常不会涉及特大金额所以暂时不做特殊处理。2.3 下拉刷新与缓存策略汇率类应用最核心的体验就是“数据不能太旧”。免费接口通常每分钟会有更新但每次刷新都会消耗请求配额所以不能无脑高频请求。我们的缓存策略是第一次启动从本地缓存读旧数据同时发起网络请求。网络请求成功更新本地缓存刷新界面。网络请求失败如果缓存存在用缓存数据并显示“更新时间”和“离线数据”标签。缓存有效期默认 30 分钟。超过有效期且请求失败时仍展示缓存但明显提示可能不准确。手动下拉刷新强制忽略缓存直接请求一次。本地缓存不是只存字符串而是把整个 JSON 序列化后写进shared_preferences。加载时先读字符串再jsonDecode成对象。这样处理简单直接不需要额外引入数据库。Dart 里定时刷新用了Timer.periodic加DateTime时间戳判断Timer.periodic(const Duration(minutes: 5), (timer) async { await _refreshRates(); });注意Timer.periodic不会自动取消页面销毁时一定要在dispose里timer.cancel()。否则切后台再回来定时器可能还在跑容易造成内存泄漏和无效请求。我还做了一个特别简单的后台时间判断每次冷启动时如果当前时间减去缓存时间戳大于 60 分钟启动后立即请求一次否则先显示缓存等用户主动刷新。这样做是为了避免用户频繁打开应用把接口额度打爆。3. 鸿蒙适配与构建发布环节3.1 Flutter 工程集成鸿蒙 SDK 的具体步骤这部分是网上资料比较少的地方也是我们踩坑最多的地方。先明确一个前提普通 Flutter 官方 SDK 还不支持直接编译鸿蒙应用需要使用社区适配版本的 Flutter SDK。好在适配版的安装步骤已经比较清晰基本可以概括为三步拉取适配版 Flutter SDK并配置环境变量。用 Flutter 创建或迁移现有工程。在工程里生成鸿蒙平台目录然后通过 DevEco Studio 打开构建。操作流程大致如下# 拉取适配版 SDK 到本地按实际仓库地址 git clone https://github.com/your-flutter-fork/flutter.git dev-flutter # 配置 PATH export PATH$PWD/dev-flutter/bin:$PATH # 检查版本 flutter --version环境准备好后在已有 Flutter 工程中执行flutter create --platforms ohos .如果--platforms参数里支持ohos会生成一个ohos目录里面包含鸿蒙工程的壳。如果没有这个参数说明当前 Flutter SDK 版本不支持鸿蒙平台需要切换分支或重新下载。生成ohos目录之后用 DevEco Studio 打开这个目录等待工程同步完成。这里要特别注意DevEco Studio 的版本和适配版 Flutter 的配置要求要一致。我们的适配版是基于某次 OpenHarmony SDK 版本编译的如果本机 DevEco Studio 版本过新构建时会出现 API Level 不匹配的报错。3.2 鸿蒙权限与网络安全配置鸿蒙应用需要在自己工程的配置文件中声明网络权限。找到ohos目录下的module.json5在requestPermissions数组里添加{ name: ohos.permission.INTERNET }同时很多免费汇率 API 都是 HTTPS 接口理论上不需要额外配置 HTTP 明文。但如果你的 API 地址是 HTTP就必须在网络安全配置文件里允许明文流量。鸿蒙的网络安全配置和安卓比较类似需要创建一个 XML 文件并配置network-security-config。当时我们为了图省事直接用了一个 HTTP 测试接口结果打包后发现真机上一请求就失败控制台提示“明文流量被禁止”排查了半小时才想起来是网络安全策略问题。如果使用的是自签名证书或者内网测试接口还需要配置信任的证书否则 HTTPS 请求也会被拦。这个坑属于开发和测试阶段最容易出现的问题。3.3 打包 HAP 与真机调试的注意事项在 DevEco Studio 里打开ohos工程后可以直接选择真机运行。首次运行会提示配置签名大致逻辑是注册设备开发者账号、创建签名证书、配置 profile。这里没有太多捷径按向导一步步走就行。真机调试时有一个热门问题Flutter 代码改了之后hot reload在鸿蒙端并不总能生效。我们在安卓上用得好好的快捷键到鸿蒙上改完 UI 之后点热重载发现界面没变化有时候甚至直接白屏。这个问题的原因在于适配版 Flutter 对鸿蒙引擎的热重载支持还不完整。后来干脆每次改动都通过 DevEco Studio 重新构建部署虽然慢一点但结果稳定。另一个容易忽略的问题是图片资源。鸿蒙工程对资源文件的大小写敏感程度和安卓不太一样如果 PNG 文件名小写在 Dart 里用大写引用界面上不会立即报错而是显示空白图标。排查这种问题最简单的方式就是把所有资源文件名强制改为小写加下划线并保证代码引用完全一致。构建 HAP 包时DevEco Studio 默认生成的包体积可能包含全量架构。如果只需要运行在特定设备上可以在构建配置里排除无关架构减小包体积。汇率查询器这种小应用包体积不是主要矛盾但养成精简资源的习惯对后续做商业项目有帮助。4. 常见问题与排查技巧实录4.1 API 请求失败与本地容错汇率查询器离开网络就失去意义但网络恰恰是最不稳定的因素。我们遇到过的典型问题有免费接口限流返回 429。接口参数格式不对返回 400。手机访问不了外网服务连接超时。返回的 rates 里缺少某种货币。针对这些问题我们做了一个简单的三层容错请求层所有异常统一捕获转成友好提示。缓存层只要本地有缓存就不让用户看到空白崩溃页。数据层缺失货币用占位符预留。一个比较务实的经验是把“最后一次成功请求的完整响应”原样存下来而不是只存几个字段。这样即使接口数据结构升级也能用旧数据兜底。我们的CacheService很简单核心代码不过 20 行class CacheService { static const _key cached_rates_json; static Futurevoid save(String data) async { final prefs await SharedPreferences.getInstance(); await prefs.setString(_key, data); } static FutureString? load() async { final prefs await SharedPreferences.getInstance(); return prefs.getString(_key); } }加载缓存后先尝试解析如果解析失败就丢弃缓存避免坏数据阻塞 UI。实测下来这种做法在弱网环境里帮助很大。用户在地铁或电梯里打开应用即使没有网络也能看到上一次的汇率不会一脸懵。4.2 鸿蒙端布局与字体适配Flutter UI 本身是自绘的在鸿蒙上基本不会出现系统字体缺失的问题但我们还是遇到了两个适配坑鸿蒙部分设备对默认文本缩放比例设置不同导致文本溢出。安全区域计算方式不同底部按钮被手势条遮挡。第一个问题解决起来比较简单页面根组件统一用MediaQuery.of(context).textScaler和Scaffold的组合必须先拿到窗口尺寸再做布局列表项高度不要写死用minHeight或ConstrainedBox约束。第二个问题真正麻烦。在安卓上Scaffold的resizeToAvoidBottomInset会自动处理底部安全区但在鸿蒙适配版上表现不稳定。我们的方案是给底部操作栏外边距加上MediaQuery.of(context).padding.bottom手动补偿手势条高度。虽然略丑但稳。文字大小方面我没有用默认的 14.0 字号。由于汇率列表需要在一屏内展示更多信息标题用了 16数值部分用 18 加粗辅助说明用 12。这样即使部分列表项带子标题也不会显得拥挤。4.3 热重载失效与工程缓存问题前文提过热重载在鸿蒙端不稳定。这里分享一个排查思路如果 Flutter 代码改动后DevEco Studio 重新构建时发现旧的产物没更新很可能是 Gradle 或构建缓存问题。解决方式是在ohos目录下执行清理构建缓存的操作然后重新构建。另外一个比较隐蔽的问题是加了新的 pub 依赖后鸿蒙端没有重新同步。现象是 Dart 代码里 import 了新包编译时报错提示“找不到包”。此时需要重新运行flutter pub get然后在 DevEco Studio 里同步工程。这个步骤很多新手容易漏掉因为安卓端加依赖后直接 Gradle Sync 就行鸿蒙端则要两边都同步。4.4 常见问题速查表报错/现象可能原因解决方案请求返回 429接口限流降低刷新频率增加缓存真机请求超时权限未声明或网络限制检查 INTERNET 权限和网络安全配置编译找不到 pub 包鸿蒙端未同步依赖重新flutter pub get DevEco Sync界面白屏热重载异常重新完整构建图片显示不出来资源文件名大小写不一致统一小写命名汇率值变成 0返回值类型强转失败使用(value as num).toDouble()定时器一直请求页面销毁未取消dispose里timer.cancel()这些坑全是我们一行一行追过的这里列出来希望能减少你重复踩坑的时间。5. 个人体会与扩展建议这个汇率查询器做下来我的整体感受是Flutter 确实能“跨平台”但“跨”到鸿蒙时必须有心理准备它不是开箱即用的官方支持而是需要你多关注适配分支版本、构建环境和真机行为。与其花时间纠结热重载不生效不如先把核心业务逻辑写干净因为 Dart 代码本身在鸿蒙和安卓上基本没有差异需要适配的只是工程壳和构建流程。如果后续要在这个项目上继续扩展我建议优先做两个方向历史汇率趋势引入图表库按天拉取区间数据用折线图展示一周走势。注意要缓存按天数据避免频繁请求接口。货币搜索与排序当前货币列表是固定的扩展成可搜索列表后需要处理搜索时的性能问题。我用过的经验是开发类似工具类应用时尽量把网络请求层和数据模型层写得稳定界面上反而可以大胆迭代。汇率查询器虽然简单但完整跑通“Flutter 代码 → 鸿蒙 HAP 包 → 真机安装使用”这个链路对后续做任何严肃跨平台项目都非常有价值。最后给一个很实际的建议第一次做鸿蒙 Flutter 开发不要开太多功能先让最简单的页面在鸿蒙真机上跑起来把签名、权限、网络访问全部跑通再逐步增加功能。链路通了后面的事都只是堆工作量。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑