Flutter 本地存储与持久化

Flutter 本地存储方案全解析:SharedPreferences、SQLite、Hive、Isar、文件系统,以及数据加密与跨平台存储策略。

开篇:为什么本地存储至关重要

在移动应用开发中,本地存储承担着远比"保存设置"更广泛的职责。它是离线功能的基础、网络缓存的载体、用户会话的安全容器,甚至是端侧 AI 模型的存储仓库。Flutter 生态提供了从简单的键值对到完整的 SQL 引擎、从原生文件系统到高性能 NoSQL 数据库的丰富选择。

选择合适的存储方案需要权衡多个维度:数据结构的复杂度、读写性能要求、查询需求、数据量大小、安全性要求,以及跨平台的一致性。本章将系统性地介绍 Flutter 生态中的主流存储方案,帮助你为不同场景做出正确的选择。


一、方案选型总览

方案数据类型适用场景性能复杂度跨平台
SharedPreferences简单键值对用户设置、标志位中等
Hive对象盒子缓存、配置、小型数据集极高
IsarNoSQL 数据库大量对象、复杂查询极高
SQLite (sqflite)关系型数据结构化数据、复杂关联
Drift/Floor类型安全 SQL大型应用数据层较高
文件系统二进制/JSON文件缓存、日志、图片
MMKV高性能键值高频读写场景极高✅ (第三方)

一句话总结:键值对 → SharedPreferences/Hive,结构化对象 → Isar/Hive,关系型数据 → SQLite/Drift,大文件 → 文件系统。


二、SharedPreferences:简单键值对

2.1 基础用法

import 'package:shared_preferences/shared_preferences.dart';

class AppSettings {
  static const _keyDarkMode = 'dark_mode';
  static const _keyLanguage = 'language';
  static const _keyUserId = 'user_id';
  
  late SharedPreferences _prefs;
  
  Future<void> init() async {
    _prefs = await SharedPreferences.getInstance();
  }
  
  // Bool
  bool get isDarkMode => _prefs.getBool(_keyDarkMode) ?? false;
  Future<bool> setDarkMode(bool value) => _prefs.setBool(_keyDarkMode, value);
  
  // String
  String? get language => _prefs.getString(_keyLanguage);
  Future<bool> setLanguage(String value) => _prefs.setString(_keyLanguage, value);
  
  // Int
  int get launchCount => _prefs.getInt('launch_count') ?? 0;
  Future<bool> incrementLaunchCount() => 
      _prefs.setInt('launch_count', launchCount + 1);
  
  // String List
  List<String> get searchHistory => _prefs.getStringList('search_history') ?? [];
  Future<bool> addSearchHistory(String query) {
    final history = searchHistory;
    history.remove(query);  // 去重
    history.insert(0, query);
    if (history.length > 20) history.removeLast();  // 限制数量
    return _prefs.setStringList('search_history', history);
  }
  
  // 清除
  Future<bool> clear() => _prefs.clear();
  Future<bool> remove(String key) => _prefs.remove(key);
}

2.2 SharedPreferences 的局限

// ❌ 不支持复杂对象
// _prefs.setObject('user', user);  // 不存在!

// ✅ 手动 JSON 序列化(性能差,不推荐)
Future<void> saveUser(User user) async {
  final json = jsonEncode(user.toJson());
  await _prefs.setString('user', json);
}

// ✅ 存取完整列表(所有数据重写,O(n))
Future<void> addTodo(Todo todo) async {
  final todos = await getTodos();
  todos.add(todo);
  await _prefs.setString('todos', jsonEncode(todos.map((t) => t.toJson()).toList()));
}

一句话总结:SharedPreferences 适合少量简单类型的读写,复杂对象和大量数据请移步 Hive 或 Isar。


三、Hive:高性能轻量 NoSQL

Hive 是用纯 Dart 编写的高性能键值数据库,无需原生依赖,跨平台一致性极佳。

3.1 基础使用

import 'package:hive/hive.dart';

// 1. 初始化(仅非 Web)
void main() async {
  final appDocumentDir = await getApplicationDocumentsDirectory();
  Hive.init(appDocumentDir.path);
  
  // Web 不需要 init,直接使用
  runApp(MyApp());
}

// 2. 定义适配器(使用 hive_generator)
@HiveType(typeId: 1)
class User extends HiveObject {
  @HiveField(0)
  String id;
  
  @HiveField(1)
  String name;
  
  @HiveField(2)
  String? email;
  
  @HiveField(3)
  DateTime createdAt;
  
  User({required this.id, required this.name, this.email, required this.createdAt});
}

