You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Flutter+Isar实现类WhatsApp的高效备份与多设备同步方案问询

Flutter + Isar:Google Drive备份与多设备同步方案

针对你的Flutter+Isar+Google Drive备份同步需求,整理了以下落地方案、代码片段和最佳实践:


一、Isar数据库高效备份到Google Drive

核心思路

先获取Isar数据库的物理文件(.isar主文件和.isar.wal日志文件),压缩后通过Google Drive API上传,优先支持增量备份减少带宽消耗。

可用库

  • googleapis_auth + googleapis:官方Google Drive API客户端
  • archive:压缩数据库文件
  • path_provider:获取本地文件路径

代码片段

import 'dart:io';
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'package:archive/archive.dart';
import 'package:googleapis/drive/v3.dart' as drive;
import 'package:googleapis_auth/googleapis_auth.dart';

// 获取Isar数据库文件路径
Future<List<File>?> getIsarDbFiles(String dbName) async {
  final isarDir = await Isar.getInstance()?.directory;
  if (isarDir == null) return null;
  
  final dbFile = File('$isarDir/$dbName.isar');
  final walFile = File('$isarDir/$dbName.isar.wal');
  
  return [dbFile, walFile];
}

// 压缩数据库文件
Future<File> compressDbFiles(List<File> files) async {
  final tempDir = await getTemporaryDirectory();
  final zipPath = '${tempDir.path}/isar_backup_${DateTime.now().millisecondsSinceEpoch}.zip';
  final zipEncoder = ZipFileEncoder();
  
  zipEncoder.create(zipPath);
  for (var file in files) {
    if (await file.exists()) zipEncoder.addFile(file);
  }
  zipEncoder.close();
  
  return File(zipPath);
}

// 上传到Google Drive(需提前完成OAuth2认证)
Future<void> uploadToDrive(AuthClient client, File zipFile) async {
  final driveApi = drive.DriveApi(client);
  
  // 获取或创建备份文件夹
  final folderQuery = "name='Isar Backups' and mimeType='application/vnd.google-apps.folder' and trashed=false";
  final folderResponse = await driveApi.files.list(q: folderQuery, spaces: 'drive');
  
  String folderId;
  if (folderResponse.files?.isNotEmpty == true) {
    folderId = folderResponse.files!.first.id!;
  } else {
    final newFolder = drive.File()
      ..name = 'Isar Backups'
      ..mimeType = 'application/vnd.google-apps.folder';
    folderId = (await driveApi.files.create(newFolder)).id!;
  }
  
  // 上传压缩包
  final media = drive.Media(zipFile.openRead(), zipFile.lengthSync());
  final uploadFile = drive.File()
    ..name = zipFile.path.split('/').last
    ..parents = [folderId];
  
  await driveApi.files.create(uploadFile, uploadMedia: media);
}

最佳实践

  • 备份时机:优先让用户手动触发,后台定时备份需注意iOS/Android的后台限制
  • 增量备份:记录上次备份时间,仅备份修改过的数据(可通过Isar的watch()监听变更生成增量快照)
  • 备份锁:备份前调用isar.writeTxnSync(() {})短暂锁定写入,避免文件损坏

二、多设备实时/近实时同步

两种落地方案

方案1:免费开源方案(Firebase作为中间层)

用Firebase Firestore/Realtime Database做云端同步中枢,Isar作为本地缓存,实现本地操作实时同步到云端,云端变更实时同步回本地。

代码片段:双向同步逻辑

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:isar/isar.dart';

// 监听云端变更同步到Isar
void listenCloudToIsar(Isar isar) {
  FirebaseFirestore.instance.collection('user_notes').snapshots().listen((snapshot) {
    for (var change in snapshot.docChanges) {
      final docData = change.doc.data();
      if (docData == null) continue;
      
      final note = Note.fromJson(docData);
      
      isar.writeTxnSync(() {
        switch (change.type) {
          case DocumentChangeType.added:
          case DocumentChangeType.modified:
            isar.notes.putSync(note);
            break;
          case DocumentChangeType.removed:
            isar.notes.deleteSync(note.id);
            break;
        }
      });
    }
  });
}

// 监听Isar变更同步到云端
void listenIsarToCloud(Isar isar) {
  isar.notes.watch().listen((note) {
    FirebaseFirestore.instance.collection('user_notes').doc(note.id.toString()).set(note.toJson());
  });
}

方案2:付费官方方案(Isar Sync)

Isar官方提供的多设备同步服务,集成简单,支持实时同步、冲突处理、离线队列,无需自建云端服务。

初始化示例:

final isar = await Isar.open(
  schemas: [NoteSchema],
  syncUrl: 'your-isar-sync-server-url',
  authCallback: () async => 'user-auth-token',
);

