问题背景

在鸿蒙与Flutter的混合开发中,数据存储是一个关键问题。Flutter应用和原生应用都需要存储数据,但它们使用不同的存储机制。如果没有建立一套有效的数据同步机制,会导致数据不一致、重复存储或数据丢失。此外,在多线程环境下,数据的并发访问也需要特别处理。

问题1:数据存储机制不统一

问题描述

Flutter通常使用shared_preferencessqflite来存储数据,而原生鸿蒙应用可能使用PreferencesRDB数据库。当两端都需要访问同一份数据时,如果没有统一的存储机制,会导致数据重复存储、数据不同步或存储冗余。

根本原因

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端调用保存、读取或删除数据的方法时,我们在原生端执行相应的操作。

最佳实践

  1. 统一存储接口:建立统一的存储接口,两端都通过这个接口进行数据操作。
  2. 数据同步:确保数据在两端保持同步,避免数据不一致。
  3. 缓存策略:使用缓存来减少存储访问的频率,提高性能。

问题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);
    }
  }
}

在原生端,我们实现了一个基于锁的并发控制机制。每个数据键都有一个对应的锁,当多个线程同时访问同一个键时,只有一个线程能够获得锁,其他线程需要等待。

最佳实践

  1. 使用锁:使用互斥锁或其他同步机制来保护共享数据。
  2. 最小化临界区:尽量减少持有锁的时间,以提高并发性能。
  3. 避免死锁:在使用多个锁时,要注意避免死锁情况。

问题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 {
    // 保存数据
  }
}

在原生端,我们也实现了类似的数据迁移管理器,用于处理原生端的数据版本升级。

最佳实践

  1. 版本号管理:为数据结构添加版本号,便于识别和迁移。
  2. 增量迁移:使用增量迁移的方式,逐个版本进行升级,避免一次性大规模转换。
  3. 备份数据:在迁移前备份数据,以便在迁移失败时恢复。

总结

数据存储与同步是混合开发中的重要问题。通过建立统一的存储接口、实现并发控制和数据迁移机制,可以确保数据的一致性和应用的稳定性。在实际开发中,建议定期进行数据一致性检查,及时发现和解决潜在的问题。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