// 3. 打开 Box(类似集合)
class UserRepository {
  late Box<User> _userBox;
  
  Future<void> init() async {
    Hive.registerAdapter(UserAdapter());
    _userBox = await Hive.openBox<User>('users');
  }
  
  // CRUD
  Future<void> create(User user) => _userBox.put(user.id, user);
  User? read(String id) => _userBox.get(id);
  Future<void> update(User user) => _userBox.put(user.id, user);
  Future<void> delete(String id) => _userBox.delete(id);
  
  // 查询
  List<User> getAll() => _userBox.values.toList();
  List<User> searchByName(String query) => 
      _userBox.values.where((u) => u.name.contains(query)).toList();
  
  // 监听变化
  Stream<BoxEvent> watch() => _userBox.watch();
  
  Future<void> close() => _userBox.close();
}

3.2 盒子嵌套与关系

// Box 之间的关系通过 ID 引用
class Order extends HiveObject {
  @HiveField(0)
  String id;
  
  @HiveField(1)
  String userId;  // 外键引用
  
  @HiveField(2)
  List<String> productIds;  // 一对多
  
  @HiveField(3)
  DateTime createdAt;
  
  // 非持久化字段(运行时查询)
  User? get user => Hive.box<User>('users').get(userId);
}

一句话总结:Hive 以零原生依赖、极高性能和简洁的 API 成为 Flutter 本地存储的首选方案,尤其适合缓存和中型数据集。


四、Isar:下一代超高速数据库

Isar 是从零为 Flutter 设计的数据库,查询性能远超 Hive,支持索引、复合查询、全文搜索和 ACID 事务。

4.1 项目配置与定义

# pubspec.yaml
dependencies:
  isar: ^3.1.0
  isar_flutter_libs: ^3.1.0

dev_dependencies:
  isar_generator: ^3.1.0
  build_runner: ^2.4.0
import 'package:isar/isar.dart';

part 'models.g.dart';  // 由 build_runner 生成

@collection
class Product {
  Id id = Isar.autoIncrement;
  
  @Index(type: IndexType.value)
  String name;
  
  @Indexed()
  String category;
  
  double price;
  
  @Index(composite: [CompositeIndex('category')])
  int stock;
  
  List<String> tags;
  
  DateTime createdAt;
  
  Product({
    required this.name,
    required this.category,
    required this.price,
    required this.stock,
    required this.tags,
    required this.createdAt,
  });
}

4.2 CRUD 与高级查询

class ProductRepository {
  late Isar _isar;
  
  Future<void> init() async {
    final dir = await getApplicationDocumentsDirectory();
    _isar = await Isar.open(
      [ProductSchema],
      directory: dir.path,
    );
  }
  
  // 创建
  Future<void> create(Product product) async {
    await _isar.writeTxn(() async {
      await _isar.products.put(product);
    });
  }
  
  // 批量插入(事务)
  Future<void> createMany(List<Product> products) async {
    await _isar.writeTxn(() async {
      await _isar.products.putAll(products);
    });
  }
  
  // 读取
  Future<Product?> getById(int id) async =>
      await _isar.products.get(id);
  
  // 条件查询
  Future<List<Product>> search({
    String? category,
    double? minPrice,
    double? maxPrice,
    String? nameQuery,
    int? minStock,
  }) async {
    return await _isar.products
        .filter()
        .optional(category != null, (q) => q.categoryEqualTo(category!))
        .optional(minPrice != null, (q) => q.priceGreaterThan(minPrice!))
        .optional(maxPrice != null, (q) => q.priceLessThan(maxPrice!))
        .optional(nameQuery != null, (q) => q.nameContains(nameQuery!, caseSensitive: false))
        .optional(minStock != null, (q) => q.stockGreaterThan(minStock!))
        .sortByPriceDesc()
        .limit(50)
        .findAll();
  }
  
  // 聚合查询
  Future<double> getAveragePrice(String category) async {
    return await _isar.products
        .filter()
        .categoryEqualTo(category)
        .priceProperty()
        .avg() ?? 0;
  }
  
  // 监听变化
  Stream<void> watchAll() => _isar.products.watchLazy();
  Stream<Product?> watchById(int id) => _isar.products.watchObject(id);
}

一句话总结:Isar 通过类型安全的查询构建器、索引优化和事务支持,将 Flutter 本地数据库的性能和开发体验推向了新的高度。


五、SQLite:经典关系型方案

5.1 原生 sqflite 包

import 'package:sqflite/sqflite.dart';

class DatabaseHelper {
  static Database? _database;
  static const _databaseName = 'app_database.db';
  static const _databaseVersion = 1;
  
