开篇:为什么本地存储至关重要
在移动应用开发中,本地存储承担着远比"保存设置"更广泛的职责。它是离线功能的基础、网络缓存的载体、用户会话的安全容器,甚至是端侧 AI 模型的存储仓库。Flutter 生态提供了从简单的键值对到完整的 SQL 引擎、从原生文件系统到高性能 NoSQL 数据库的丰富选择。
选择合适的存储方案需要权衡多个维度:数据结构的复杂度、读写性能要求、查询需求、数据量大小、安全性要求,以及跨平台的一致性。本章将系统性地介绍 Flutter 生态中的主流存储方案,帮助你为不同场景做出正确的选择。
一、方案选型总览
| 方案 | 数据类型 | 适用场景 | 性能 | 复杂度 | 跨平台 |
|---|---|---|---|---|---|
| SharedPreferences | 简单键值对 | 用户设置、标志位 | 中等 | 低 | ✅ |
| Hive | 对象盒子 | 缓存、配置、小型数据集 | 极高 | 低 | ✅ |
| Isar | NoSQL 数据库 | 大量对象、复杂查询 | 极高 | 中 | ✅ |
| 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/ — 性能优化(含存储性能调优)
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。