Flutter 三方库 simple_circular_progress_bar 在 OHOS 平台的适配实践
引言
OpenHarmony(OHOS)生态发展很快,其"一次开发,多端部署"的理念,与 Flutter 的跨平台愿景不谋而合。现在,越来越多的开发者开始尝试将成熟的 Flutter 应用生态迁移到鸿蒙平台,以覆盖更广泛的设备。不过,Flutter 丰富的三方库大多是围绕 iOS 和 Android 构建的,直接搬到 OHOS 平台,经常会遇到原生端实现缺失的问题。
本文将以一个典型的纯 Dart 渲染库------simple_circular_progress_bar(圆形进度条库)为例,分享将其完整适配到 OHOS 平台的实战过程。我们不止步于"能跑通",还会深入聊聊 Flutter 插件在 OHOS 端的工作原理、适配时面临的关键技术选择、性能上需要注意的点,并提供可运行的完整代码。希望能为你后续的跨平台适配工作,提供一套可以参考的思路和方法。
一、适配原理与技术分析
1.1 Flutter 插件架构回顾
Flutter 与原生平台交互,核心靠的是平台通道(Platform Channel),主要有三种:
- MethodChannel:用于方法调用,传递字符串或一些半结构化的信息。
- EventChannel:用于数据流通信,支持原生端持续向 Dart 端发送事件。
- BasicMessageChannel:用于简单的数据传递,使用标准的消息编解码器。
对于 simple_circular_progress_bar 这种纯用 Dart Canvas 绘制的 UI 库,如果它不依赖任何原生功能,理论上可以直接在 OHOS 的 Flutter 引擎上运行,无需额外适配。但为了更贴近实际开发中可能遇到的复杂场景,我们假设它的某个高级功能(比如,依赖原生传感器数据来控制进度)需要调用 OHOS 的系统 API。这样一来,我们就得为它创建 OHOS 端的原生实现。
1.2 OHOS 平台适配层设计
适配的核心,其实就是为这个 Flutter 插件在 OHOS 端造一个"孪生兄弟"。我们需要在 OHOS 侧建立一个原生模块,让它能够:
- 接收来自 Dart 端的指令(比如开始动画、更新进度值)。
- 调用 OHOS NDK 或 JS UI Kit 提供的原生能力(比如读取系统电源或传感器数据)。
- 将处理结果或原生事件回传给 Dart 端。
技术栈怎么选? OHOS 提供了多种原生开发方式。考虑到 Flutter 引擎本身是基于 C/C++ 的,为了获得最好的性能和最无缝的集成体验,我们优先选择使用 Native API (C API) 来开发插件的原生部分。
二、完整适配步骤与代码实现
2.1 环境配置与项目初始化
(这部分在原"准备工作"基础上做了补充)
需要准备的系统和工具:
- 开发机:Ubuntu 22.04 / Windows 11(配 WSL2)/ macOS 13+
- Flutter SDK:3.19.0+(确保包含对 OHOS 的实验性支持)
- DevEco Studio:4.0+(用于 OHOS 原生侧的开发和调试)
- OHOS SDK:API 11+
环境搭建步骤:
bash
# 1. 配置 Flutter for OpenHarmony 环境
flutter channel master # 使用 master 分支以获取最新的 OHOS 支持
flutter upgrade
flutter doctor --android-licenses
# 检查 Flutter 对 OHOS 的支持情况
flutter doctor -v
# 如果配置正确,应该能看到 OpenHarmony 设备相关的工具链信息。
# 2. 创建测试项目并引入待适配的库
flutter create --platforms=ohos,android ohos_circular_progress_demo
cd ohos_circular_progress_demo
# 添加原版的 simple_circular_progress_bar 库
flutter pub add simple_circular_progress_bar
2.2 创建 OHOS 平台插件包
因为原库没有 OHOS 实现,我们需要自己创建一个 flutter_ohos_plugin。
第一步:创建插件项目结构
bash
# 在项目根目录下创建 OHOS 原生插件模块
mkdir -p ohos/simple_circular_progress_bar
cd ohos/simple_circular_progress_bar
ohos create -t native_library simple_circular_progress_bar_impl
这个命令会生成一个标准的 OHOS Native C++ 库项目结构,里面包含 cpp 目录、CMakeLists.txt 和 index.d.ts 声明文件。
第二步:实现 Dart 端平台接口 (lib/src/ohos_adapter.dart)
我们首先在 Dart 层定义一个接口,把需要 OHOS 平台实现的功能抽象出来。这里我们假设需要从 OHOS 系统获取"电池健康状态"来影响进度条动画。
dart
// lib/src/ohos_adapter.dart
import 'dart:async';
import 'package:flutter/services.dart';
/// 定义需要 OHOS 平台实现的特定功能
class OhosProgressController {
static const MethodChannel _channel = MethodChannel(
'com.example/simple_circular_progress_bar_ohos');
/// 获取 OHOS 系统电池健康度(模拟一个需要原生能力的场景)
/// 返回一个 0.0 到 1.0 之间的值,1.0 表示电池完全健康。
static Future<double> getBatteryHealthFactor() async {
try {
final double factor = await _channel.invokeMethod('getBatteryHealth');
return factor.clamp(0.0, 1.0);
} on PlatformException catch (e) {
print('获取电池健康度失败: ${e.message}');
// 降级策略:返回默认值 1.0,确保核心的进度条功能不受影响
return 1.0;
}
}
/// 通知 OHOS 端进度条动画的生命周期事件
static Future<void> notifyAnimationState(bool isAnimating) async {
try {
await _channel.invokeMethod(
'onAnimationStateChanged',
{'isAnimating': isAnimating},
);
} on PlatformException catch (e) {
print('通知动画状态失败: ${e.message}');
// 这个错误可以忽略,不影响主流程
}
}
}
第三步:实现 OHOS Native C++ 层 (ohos/simple_circular_progress_bar/cpp/plugin_impl.cpp)
这是整个适配最核心的一步,我们需要实现 Dart 端通过 MethodChannel 调用的原生逻辑。
cpp
#include "napi/native_api.h"
#include "hilog/log.h"
#include <string>
#include <map>
#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0xFF00
#define LOG_TAG "ProgressBarPlugin"
// 模拟获取电池健康度,真实场景中应调用 OHOS 电池服务 API
static double SimulateGetBatteryHealth() {
// TODO: 替换为实际的 OHOS 系统调用,例如通过 `BatteryInfo` 接口
// 这里先返回一个模拟值
return 0.85; // 表示 85% 健康度
}
// 处理 Dart 端调用 `getBatteryHealth` 方法
static napi_value OHOS_GetBatteryHealth(napi_env env, napi_callback_info info) {
napi_value result = nullptr;
double healthFactor = SimulateGetBatteryHealth();
// 将 double 值包装成 napi_value 返回给 Dart
napi_create_double(env, healthFactor, &result);
HiLog::Info(LABEL, "OHOS_GetBatteryHealth 被调用,返回值: %{public}f", healthFactor);
return result;
}
// 处理 Dart 端调用 `onAnimationStateChanged` 方法
static napi_value OHOS_OnAnimationStateChanged(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value argv[1] = {nullptr};
napi_value thisArg = nullptr;
void* data = nullptr;
// 解析从 JavaScript 端传入的参数
napi_get_cb_info(env, info, &argc, argv, &thisArg, &data);
if (argc < 1) {
napi_throw_error(env, nullptr, "参数无效");
return nullptr;
}
bool isAnimating = false;
napi_get_value_bool(env, argv[0], &isAnimating);
HiLog::Info(LABEL, "动画状态变为: %{public}s", isAnimating ? "进行中" : "已停止");
// 这里可以触发 OHOS 端的其他操作,例如根据动画状态调整后台任务优先级
// TODO: 调用 OHOS 后台任务管理 API
napi_value undefined;
napi_get_undefined(env, &undefined);
return undefined;
}
// 模块初始化函数,在这里注册 MethodChannel 对应的方法
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {
{"getBatteryHealth", nullptr, OHOS_GetBatteryHealth, nullptr, nullptr, nullptr, napi_default, nullptr},
{"onAnimationStateChanged", nullptr, OHOS_OnAnimationStateChanged, nullptr, nullptr, nullptr, napi_default, nullptr}
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
HiLog::Info(LABEL, "simple_circular_progress_bar OHOS 插件初始化完成。");
return exports;
}
// 定义模块
NAPI_MODULE(simple_circular_progress_bar_ohos, Init)
第四步:配置 Native 模块的编译与依赖 (ohos/simple_circular_progress_bar/CMakeLists.txt)
cmake
cmake_minimum_required(VERSION 3.4.1)
project(simple_circular_progress_bar_ohos)
set(NATIVE_LIB_NAME simple_circular_progress_bar_ohos)
add_library(${NATIVE_LIB_NAME} SHARED
./cpp/plugin_impl.cpp
)
target_link_libraries(${NATIVE_LIB_NAME} PUBLIC
hilog_ndk.z
# 可以链接其他 OHOS NDK 库,例如电池服务: libbattery_info.z
)
# 包含 OHOS NDK 头文件
target_include_directories(${NATIVE_LIB_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/cpp
${OHOS_NDK_HOME}/include
)
第五步:在 Flutter 应用中集成与使用 最后,我们修改 lib/main.dart,把 OHOS 适配层和原来的 UI 库结合起来。
dart
import 'package:flutter/material.dart';
import 'package:simple_circular_progress_bar/simple_circular_progress_bar.dart';
import './src/ohos_adapter.dart'; // 导入我们的适配层
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
double _progress = 0;
double _batteryHealthFactor = 1.0;
bool _isAnimating = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_initBatteryHealth();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
// 应用生命周期回调,用来通知 OHOS 端动画状态
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
final isAnimating = state == AppLifecycleState.resumed && _isAnimating;
OhosProgressController.notifyAnimationState(isAnimating);
}
Future<void> _initBatteryHealth() async {
// 从 OHOS 平台获取影响动画的因子
final factor = await OhosProgressController.getBatteryHealthFactor();
setState(() {
_batteryHealthFactor = factor;
});
}
void _startProgress() {
setState(() {
_isAnimating = true;
_progress = 0;
});
// 通知 OHOS 端动画开始了
OhosProgressController.notifyAnimationState(true);
// 模拟一个受电池健康度影响的进度动画
const totalSteps = 100;
final stepDelay = 50 + (100 * (1.0 - _batteryHealthFactor)).toInt(); // 健康度越低,动画越慢
Future.doWhile(() async {
if (_progress >= 100) {
setState(() => _isAnimating = false);
OhosProgressController.notifyAnimationState(false);
return false;
}
await Future.delayed(Duration(milliseconds: stepDelay));
setState(() => _progress += (100 / totalSteps));
return true;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('OHOS 适配进度条'),
subtitle: Text('电池健康因子: ${_batteryHealthFactor.toStringAsFixed(2)}'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SimpleCircularProgressBar(
size: 200,
progressStrokeWidth: 15,
backStrokeWidth: 15,
progressColors: const [Colors.blue, Colors.lightBlue],
backColor: Colors.grey.shade200,
animationDuration: 0, // 我们自定义动画,所以禁用内置动画
valueNotifier: ValueNotifier(_progress),
onGetText: (double value) {
return Text(
'${value.toInt()}%',
style: const TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
);
},
),
const SizedBox(height: 40),
Text(
'进度: ${_progress.toStringAsFixed(1)}%',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _isAnimating ? null : _startProgress,
child: const Text('开始受 OHOS 健康度影响的动画'),
),
],
),
),
);
}
}
三、性能优化与调试要点
3.1 降低平台通道开销
- 批量化通信:千万别在动画的每一帧都通过 MethodChannel 调用原生端。像上面的例子,只在动画开始、结束这类关键生命周期节点进行通信。
- 使用高效数据类型 :在通道里传递
int、double、bool这些基本类型,而不是复杂的对象,能显著减少序列化和反序列化的开销。
3.2 原生端性能优化
- 异步处理 :OHOS 原生端如果需要执行耗时操作(比如真的去查询电池服务),一定要用异步任务,避免阻塞 Flutter 的 UI 线程。可以用
libuv或者 OHOS 自带的异步机制。 - 资源管理 :确保在插件生命周期结束(比如
onDetached)时,释放所有在 OHOS 端申请的资源(比如传感器监听器),防止内存泄漏。
3.3 调试与日志
- 善用 HiLog :在 OHOS Native 代码里多打一些
HiLog,可以在 DevEco Studio 的Log窗口清晰看到,方便跟踪原生逻辑的执行路径。 - Flutter 侧日志 :Dart 端可以用
print或者logger包,并通过flutter logs命令来抓取混合日志。 - 错误边界处理 :就像 Dart 代码里展示的,所有
PlatformChannel的调用都必须用try-catch包起来。这能保证即使原生端出了异常,Flutter 应用也不会崩溃,并且有合理的降级方案。
四、总结与展望
通过 simple_circular_progress_bar 这个库的适配实战,我们完整走了一遍将 Flutter 三方库迁移到 OpenHarmony 平台的流程。从环境搭建、插件架构设计、Dart 与 OHOS Native(C++) 的双向通信,到性能优化和调试,每个环节都进行了探讨。
有几点关键体会:
- 适配的本质 ,就是为 Flutter 插件在 OHOS 平台建立一个功能对等的原生实现,核心是玩转
Platform Channel。 - 技术选型上,OHOS Native API (C/C++) 是实现高性能、深度系统集成插件的最佳路径。
- 代码健壮性很重要,适配必须包含完善的错误处理和降级策略,确保即使 OHOS 平台某个功能暂时缺失,Flutter 应用的核心体验仍然在线。
- 要时刻注意性能,跨平台通信的频次和数据量要精心设计,避免这里成为性能瓶颈。
展望未来,随着 OpenHarmony Hvigor 构建系统对 Flutter 插件编译的支持越来越完善,以及 flutter_ohos_tools 这类工具的成熟,Flutter 库的 OHOS 平台适配流程肯定会变得更加标准和自动化。到时候,开发者就能更专注于业务逻辑的跨平台抽象,而不是底层适配的细枝末节,这一定会加速鸿蒙生态的繁荣。
附录:性能对比数据(模拟)
| 场景 | 纯 Dart 版本 (FPS) | 集成 OHOS 插件版本 (FPS) | 说明 |
|---|---|---|---|
| 静态显示 | 60 | 60 | 无平台调用,无差异 |
| 基础动画 | 58-60 | 58-60 | 仅 Dart 端动画,无差异 |
| 高频平台调用 (每帧) | 60 | ~35 | 性能瓶颈出现 |
| 低频平台调用 (生命周期事件) | 60 | 58-60 | 推荐做法,开销几乎可忽略 |
从模拟数据可以看出,只要设计合理(采用低频、批量的平台通信),适配对 Flutter 应用性能的影响微乎其微。这让我们有信心开发出既功能丰富又高性能的跨 OHOS 应用。