  Future<Database> get database async {
    _database ??= await _initDatabase();
    return _database!;
  }
  
  Future<Database> _initDatabase() async {
    final path = join(await getDatabasesPath(), _databaseName);
    return await openDatabase(
      path,
      version: _databaseVersion,
      onCreate: (db, version) async {
        await db.execute('''
          CREATE TABLE users(
            id TEXT PRIMARY KEY,
            name TEXT NOT NULL,
            email TEXT,
            created_at INTEGER
          )
        ''');
        await db.execute('''
          CREATE TABLE orders(
            id TEXT PRIMARY KEY,
            user_id TEXT NOT NULL,
            total REAL NOT NULL,
            status TEXT NOT NULL,
            created_at INTEGER,
            FOREIGN KEY (user_id) REFERENCES users(id)
          )
        ''');
      },
      onUpgrade: (db, oldVersion, newVersion) async {
        if (oldVersion < 2) {
          await db.execute('ALTER TABLE users ADD COLUMN avatar_url TEXT');
        }
      },
    );
  }
  
  // CRUD
  Future<void> insertUser(Map<String, dynamic> user) async {
    final db = await database;
    await db.insert('users', user, conflictAlgorithm: ConflictAlgorithm.replace);
  }
  
  Future<List<Map<String, dynamic>>> getUsers() async {
    final db = await database;
    return await db.query('users', orderBy: 'created_at DESC');
  }
  
  Future<List<Map<String, dynamic>>> searchUsers(String query) async {
    final db = await database;
    return await db.query(
      'users',
      where: 'name LIKE ?',
      whereArgs: ['%$query%'],
    );
  }
  
  // 关联查询(JOIN)
  Future<List<Map<String, dynamic>>> getUserOrders(String userId) async {
    final db = await database;
    return await db.rawQuery('''
      SELECT o.*, u.name as user_name
      FROM orders o
      INNER JOIN users u ON o.user_id = u.id
      WHERE o.user_id = ?
      ORDER BY o.created_at DESC
    ''', [userId]);
  }
  
  // 事务
  Future<void> createOrderWithItems(
    Map<String, dynamic> order,
    List<Map<String, dynamic>> items,
  ) async {
    final db = await database;
    await db.transaction((txn) async {
      await txn.insert('orders', order);
      for (final item in items) {
        await txn.insert('order_items', item);
      }
      // 任一步失败自动回滚
    });
  }
}

一句话总结:sqflite 提供了完整的 SQL 能力(JOIN、事务、聚合),但类型安全和样板代码是痛点,Drift 和 Floor 提供了更现代的封装。


六、文件系统存储

import 'path_provider/path_provider.dart';
import 'dart:io';

class FileStorageService {
  // 文档目录 - 用户文件(会备份到 iCloud/Google Drive)
  Future<Directory> get documentsDir => getApplicationDocumentsDirectory();
  
  // 临时目录 - 缓存文件(系统可能随时清理)
  Future<Directory> get tempDir => getTemporaryDirectory();
  
  // 应用支持目录 - 应用数据(不备份)
  Future<Directory> get supportDir => getApplicationSupportDirectory();
  
  // 外部存储(Android)
  Future<Directory?> get externalDir => getExternalStorageDirectory();
  
  // 缓存图片
  Future<File> cacheImage(String url, Uint8List bytes) async {
    final dir = await supportDir;
    final fileName = md5.convert(utf8.encode(url)).toString();
    final file = File('${dir.path}/images/$fileName');
    await file.create(recursive: true);
    await file.writeAsBytes(bytes);
    return file;
  }
  
  // 读写 JSON 配置
  Future<Map<String, dynamic>> readConfig() async {
    final dir = await supportDir;
    final file = File('${dir.path}/config.json');
    if (!await file.exists()) return {};
    final content = await file.readAsString();
    return jsonDecode(content);
  }
  
  Future<void> writeConfig(Map<String, dynamic> config) async {
    final dir = await supportDir;
    final file = File('${dir.path}/config.json');
    await file.writeAsString(jsonEncode(config));
  }
  
  // 日志文件(按日期轮转)
  Future<void> appendLog(String message) async {
    final dir = await supportDir;
    final date = DateTime.now().toIso8601String().split('T').first;
    final file = File('${dir.path}/logs/$date.log');
    await file.create(recursive: true);
    final line = '[${DateTime.now().toIso8601String()}] $message\n';
    await file.writeAsString(line, mode: FileMode.append);
  }
  
