本地存储
为什么要使用本地存储
移动应用在运行过程中经常需要持久化数据,本地存储是解决这一需求的核心手段。使用本地存储的主要理由:
- 离线可用:网络不可用时,应用仍能读取本地缓存的数据,保证基本功能可用
- 提升性能:将频繁访问的数据缓存到本地,避免重复的网络请求,减少加载时间
- 保存用户偏好:主题、语言、字体大小等个性化设置需要在应用重启后保留
- 减少流量消耗:已获取的数据本地缓存后,不必每次都从服务器拉取
- 提升用户体验:启动时立即展示本地缓存内容(骨架屏/占位数据),再异步刷新,避免白屏等待
常见使用场景
| 场景 | 说明 | 推荐方案 |
|---|---|---|
| 用户设置 | 主题色、语言、字体大小、通知开关等 | shared_preferences / get_storage |
| 登录状态 | Token、是否首次启动、是否同意协议等 | shared_preferences / get_storage |
| 表单草稿 | 用户未提交的表单数据暂存,防止丢失 | get_storage / hive |
| 列表缓存 | 首页列表数据缓存,实现离线浏览 | hive / sqflite |
| 聊天记录 | 消息的本地持久化与历史查询 | sqflite / isar |
| 购物车 | 商品增删改查、事务一致性 | sqflite |
| 收藏/点赞 | 离线操作队列,网络恢复后同步 | sqflite / isar |
| 搜索历史 | 最近搜索关键词记录 | shared_preferences / get_storage |
| 文件缓存 | 下载的图片、PDF 等文件资源 | 文件读写(path_provider) |
本地存储方案对比
| 方案 | 适合存储 | 性能 | 备注 |
|---|---|---|---|
shared_preferences | 键值对、用户设置 | 好 | 官方维护,最通用 |
get_storage | 键值对、简单配置 | 优秀 | 同步 API,最简单 |
sqflite | 关系型数据、大量数据 | 好 | 中文社区最常用的 SQLite 方案 |
isar | NoSQL 数据库 | 优秀 | 维护状态不稳定,谨慎选用 |
hive | 结构化对象 | 优秀 | 原生 Dart,作者已转向 Isar |
path_provider + 文件读写 | JSON、自定义文件 | 好 | 最灵活,需手动管理 |
SharedPreferences(键值对存储)
适合存储简单配置、用户偏好等。
安装
bash
flutter pub add shared_preferences基本用法
dart
import 'package:shared_preferences/shared_preferences.dart';
// 写入
Future<void> saveSettings() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', 'Tom');
await prefs.setInt('age', 25);
await prefs.setBool('isDarkMode', true);
await prefs.setDouble('fontSize', 16.0);
await prefs.setStringList('tags', ['flutter', 'dart']);
}
// 读取
Future<void> loadSettings() async {
final prefs = await SharedPreferences.getInstance();
final username = prefs.getString('username') ?? 'Guest';
final age = prefs.getInt('age') ?? 0;
final isDarkMode = prefs.getBool('isDarkMode') ?? false;
final fontSize = prefs.getDouble('fontSize') ?? 14.0;
final tags = prefs.getStringList('tags') ?? [];
}
// 删除
await prefs.remove('username'); // 删除单个
await prefs.clear(); // 清除所有GetStorage(最简单的键值对存储)
来自 GetX 生态,中文社区使用广泛。相比 shared_preferences,API 更简洁,读写同步无需 await。
安装
bash
flutter pub add get_storage基本用法
dart
import 'package:get_storage/get_storage.dart';
// 初始化(main 中调用一次)
await GetStorage.init();
final box = GetStorage();
// 写入(同步,无需 await)
box.write('username', 'Tom');
box.write('age', 25);
box.write('isDarkMode', true);
// 读取(同步)
final username = box.read('username') ?? 'Guest';
final age = box.read<int>('age') ?? 0;
// 删除
box.remove('username');
box.erase(); // 清除所有监听变化
dart
box.listen(() {
print('数据已变化');
});
box.listenKey('username', (value) {
print('username 变为: $value');
});适用场景:简单配置、用户偏好、小型缓存。不适合大量数据或复杂查询。
sqflite(关系型数据库)
中文社区最常用的 SQLite 方案,适合存储关系型数据和大量结构化数据,支持完整的 SQL 语法。
安装
bash
flutter pub add sqflite
flutter pub add path # 辅助拼接数据库路径基本用法
1. 打开数据库
dart
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
Future<Database> openMyDatabase() async {
final dbPath = await getDatabasesPath();
return openDatabase(
join(dbPath, 'app.db'),
version: 1,
onCreate: (db, version) async {
// 首次创建时执行建表
await db.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER,
email TEXT
)
''');
await db.execute('''
CREATE TABLE orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
userId INTEGER,
product TEXT,
price REAL,
createdAt TEXT,
FOREIGN KEY (userId) REFERENCES users(id)
)
''');
},
onUpgrade: (db, oldVersion, newVersion) async {
// 版本升级时执行迁移
if (oldVersion < 2) {
await db.execute('ALTER TABLE users ADD COLUMN phone TEXT');
}
},
);
}2. 插入数据
dart
final db = await openMyDatabase();
// 插入单条
int id = await db.insert('users', {
'name': 'Tom',
'age': 25,
'email': 'tom@example.com',
});
// 原始 SQL 插入
await db.rawInsert(
'INSERT INTO users(name, age, email) VALUES(?, ?, ?)',
['Jerry', 30, 'jerry@example.com'],
);
// 批量插入(事务)
await db.transaction((txn) async {
await txn.insert('users', {'name': 'Alice', 'age': 22});
await txn.insert('users', {'name': 'Bob', 'age': 28});
});3. 查询数据
dart
// 查询所有
List<Map<String, dynamic>> users = await db.query('users');
// 条件查询
List<Map<String, dynamic>> results = await db.query(
'users',
columns: ['id', 'name', 'age'],
where: 'age > ?',
whereArgs: [20],
orderBy: 'age DESC',
limit: 10,
);
// 原始 SQL 查询
List<Map<String, dynamic>> results = await db.rawQuery(
'SELECT * FROM users WHERE age > ? ORDER BY age DESC LIMIT ?',
[20, 10],
);
// 联表查询
List<Map<String, dynamic>> results = await db.rawQuery('''
SELECT u.name, o.product, o.price
FROM users u
INNER JOIN orders o ON u.id = o.userId
WHERE o.price > ?
ORDER BY o.price DESC
''', [100.0]);4. 更新数据
dart
// 更新
int count = await db.update(
'users',
{'age': 26, 'email': 'tom_new@example.com'},
where: 'name = ?',
whereArgs: ['Tom'],
);
// 原始 SQL 更新
int count = await db.rawUpdate(
'UPDATE users SET age = ? WHERE name = ?',
[26, 'Tom'],
);5. 删除数据
dart
// 删除
int count = await db.delete(
'users',
where: 'age < ?',
whereArgs: [18],
);
// 原始 SQL 删除
int count = await db.rawDelete(
'DELETE FROM users WHERE age < ?',
[18],
);使用事务
dart
await db.transaction((txn) async {
// 所有操作在同一个事务中,任一失败则全部回滚
await txn.insert('orders', {
'userId': 1,
'product': 'Flutter Book',
'price': 59.9,
'createdAt': DateTime.now().toIso8601String(),
});
await txn.update(
'users',
{'age': 26},
where: 'id = ?',
whereArgs: [1],
);
});封装数据库帮助类
dart
class DatabaseHelper {
static Database? _database;
static Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDatabase();
return _database!;
}
static Future<Database> _initDatabase() async {
final dbPath = await getDatabasesPath();
return openDatabase(
join(dbPath, 'app.db'),
version: 1,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER,
email TEXT
)
''');
},
);
}
// CRUD 封装示例
static Future<int> insertUser(Map<String, dynamic> user) async {
final db = await database;
return db.insert('users', user);
}
static Future<List<Map<String, dynamic>>> getUsers({int? minAge}) async {
final db = await database;
if (minAge != null) {
return db.query('users', where: 'age > ?', whereArgs: [minAge]);
}
return db.query('users');
}
static Future<int> updateUser(int id, Map<String, dynamic> values) async {
final db = await database;
return db.update('users', values, where: 'id = ?', whereArgs: [id]);
}
static Future<int> deleteUser(int id) async {
final db = await database;
return db.delete('users', where: 'id = ?', whereArgs: [id]);
}
}删除数据库
dart
final dbPath = await getDatabasesPath();
await deleteDatabase(join(dbPath, 'app.db'));适用场景:需要复杂查询、多表关联、事务支持的场景,如购物车、订单管理、消息记录等。
Isar(NoSQL 数据库)
注意:Isar 维护状态不稳定,生产项目请谨慎选用。如需稳定方案,优先考虑
sqflite。
高性能 NoSQL 数据库,支持全文搜索、索引、查询等,API 设计现代。
安装
bash
flutter pub add isar
flutter pub add isar_flutter_libs
flutter pub add build_runner
flutter pub add isar_generator基本用法
dart
import 'package:isar/isar.dart';
// 1. 定义模型
@collection
class User {
Id id = Isar.autoIncrement;
String name;
int age;
User({required this.name, required this.age});
}
// 2. 打开数据库
final isar = await Isar.open(
[UserSchema],
directory: (await getApplicationDocumentsDirectory()).path,
);
// 3. 写入
await isar.writeTxn(() async {
await isar.users.put(User(name: 'Tom', age: 25));
});
// 4. 查询
final users = await isar.users.where().findAll();
final adult = await isar.users.filter().ageGreaterThan(18).findAll();
// 5. 删除
await isar.writeTxn(() async {
await isar.users.delete(1);
});适用场景:需要高性能 NoSQL 存储、全文搜索的场景。因维护状态不稳定,建议评估风险后使用。
Hive(高性能键值存储)
注意:Hive 原作者已转向 Isar 开发,Hive 维护频率降低。新项目如需轻量键值存储优先考虑
get_storage,需复杂 NoSQL 优先考虑isar(但需注意其维护状态)。仅在已有 Hive 项目或特殊需求时选用。
安装
bash
flutter pub add hive
flutter pub add hive_flutter基本用法
dart
import 'package:hive_flutter/hive_flutter.dart';
// 初始化
await Hive.initFlutter();
// 打开盒子
var box = await Hive.openBox('settings');
// 写入
box.put('username', 'Tom');
box.put('age', 25);
box.putAll({'key1': 'value1', 'key2': 'value2'});
// 读取
var username = box.get('username', defaultValue: 'Guest');
// 删除
box.delete('username');
await box.close();Hive 对象存储
dart
// 1. 定义模型(加注解)
@HiveType(typeId: 0)
class User extends HiveObject {
@HiveField(0)
String name;
@HiveField(1)
int age;
User({required this.name, required this.age});
}
// 2. 生成适配器(运行命令)
// flutter pub run build_runner build
// 3. 注册适配器
Hive.registerAdapter(UserAdapter());
// 4. 使用
var box = await Hive.openBox<User>('users');
box.put('tom', User(name: 'Tom', age: 25));
var user = box.get('tom');文件读写
dart
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'dart:convert';
// 获取应用文档目录
final directory = await getApplicationDocumentsDirectory();
// 写入
final file = File('${directory.path}/data.json');
final jsonString = jsonEncode({'key': 'value'});
await file.writeAsString(jsonString);
// 读取
if (await file.exists()) {
final content = await file.readAsString();
}