1. 项目概述:当Flutter遇上OpenHarmony的油耗管理方案
在移动应用开发领域,Flutter的跨平台能力与OpenHarmony的分布式特性结合,为智能终端应用开发带来了全新可能。这次我们要构建的是一款面向车主的油耗追踪应用,核心功能包括加油记录、油耗计算、行驶数据分析等。选择Flutter框架开发OpenHarmony应用(通过flutter_for_openharmony适配层),既能复用Flutter丰富的UI组件和热重载优势,又能获得OpenHarmony在多设备协同上的独特能力。
这个项目的技术亮点在于:
- 使用Flutter实现90%的共享代码
- 通过OpenHarmony的分布式数据管理实现手机与车机数据同步
- 利用SQLite本地存储保证离线使用体验
- 采用响应式编程架构处理复杂的表单交互
提示:虽然项目名称为flutter_for_openharmonyFillUp,但实际开发中我们使用的是官方推荐的flutter_ohos插件,这是目前最稳定的Flutter on OpenHarmony解决方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心功能设计与技术选型
2.1 数据模型设计
油耗追踪的核心是数据建模,我们设计了三个核心实体类:
dart复制class FillUpRecord {
DateTime date;
double odometer;
double volume;
double pricePerLiter;
String gasStation;
String? memo;
// 计算本次加油的行驶里程(需要关联上条记录)
double get tripDistance => ...
// 计算本次加油的油耗(L/100km)
double get fuelConsumption => ...
}
class Vehicle {
String id;
String name;
String model;
double initialOdometer;
String fuelType;
}
class Statistics {
double avgConsumption;
double totalCost;
double costPerKm;
List<ConsumptionTrend> trends;
}
数据持久化方案选型时,我们放弃了常规的Hive/SharedPreferences组合,因为:
- OpenHarmony的文件系统访问有特殊限制
- 需要支持复杂的查询统计功能
- 未来可能扩展多设备同步
最终采用SQLite+floor的组合:
- SQLite作为底层引擎
- floor提供类型安全的Dart接口
- 通过openharmony_sqlite插件适配系统差异
2.2 状态管理方案
面对包含表单验证、数据加载、统计计算等复杂交互的场景,经过对比测试后选择了Riverpod+StateNotifier的方案:
dart复制final fillUpListProvider = StateNotifierProvider<FillUpListNotifier, AsyncValue<List<FillUpRecord>>>((ref) {
return FillUpListNotifier();
});
class FillUpListNotifier extends StateNotifier<AsyncValue<List<FillUpRecord>>> {
final Database _db;
FillUpListNotifier(this._db): super(const AsyncValue.loading()) {
_loadData();
}
Future<void> _loadData() async {
try {
final records = await _db.fillUpDao.getAllRecords();
state = AsyncValue.data(records);
} catch (e) {
state = AsyncValue.error(e, StackTrace.current);
}
}
// 添加新记录的方法
Future<void> addRecord(FillUpRecord record) async {
await _db.fillUpDao.insertRecord(record);
await _loadData(); // 重新加载数据
}
}
这种架构的优势在于:
- 清晰的关注点分离(UI/逻辑/持久化)
- 内置的异步状态处理
- 易于测试和维护
- 完美支持OpenHarmony的后台任务机制
3. 记录详情页的实现细节
3.1 表单交互设计
加油记录表单需要处理多种边界情况:
- 里程表数值必须单调递增
- 油价需要根据地区自动补全
- 日期选择需要特殊处理时区
我们使用ReactiveForms构建了带验证的复杂表单:
dart复制final form = fb.group({
'date': FormControl<DateTime>(
validators: [Validators.required],
value: DateTime.now()
),
'odometer': FormControl<double>(
validators: [
Validators.required,
Validators.min(lastRecord?.odometer ?? 0),
CustomValidators.numberPrecision(1)
]
),
'volume': FormControl<double>(
validators: [
Validators.required,
Validators.min(0.1),
CustomValidators.numberPrecision(2)
]
),
'price': FormControl<double>(
validators: [
Validators.required,
CustomValidators.numberPrecision(3)
]
),
'station': FormControl<String>(
validators: [Validators.maxLength(50)]
)
});
// 自动计算功能
void _setupAutoCalculations() {
form.controls['volume']!.valueChanges.listen((volume) {
if (volume != null && form.controls['price']!.value != null) {
final total = volume * form.controls['price']!.value;
_updateTotalCost(total);
}
});
}
3.2 数据可视化实现
详情页需要展示油耗趋势图表,我们对比了多个图表库后选择fl_chart:
dart复制LineChartData buildConsumptionChart(List<FillUpRecord> records) {
return LineChartData(
lineTouchData: LineTouchData(enabled: true),
gridData: FlGridData(show: true),
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
return Text(records[value.toInt()].date.toString('MM-dd'));
},
),
),
),
borderData: FlBorderData(show: true),
lineBarsData: [
LineChartBarData(
spots: records.asMap().entries.map((e) {
return FlSpot(
e.key.toDouble(),
e.value.fuelConsumption
);
}).toList(),
isCurved: true,
barWidth: 4,
belowBarData: BarAreaData(show: true),
),
],
);
}
注意:在OpenHarmony上使用自定义Paint时,需要特别处理GPU加速兼容性问题。我们通过以下配置解决:
dart复制void main() { WidgetsFlutterBinding.ensureInitialized(); if (isOpenHarmony) { // 调整渲染设置 FlOpenHarmonyRenderKit.setup(); } runApp(const MyApp()); }
4. OpenHarmony特性集成
4.1 分布式数据同步
利用OpenHarmony的分布式能力,我们实现了手机与车机的数据自动同步:
java复制// 在Ability中注册数据观察者
private void registerDataObserver() {
DataObserverHelper.registerObserver(
this,
new IDataObserver.Stub() {
@Override
public void onChange() {
getUITaskDispatcher().asyncDispatch(() -> {
// 通知Flutter端刷新数据
EventChannel channel = new EventChannel(
getFlutterEngine().getDartExecutor(),
"data_sync_events"
);
channel.setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object args, EventChannel.EventSink events) {
events.success("data_changed");
}
});
});
}
},
"datashare:///fillup_records"
);
}
同步策略设计要点:
- 基于时间戳的增量同步
- 冲突解决采用"最后修改优先"原则
- 仅在WiFi环境下同步大数据量
- 支持手动触发同步
4.2 原子化服务适配
为了让应用能作为OpenHarmony原子化服务运行,我们进行了以下适配:
- 修改
config.json配置:
json复制{
"abilities": [
{
"name": "MainAbility",
"type": "page",
"formsEnabled": true,
"forms": [
{
"name": "widget",
"description": "油耗统计卡片",
"type": "JS",
"colorMode": "auto",
"isDefault": true,
"updateEnabled": true,
"scheduledUpdateTime": "10:30",
"updateDuration": 1
}
]
}
]
}
- 实现卡片数据提供器:
dart复制class FuelCardProvider extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer(builder: (context, ref, _) {
final stats = ref.watch(statisticsProvider);
return stats.when(
loading: () => _buildLoadingCard(),
error: (err, stack) => _buildErrorCard(err),
data: (data) => _buildDataCard(data),
);
});
}
Widget _buildDataCard(Statistics stats) {
return Card(
child: Column(
children: [
Text("平均油耗: ${stats.avgConsumption.toStringAsFixed(1)}L/100km"),
GaugeChart(value: stats.avgConsumption),
Text("每公里成本: ¥${stats.costPerKm.toStringAsFixed(2)}")
],
),
);
}
}
5. 性能优化实践
5.1 数据库查询优化
针对可能积累的大量历史记录,我们实施了以下优化措施:
- 分页加载实现:
dart复制Future<List<FillUpRecord>> getRecordsPaged(int page, int pageSize) async {
return database.transaction((txn) async {
return await txn.rawQuery('''
SELECT * FROM fillups
ORDER BY date DESC
LIMIT ? OFFSET ?
''', [pageSize, page * pageSize]);
});
}
- 添加复合索引:
sql复制CREATE INDEX idx_fillups_vehicle_date ON fillups(vehicle_id, date);
- 预计算统计信息:
dart复制class StatisticsRepository {
Future<Statistics> computeStats(String vehicleId) async {
// 使用WITH子句减少重复计算
final result = await database.rawQuery('''
WITH consumption_data AS (
SELECT
(curr.odometer - prev.odometer) / curr.volume * 100 AS consumption
FROM fillups curr
JOIN fillups prev ON curr.prev_id = prev.id
WHERE curr.vehicle_id = ?
)
SELECT
AVG(consumption) as avg_consumption,
SUM(curr.volume * curr.price_per_liter) as total_cost,
SUM(curr.volume * curr.price_per_liter) / SUM(curr.odometer - prev.odometer) as cost_per_km
FROM fillups curr
JOIN fillups prev ON curr.prev_id = prev.id
WHERE curr.vehicle_id = ?
''', [vehicleId, vehicleId]);
return Statistics.fromMap(result.first);
}
}
5.2 内存管理策略
在资源受限的OpenHarmony设备上,我们采用了特殊的内存管理方案:
- 图片资源优化:
yaml复制flutter:
assets:
- assets/images/compressed/
fonts:
- family: Inter
fonts:
- asset: assets/fonts/Inter-Regular.otf
weight: 400
- asset: assets/fonts/Inter-Medium.otf
weight: 500
- 列表渲染优化:
dart复制ListView.builder(
itemCount: records.length,
itemBuilder: (context, index) {
final record = records[index];
return AutoDispose(
key: ValueKey(record.id),
child: FillUpListItem(
record: record,
onTap: () => _showDetails(record),
),
);
},
addAutomaticKeepAlives: false,
addRepaintBoundaries: true,
)
- 使用
compute进行后台计算:
dart复制Future<Statistics> _computeStats() async {
return await compute(_heavyCalculation, records);
}
static Statistics _heavyCalculation(List<FillUpRecord> records) {
// 复杂的统计计算
}
6. 测试与调试技巧
6.1 自动化测试策略
我们建立了三层测试体系:
- 单元测试(业务逻辑):
dart复制void main() {
test('油耗计算逻辑测试', () {
final prev = FillUpRecord(
odometer: 1000,
volume: 50,
pricePerLiter: 7.5,
date: DateTime(2023,1,1)
);
final curr = FillUpRecord(
odometer: 1500,
volume: 40,
pricePerLiter: 7.8,
date: DateTime(2023,1,10)
);
expect(curr.tripDistance, 500);
expect(curr.fuelConsumption, closeTo(8.0, 0.1));
});
}
- Widget测试(UI交互):
dart复制testWidgets('表单验证测试', (tester) async {
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(
home: AddRecordPage(),
),
),
);
// 测试无效输入
await tester.enterText(find.byType(TextField).first, '');
await tester.tap(find.text('保存'));
await tester.pump();
expect(find.text('请输入有效里程数'), findsOneWidget);
});
- 集成测试(完整流程):
dart复制void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('完整添加记录流程', (tester) async {
// 启动应用
await tester.pumpWidget(const MyApp());
// 导航到添加页面
await tester.tap(find.text('添加记录'));
await tester.pumpAndSettle();
// 填写表单
await tester.enterText(find.byType(TextField).at(0), '2023-01-01');
await tester.enterText(find.byType(TextField).at(1), '1500');
// ...其他字段
// 提交表单
await tester.tap(find.text('保存'));
await tester.pumpAndSettle();
// 验证结果
expect(find.text('记录保存成功'), findsOneWidget);
expect(find.text('1500 km'), findsOneWidget);
});
}
6.2 OpenHarmony真机调试
在OpenHarmony设备上调试Flutter应用的特殊技巧:
- 日志过滤命令:
bash复制hdc shell hilog | grep Flutter
- 性能分析工具使用:
dart复制void main() {
// 在main函数中添加性能监控
FlutterOHOS.init();
FlutterOHOS.enablePerformanceOverlay();
runApp(const MyApp());
}
- 内存泄漏检测方法:
dart复制// 在DevTools中检查以下指标:
// - Dart VM内存使用
// - Skia图片缓存大小
// - OpenHarmony原生内存占用
7. 项目构建与发布
7.1 多环境配置管理
我们使用flutter_config管理不同环境的配置:
yaml复制# .env.prod
APP_NAME=油耗追踪专业版
API_URL=https://api.fuel-tracker.com
ANALYTICS_ENABLED=true
# .env.dev
APP_NAME=油耗追踪(开发版)
API_URL=http://localhost:8080
ANALYTICS_ENABLED=false
构建脚本示例:
bash复制#!/bin/bash
# 读取构建类型
BUILD_TYPE=$1
case $BUILD_TYPE in
"prod")
cp .env.prod .env
flutter build ohos --release
;;
"dev")
cp .env.dev .env
flutter build ohos --debug
;;
*)
echo "Usage: ./build.sh [prod|dev]"
exit 1
;;
esac
7.2 OpenHarmony应用签名
发布到AppGallery前的签名步骤:
- 生成密钥库:
bash复制keytool -genkeypair -alias "fuelup" -keyalg RSA -keysize 2048 \
-validity 3650 -keystore fuelup.keystore \
-storepass [密码] -keypass [密码]
- 配置签名信息:
json复制// ohos/build.gradle
android {
signingConfigs {
release {
storeFile file("../fuelup.keystore")
storePassword System.getenv("STORE_PASSWORD")
keyAlias "fuelup"
keyPassword System.getenv("KEY_PASSWORD")
v1SigningEnabled true
v2SigningEnabled true
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
- 构建发布包:
bash复制flutter build ohos --release --target-platform ohos-arm64
8. 实际开发中的经验教训
经过三个月的开发和迭代,总结出以下关键经验:
-
Flutter与OpenHarmony的兼容性问题:
- 部分插件(如camera)需要自行实现ohos版本
- 文本渲染在ohos上有细微差异,需要额外测试
- 动画性能在不同设备上表现不一致
-
分布式数据同步的陷阱:
- 设备时区不同会导致数据不一致
- 网络切换时可能丢失同步进度
- 需要处理设备离线又上线的边界情况
-
性能优化的关键点:
- 避免在build方法中执行耗时操作
- 对长列表使用
ListView.builder+AutomaticKeepAlive - 复杂计算使用
compute放到isolate执行
-
测试策略的调整:
- 增加设备兼容性测试矩阵
- 模拟弱网环境测试数据同步
- 针对不同屏幕尺寸测试布局
-
用户反馈最有价值的改进:
- 添加加油位置地图标记
- 支持多车辆切换
- 导出Excel报表功能
- 与车载系统深度集成的快捷操作
这个项目最让我意外的是OpenHarmony的分布式能力确实带来了独特价值 - 当用户在手机上添加记录后,上车时导航系统已经能根据最新油耗数据调整路线建议,这种无缝体验是传统开发方式难以实现的。未来计划探索更多设备协同场景,比如利用智能手表检测驾驶行为对油耗的影响。