  // 清理过期缓存
  Future<void> cleanCache({Duration maxAge = const Duration(days: 7)}) async {
    final dir = await tempDir;
    final now = DateTime.now();
    await for (final entity in dir.list(recursive: true)) {
      if (entity is File) {
        final stat = await entity.stat();
        if (now.difference(stat.modified) > maxAge) {
          await entity.delete();
        }
      }
    }
  }
}

一句话总结:不同目录有不同用途和备份策略,正确选择目录是文件存储设计的第一步。


七、数据加密与安全存储

import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:encrypt/encrypt.dart' as encrypt;

class SecureStorageService {
  final _secureStorage = const FlutterSecureStorage(
    aOptions: AndroidOptions(
      encryptedSharedPreferences: true,
    ),
    iOptions: IOSOptions(
      accessibility: KeychainAccessibility.first_unlock,
    ),
  );
  
  // Token 管理
  Future<void> saveToken(String token) async {
    await _secureStorage.write(key: 'auth_token', value: token);
  }
  
  Future<String?> getToken() async {
    return await _secureStorage.read(key: 'auth_token');
  }
  
  Future<void> deleteToken() async {
    await _secureStorage.delete(key: 'auth_token');
  }
  
  // 保存密钥(用于加密本地数据库)
  Future<void> saveEncryptionKey(String key) async {
    await _secureStorage.write(key: 'db_encryption_key', value: key);
  }
  
  Future<String?> getEncryptionKey() async {
    return await _secureStorage.read(key: 'db_encryption_key');
  }
}

// 数据库加密(Hive + 自定义加密)
Future<void> initEncryptedHive() async {
  final secureStorage = SecureStorageService();
  var key = await secureStorage.getEncryptionKey();
  
  if (key == null) {
    final newKey = Hive.generateSecureKey();
    key = base64UrlEncode(newKey);
    await secureStorage.saveEncryptionKey(key);
  }
  
  final encryptionKey = base64Url.decode(key);
  final box = await Hive.openBox('secrets',
    encryptionCipher: HiveAesCipher(encryptionKey),
  );
}

一句话总结:敏感数据(Token、密钥、PII)必须使用 Keychain/Keystore 级别的安全存储,绝对不能明文保存在 SharedPreferences 或普通文件中。


FAQ

Q1: Hive 和 Isar 怎么选?

  • 数据量 < 10K,查询简单 → Hive(更简单、启动更快)
  • 数据量 > 10K,需要复杂查询/索引/事务 → Isar(性能更强)
  • Isar 是 Hive 的精神继承者,长期项目优先 Isar

Q2: 本地存储数据会丢失吗?

会。用户卸载应用或清除数据时,本地存储全部丢失。关键数据必须同步到服务端,本地仅作为缓存。例外:getApplicationDocumentsDirectory() 在 iOS 中如果设置 isExcludedFromBackup = false 会被 iCloud 备份。

Q3: sqflite 的并发访问安全吗?

sqflite 内部使用队列保证单连接串行访问,直接调用是线程安全的。但不要试图在多个 Isolate 中同时打开同一个数据库文件。

Q4: 存储数据量有没有限制?

  • SharedPreferences:Android 10KB 以内较安全(再大性能急剧下降)
  • Hive/Isar:理论上只受磁盘空间限制,百万级数据仍可流畅查询
  • SQLite:单个文件最大 281TB

Q5: Web 端本地存储方案?

  • Hive(支持 IndexedDB 后端)
  • SharedPreferences(使用 localStorage)
  • 文件系统不可用,需使用内存缓存或服务端存储

Q6: 如何检测存储空间不足?

import 'package:disk_space_plus/disk_space_plus.dart';

final freeSpace = await DiskSpacePlus.getFreeDiskSpace;  // MB
if (freeSpace != null && freeSpace < 100) {
  // 警告用户空间不足
}

Q7: 数据迁移策略?

Future<void> migrateData() async {
  final currentVersion = _prefs.getInt('db_version') ?? 0;
  
  if (currentVersion < 2) {
    // 执行 v1 → v2 迁移
    await _migrateV1ToV2();
  }
  if (currentVersion < 3) {
    // 执行 v2 → v3 迁移
    await _migrateV2ToV3();
  }
  
  await _prefs.setInt('db_version', 3);
}

相关阅读

  • https://plumephp.com/flutter-async-networking/ — 网络通信与离线优先策略
  • https://plumephp.com/flutter-performance-optimization/ — 性能优化(含存储性能调优)

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「Flutter」更多文章

  1. Widget 体系与布局系统
  2. Flutter 状态管理全解析
  3. Flutter 测试策略与自动化