三、数据冲突处理(可选)

常用策略

  1. 最后写入获胜(LWW):最简单的策略,给每个文档加lastModified时间戳,同步时保留最新版本
  2. 版本号机制:每个文档加version字段,同步时若本地版本低于云端则覆盖,否则提示用户合并
  3. 字段级合并:针对文本等可合并字段,采用编辑合并逻辑(需业务定制)

LWW策略代码示例

在Isar模型中添加时间戳字段:

@collection
class Note {
  Id id = Isar.autoIncrement;
  String content;
  DateTime lastModified;
  
  Note({required this.content, required this.lastModified});
  
  Map<String, dynamic> toJson() => {
    'id': id,
    'content': content,
    'lastModified': lastModified.toIso8601String(),
  };
  
  factory Note.fromJson(Map<String, dynamic> json) => Note(
    content: json['content'],
    lastModified: DateTime.parse(json['lastModified']),
  );
}

同步时比较时间戳:

// 处理云端变更时
final localNote = isar.notes.getSync(cloudNote.id);
if (localNote != null && localNote.lastModified.isAfter(cloudNote.lastModified)) {
  // 本地版本更新,将本地数据推回云端
  FirebaseFirestore.instance.collection('user_notes').doc(localNote.id.toString()).set(localNote.toJson());
} else {
  isar.notes.putSync(cloudNote);
}

四、大型数据库/频繁更新性能优化(可选)

  • 批量同步:收集5-10秒内的变更,批量上传到云端,减少API调用次数
  • 增量同步:用Isar的watchLazy()监听变更,仅同步修改过的文档
  • 后台同步:用workmanager(Android)/background_fetch(iOS)在后台完成同步,不阻塞UI
  • 索引优化:在Isar和Firestore中为常用查询字段建立索引,加快同步查询速度

批量同步示例:

final List<Note> pendingChanges = [];
Timer? syncTimer;

void setupBatchSync(Isar isar) {
  isar.notes.watch().listen((note) {
    pendingChanges.add(note);
    syncTimer?.cancel();
    // 5秒后批量同步
    syncTimer = Timer(const Duration(seconds: 5), () async {
      if (pendingChanges.isEmpty) return;
      
      final batch = FirebaseFirestore.instance.batch();
      for (var note in pendingChanges) {
        batch.set(FirebaseFirestore.instance.collection('user_notes').doc(note.id.toString()), note.toJson());
      }
      
      await batch.commit();
      pendingChanges.clear();
    });
  });
}

五、离线功能支持(可选)

  • 本地优先:所有操作先写入Isar,网络恢复后再同步到云端
  • 离线队列:用Isar存储未同步的操作(新增/修改/删除),网络恢复时按顺序执行
  • 状态标记:给每个文档加syncStatus字段(pending/synced/failed),同步失败时标记以便重试

离线队列示例:

@collection
class SyncQueue {
  Id id = Isar.autoIncrement;
  String operation; // 'add'/'update'/'delete'
  Map<String, dynamic> data;
  DateTime createdAt;
  
  SyncQueue({required this.operation, required this.data, required this.createdAt});
}

// 本地添加数据时写入队列
Future<void> addNoteOffline(Isar isar, Note note) async {
  await isar.writeTxn(() async {
    await isar.notes.put(note);
    await isar.syncQueues.put(SyncQueue(
      operation: 'add',
      data: note.toJson(),
      createdAt: DateTime.now(),
    ));
  });
  await trySyncQueue(isar);
}

// 尝试同步队列
Future<void> trySyncQueue(Isar isar) async {
  if (!await _isNetworkAvailable()) return;
  
  final pendingQueues = await isar.syncQueues.where().findAll();
  if (pendingQueues.isEmpty) return;
  
  final batch = FirebaseFirestore.instance.batch();
  for (var queue in pendingQueues) {
    switch (queue.operation) {
      case 'add':
      case 'update':
        final note = Note.fromJson(queue.data);
        batch.set(FirebaseFirestore.instance.collection('user_notes').doc(note.id.toString()), note.toJson());
        break;
      case 'delete':
        final noteId = queue.data['id'] as int;
        batch.delete(FirebaseFirestore.instance.collection('user_notes').doc(noteId.toString()));
        break;
    }
  }
  
  try {
    await batch.commit();
    await isar.syncQueues.deleteAll(pendingQueues.map((q) => q.id).toList());
  } catch (e) {
    // 记录错误,后续重试
    print('Sync failed: $e');
  }
}

// 检查网络状态
Future<bool> _isNetworkAvailable() async {
  try {
    final result = await InternetAddress.lookup('google.com');
    return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
  } on SocketException catch (_) {
    return false;
  }
}

内容的提问来源于stack exchange,提问作者Virtual Tech

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 03:34:58