鸿蒙与Flutter混合开发:数据存储与同步
摘要:本文探讨了鸿蒙与Flutter混合开发中的数据存储问题。由于Flutter和原生应用使用不同的存储机制(如shared_preferences与Preferences),可能导致数据不一致、重复存储等问题。针对该问题,文章提出了一个统一数据存储管理器的解决方案,通过定义通用数据模型、建立同步机制和缓存策略来实现两端数据共享。该方案包含数据保存、读取、删除等核心功能,并通过平台通道实现Flut
问题背景
在鸿蒙与Flutter的混合开发中,数据存储是一个关键问题。Flutter应用和原生应用都需要存储数据,但它们使用不同的存储机制。如果没有建立一套有效的数据同步机制,会导致数据不一致、重复存储或数据丢失。此外,在多线程环境下,数据的并发访问也需要特别处理。
问题1:数据存储机制不统一
问题描述
Flutter通常使用shared_preferences或sqflite来存储数据,而原生鸿蒙应用可能使用Preferences或RDB数据库。当两端都需要访问同一份数据时,如果没有统一的存储机制,会导致数据重复存储、数据不同步或存储冗余。
根本原因
Flutter和原生代码各自有自己的存储方案,它们通常是独立的。当需要共享数据时,开发者往往会在两端各存储一份,导致数据一致性问题。此外,不同的存储格式也会增加数据转换的复杂度。
解决方案
统一数据存储管理器示例:
// 定义存储数据模型
class StorageData {
final String key;
final dynamic value;
final DateTime timestamp;
final String source; // 'flutter' 或 'native'
StorageData({
required this.key,
required this.value,
required this.timestamp,
required this.source,
});
Map<String, dynamic> toMap() {
return {
'key': key,
'value': value,
'timestamp': timestamp.millisecondsSinceEpoch,
'source': source,
};
}
factory StorageData.fromMap(Map<dynamic, dynamic> map) {
return StorageData(
key: map['key'] as String,
value: map['value'],
timestamp: DateTime.fromMillisecondsSinceEpoch(map['timestamp'] as int),
source: map['source'] as String,
);
}
}
// 统一数据存储管理器
class UnifiedStorageManager {
static final UnifiedStorageManager _instance = UnifiedStorageManager._internal();
factory UnifiedStorageManager() {
return _instance;
}
UnifiedStorageManager._internal();
late SharedPreferences _preferences;
final Map<String, StorageData> _cache = {};
// 初始化存储管理器
Future<void> initialize() async {
_preferences = await SharedPreferences.getInstance();
print('Storage manager initialized');
}
// 保存数据到本地和原生端
Future<bool> saveData(String key, dynamic value) async {
try {
final storageData = StorageData(
key: key,
value: value,
timestamp: DateTime.now(),
source: 'flutter',
);
// 保存到本地存储
await _saveToLocal(key, value);
// 同步到原生端
await _syncToNative(storageData);
// 更新缓存
_cache[key] = storageData;
print('Data saved: $key');
return true;
} catch (e) {
print('Error saving data: $e');
return false;
}
}
// 从本地或原生端读取数据
Future<dynamic> getData(String key) async {
// 先从缓存中查找
if (_cache.containsKey(key)) {
return _cache[key]!.value;
}
// 从本地存储中查找
final localValue = _getFromLocal(key);
if (localValue != null) {
return localValue;
}
// 从原生端查找
try {
final result = await PlatformChannelManager.methodChannel.invokeMethod(
'getData',
{'key': key},
);
if (result != null) {
final storageData = StorageData.fromMap(result as Map<dynamic, dynamic>);
_cache[key] = storageData;
return storageData.value;
}
} on PlatformException catch (e) {
print('Error getting data from native: ${e.message}');
}
return null;
}
// 删除数据
Future<bool> deleteData(String key) async {
try {
// 从本地删除
await _preferences.remove(key);
// 从原生端删除
await PlatformChannelManager.methodChannel.invokeMethod(
'deleteData',
{'key': key},
);
// 从缓存删除
_cache.remove(key);
print('Data deleted: $key');
return true;
} catch (e) {
print('Error deleting data: $e');
return false;
}
}
// 保存到本地存储
Future<void> _saveToLocal(String key, dynamic value) async {
if (value is String) {
await _preferences.setString(key, value);
} else if (value is int) {
await _preferences.setInt(key, value);
} else if (value is double) {
await _preferences.setDouble(key, value);
} else if (value is bool) {
await _preferences.setBool(key, value);
} else if (value is List<String>) {
await _preferences.setStringList(key, value);
}
}
// 从本地存储读取
dynamic _getFromLocal(String key) {
return _preferences.get(key);
}
// 同步到原生端
Future<void> _syncToNative(StorageData data) async {
try {
await PlatformChannelManager.methodChannel.invokeMethod(
'saveData',
data.toMap(),
);
} on PlatformException catch (e) {
print('Error syncing to native: ${e.message}');
}
}
}
// 使用示例
class StorageExample extends StatefulWidget {
State<StorageExample> createState() => _StorageExampleState();
}
class _StorageExampleState extends State<StorageExample> {
final _storageManager = UnifiedStorageManager();
String _displayText = 'No data';
void initState() {
super.initState();
_loadData();
}
Future<void> _loadData() async {
final value = await _storageManager.getData('user_name');
setState(() {
_displayText = value ?? 'No data';
});
}
Future<void> _saveData() async {
await _storageManager.saveData('user_name', 'John Doe');
await _loadData();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Storage Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Stored data: $_displayText'),
SizedBox(height: 16),
ElevatedButton(
onPressed: _saveData,
child: Text('Save Data'),
),
],
),
),
);
}
}
这段代码定义了一个统一的数据存储管理器,用于管理Flutter端和原生端的数据存储。当保存数据时,我们同时将数据保存到本地存储和原生端。当读取数据时,我们先从缓存中查找,然后从本地存储中查找,最后从原生端查找。这样可以确保数据的一致性。
原生端数据存储示例:
// 数据存储管理器
export class NativeStorageManager {
private storage: Map<string, StorageData> = new Map();
private methodChannel: MethodChannel | null = null;
// 初始化存储管理器
initialize(flutterEngine: any): void {
this.methodChannel = new MethodChannel(
flutterEngine.getDartExecutor(),
'com.example.qiyin/storage'
);
this.methodChannel.setMethodCallHandler((call: any, result: any) => {
switch (call.method) {
case 'saveData':
this.handleSaveData(call.arguments, result);
break;
case 'getData':
this.handleGetData(call.arguments, result);
break;
case 'deleteData':
this.handleDeleteData(call.arguments, result);
break;
default:
result.notImplemented();
}
});
}
// 处理保存数据
private handleSaveData(arguments: any, result: any): void {
try {
const key = arguments['key'] as string;
const value = arguments['value'];
const timestamp = arguments['timestamp'] as number;
const source = arguments['source'] as string;
const storageData: StorageData = {
key,
value,
timestamp,
source,
};
this.storage.set(key, storageData);
console.log(`Data saved: ${key}`);
result.success({ status: 'success' });
} catch (error) {
result.error('STORAGE_ERROR', 'Failed to save data', error);
}
}
// 处理读取数据
private handleGetData(arguments: any, result: any): void {
try {
const key = arguments['key'] as string;
const storageData = this.storage.get(key);
if (storageData) {
result.success({
key: storageData.key,
value: storageData.value,
timestamp: storageData.timestamp,
source: storageData.source,
});
} else {
result.success(null);
}
} catch (error) {
result.error('STORAGE_ERROR', 'Failed to get data', error);
}
}
// 处理删除数据
private handleDeleteData(arguments: any, result: any): void {
try {
const key = arguments['key'] as string;
this.storage.delete(key);
console.log(`Data deleted: ${key}`);
result.success({ status: 'success' });
} catch (error) {
result.error('STORAGE_ERROR', 'Failed to delete data', error);
}
}
}
// 存储数据接口
interface StorageData {
key: string;
value: any;
timestamp: number;
source: string;
}
在原生端,我们实现了一个数据存储管理器,用于存储和管理数据。当Flutter端调用保存、读取或删除数据的方法时,我们在原生端执行相应的操作。
最佳实践
- 统一存储接口:建立统一的存储接口,两端都通过这个接口进行数据操作。
- 数据同步:确保数据在两端保持同步,避免数据不一致。
- 缓存策略:使用缓存来减少存储访问的频率,提高性能。
问题2:并发访问导致数据竞争
问题描述
在多线程环境下,如果多个线程同时访问同一份数据,会导致数据竞争。例如,Flutter端和原生端可能同时修改同一个数据,导致数据不一致或损坏。
根本原因
数据竞争通常是由于缺乏适当的同步机制。当多个线程同时访问共享数据时,如果没有使用锁或其他同步机制,就会导致数据不一致。
解决方案
线程安全的数据存储示例:
// 线程安全的数据存储
class ThreadSafeStorage {
static final ThreadSafeStorage _instance = ThreadSafeStorage._internal();
factory ThreadSafeStorage() {
return _instance;
}
ThreadSafeStorage._internal();
final Map<String, dynamic> _data = {};
final Mutex _mutex = Mutex(); // 使用互斥锁
// 安全地保存数据
Future<void> saveData(String key, dynamic value) async {
await _mutex.lock();
try {
_data[key] = value;
print('Data saved safely: $key');
} finally {
_mutex.unlock();
}
}
// 安全地读取数据
Future<dynamic> getData(String key) async {
await _mutex.lock();
try {
return _data[key];
} finally {
_mutex.unlock();
}
}
// 安全地删除数据
Future<void> deleteData(String key) async {
await _mutex.lock();
try {
_data.remove(key);
print('Data deleted safely: $key');
} finally {
_mutex.unlock();
}
}
}
// 简单的互斥锁实现
class Mutex {
bool _locked = false;
final List<Completer<void>> _waitQueue = [];
Future<void> lock() async {
if (!_locked) {
_locked = true;
return;
}
final completer = Completer<void>();
_waitQueue.add(completer);
await completer.future;
}
void unlock() {
if (_waitQueue.isNotEmpty) {
final completer = _waitQueue.removeAt(0);
completer.complete();
} else {
_locked = false;
}
}
}
// 使用示例
class ConcurrentAccessExample extends StatefulWidget {
State<ConcurrentAccessExample> createState() => _ConcurrentAccessExampleState();
}
class _ConcurrentAccessExampleState extends State<ConcurrentAccessExample> {
final _storage = ThreadSafeStorage();
String _status = 'Ready';
Future<void> _testConcurrentAccess() async {
setState(() => _status = 'Testing...');
try {
// 创建多个并发任务
final futures = <Future>[];
for (int i = 0; i < 10; i++) {
futures.add(_storage.saveData('key_$i', 'value_$i'));
}
await Future.wait(futures);
setState(() => _status = 'All data saved successfully');
} catch (e) {
setState(() => _status = 'Error: $e');
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Concurrent Access')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_status),
SizedBox(height: 16),
ElevatedButton(
onPressed: _testConcurrentAccess,
child: Text('Test Concurrent Access'),
),
],
),
),
);
}
}
这段代码实现了一个线程安全的数据存储,使用互斥锁来保护共享数据。当多个线程同时访问数据时,互斥锁会确保只有一个线程能够访问数据,其他线程需要等待。这样可以避免数据竞争。
原生端并发控制示例:
// 线程安全的数据存储
export class ThreadSafeStorage {
private data: Map<string, any> = new Map();
private locks: Map<string, boolean> = new Map();
private waitQueues: Map<string, Array<() => void>> = new Map();
// 获取锁
private async acquireLock(key: string): Promise<void> {
if (!this.locks.has(key)) {
this.locks.set(key, false);
}
if (!this.locks.get(key)) {
this.locks.set(key, true);
return;
}
// 如果锁已被占用,等待
return new Promise((resolve) => {
if (!this.waitQueues.has(key)) {
this.waitQueues.set(key, []);
}
this.waitQueues.get(key)!.push(resolve);
});
}
// 释放锁
private releaseLock(key: string): void {
const waitQueue = this.waitQueues.get(key);
if (waitQueue && waitQueue.length > 0) {
const resolve = waitQueue.shift();
resolve?.();
} else {
this.locks.set(key, false);
}
}
// 安全地保存数据
async saveData(key: string, value: any): Promise<void> {
await this.acquireLock(key);
try {
this.data.set(key, value);
console.log(`Data saved safely: ${key}`);
} finally {
this.releaseLock(key);
}
}
// 安全地读取数据
async getData(key: string): Promise<any> {
await this.acquireLock(key);
try {
return this.data.get(key);
} finally {
this.releaseLock(key);
}
}
// 安全地删除数据
async deleteData(key: string): Promise<void> {
await this.acquireLock(key);
try {
this.data.delete(key);
console.log(`Data deleted safely: ${key}`);
} finally {
this.releaseLock(key);
}
}
}
在原生端,我们实现了一个基于锁的并发控制机制。每个数据键都有一个对应的锁,当多个线程同时访问同一个键时,只有一个线程能够获得锁,其他线程需要等待。
最佳实践
- 使用锁:使用互斥锁或其他同步机制来保护共享数据。
- 最小化临界区:尽量减少持有锁的时间,以提高并发性能。
- 避免死锁:在使用多个锁时,要注意避免死锁情况。
问题3:数据版本控制与迁移
问题描述
随着应用的发展,数据结构可能会发生变化。当应用更新时,旧版本的数据可能与新版本不兼容。如果没有建立一套有效的数据迁移机制,会导致应用崩溃或数据丢失。
根本原因
数据版本控制的缺失导致无法识别旧数据,也无法自动进行数据迁移。当应用更新后,新代码可能无法正确处理旧数据。
解决方案
数据版本控制与迁移示例:
// 定义数据版本
const int CURRENT_DATA_VERSION = 2;
// 数据迁移管理器
class DataMigrationManager {
static final DataMigrationManager _instance = DataMigrationManager._internal();
factory DataMigrationManager() {
return _instance;
}
DataMigrationManager._internal();
late SharedPreferences _preferences;
Future<void> initialize() async {
_preferences = await SharedPreferences.getInstance();
}
// 检查并执行数据迁移
Future<void> checkAndMigrate() async {
final currentVersion = _preferences.getInt('data_version') ?? 0;
if (currentVersion < CURRENT_DATA_VERSION) {
print('Migrating data from version $currentVersion to $CURRENT_DATA_VERSION');
if (currentVersion < 1) {
await _migrateToVersion1();
}
if (currentVersion < 2) {
await _migrateToVersion2();
}
await _preferences.setInt('data_version', CURRENT_DATA_VERSION);
print('Data migration completed');
}
}
// 迁移到版本1:添加新字段
Future<void> _migrateToVersion1() async {
print('Migrating to version 1...');
// 获取旧数据
final userName = _preferences.getString('user_name') ?? '';
// 创建新的数据结构
final userData = {
'name': userName,
'email': '', // 新字段,使用默认值
'createdAt': DateTime.now().millisecondsSinceEpoch,
};
// 保存新数据
await _preferences.setString('user_data', jsonEncode(userData));
// 删除旧数据
await _preferences.remove('user_name');
}
// 迁移到版本2:数据转换
Future<void> _migrateToVersion2() async {
print('Migrating to version 2...');
// 获取版本1的数据
final userDataJson = _preferences.getString('user_data');
if (userDataJson != null) {
final userData = jsonDecode(userDataJson) as Map<String, dynamic>;
// 转换数据格式
final migratedData = {
'id': DateTime.now().millisecondsSinceEpoch, // 新字段
'profile': {
'name': userData['name'],
'email': userData['email'],
},
'metadata': {
'createdAt': userData['createdAt'],
'updatedAt': DateTime.now().millisecondsSinceEpoch,
},
};
// 保存迁移后的数据
await _preferences.setString('user_profile', jsonEncode(migratedData));
// 删除旧数据
await _preferences.remove('user_data');
}
}
}
// 使用示例
class MigrationExample extends StatefulWidget {
State<MigrationExample> createState() => _MigrationExampleState();
}
class _MigrationExampleState extends State<MigrationExample> {
String _status = 'Initializing...';
void initState() {
super.initState();
_initializeAndMigrate();
}
Future<void> _initializeAndMigrate() async {
try {
final migrationManager = DataMigrationManager();
await migrationManager.initialize();
await migrationManager.checkAndMigrate();
setState(() => _status = 'Migration completed successfully');
} catch (e) {
setState(() => _status = 'Migration failed: $e');
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Data Migration')),
body: Center(
child: Text(_status),
),
);
}
}
这段代码实现了一个数据迁移管理器,用于处理数据版本升级。当应用启动时,我们检查当前的数据版本。如果版本低于最新版本,我们逐步执行迁移操作,将旧数据转换为新格式。每个迁移步骤都是独立的,可以逐个执行。
原生端数据迁移示例:
// 数据迁移管理器
export class DataMigrationManager {
private static readonly CURRENT_VERSION = 2;
// 检查并执行数据迁移
async checkAndMigrate(): Promise<void> {
const currentVersion = this.getDataVersion();
if (currentVersion < DataMigrationManager.CURRENT_VERSION) {
console.log(`Migrating data from version ${currentVersion} to ${DataMigrationManager.CURRENT_VERSION}`);
if (currentVersion < 1) {
await this.migrateToVersion1();
}
if (currentVersion < 2) {
await this.migrateToVersion2();
}
this.setDataVersion(DataMigrationManager.CURRENT_VERSION);
console.log('Data migration completed');
}
}
// 迁移到版本1
private async migrateToVersion1(): Promise<void> {
console.log('Migrating to version 1...');
// 获取旧数据并转换
const oldData = this.getOldData();
const newData = {
name: oldData.name,
email: '',
createdAt: Date.now(),
};
// 保存新数据
this.saveData('user_data', newData);
}
// 迁移到版本2
private async migrateToVersion2(): Promise<void> {
console.log('Migrating to version 2...');
const userData = this.getData('user_data');
if (userData) {
const migratedData = {
id: Date.now(),
profile: {
name: userData.name,
email: userData.email,
},
metadata: {
createdAt: userData.createdAt,
updatedAt: Date.now(),
},
};
this.saveData('user_profile', migratedData);
}
}
private getDataVersion(): number {
// 从存储中获取版本号
return 0; // 默认版本
}
private setDataVersion(version: number): void {
// 保存版本号到存储
}
private getOldData(): any {
// 获取旧数据
return {};
}
private getData(key: string): any {
// 获取数据
return null;
}
private saveData(key: string, data: any): void {
// 保存数据
}
}
在原生端,我们也实现了类似的数据迁移管理器,用于处理原生端的数据版本升级。
最佳实践
- 版本号管理:为数据结构添加版本号,便于识别和迁移。
- 增量迁移:使用增量迁移的方式,逐个版本进行升级,避免一次性大规模转换。
- 备份数据:在迁移前备份数据,以便在迁移失败时恢复。
总结
数据存储与同步是混合开发中的重要问题。通过建立统一的存储接口、实现并发控制和数据迁移机制,可以确保数据的一致性和应用的稳定性。在实际开发中,建议定期进行数据一致性检查,及时发现和解决潜在的问题。
更多推荐



所有评论(0)