一、为什么需要持久化存储?
想象一下你正在开发一个天气应用。用户设置了喜欢的城市和温度单位(比如摄氏度或华氏度),如果每次重启应用这些设置都消失,用户肯定会抓狂。这就是我们需要持久化存储的原因——把数据"记住"在设备上。
Flutter提供了多种持久化方案,其中最常用的就是SharedPreferences和SQLite。它们就像两个不同大小的收纳盒:一个适合放零碎小物件,另一个适合整理大量规整物品。
二、SharedPreferences:轻量级存储好帮手
基本特点
SharedPreferences就像手机里的便利贴,适合存储简单的键值对数据。它的优势在于使用简单,适合存储用户偏好设置、登录状态等小数据。
技术栈:Flutter + shared_preferences插件
// 示例:保存和读取用户主题偏好
import 'package:shared_preferences/shared_preferences.dart';
void main() async {
// 获取SharedPreferences实例
final prefs = await SharedPreferences.getInstance();
// 保存数据(就像往便利贴上写字)
await prefs.setBool('isDarkMode', true); // 保存布尔值
await prefs.setString('username', '张伟'); // 保存字符串
await prefs.setInt('loginCount', 5); // 保存整数
// 读取数据(查看便利贴内容)
final isDark = prefs.getBool('isDarkMode') ?? false; // 读取不到时返回默认值
final name = prefs.getString('username') ?? '游客';
final count = prefs.getInt('loginCount') ?? 0;
print('欢迎$name,这是你第$count次登录,当前主题:${isDark ? "深色" : "浅色"}');
}
适用场景
- 用户设置(主题、语言偏好)
- 简单的用户数据(用户名、是否已登录)
- 应用状态标记(是否首次启动)
注意事项
- 只能存储基本数据类型(String, int, bool, double, StringList)
- 不适合存储大量数据或复杂结构
- 所有操作都是异步的,记得使用await
- Android平台下数据最终会以XML文件形式存储
三、SQLite:结构化数据专家
基本介绍
SQLite就像一个小型文件柜,适合存储需要查询、排序的规整数据。比如聊天记录、本地缓存文章等结构化数据。
技术栈:Flutter + sqflite插件
// 示例:创建本地数据库存储备忘录
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
void main() async {
// 打开数据库(如果不存在会自动创建)
final database = openDatabase(
join(await getDatabasesPath(), 'memo_database.db'),
onCreate: (db, version) {
// 创建备忘录表
return db.execute(
'CREATE TABLE memos(id INTEGER PRIMARY KEY, title TEXT, content TEXT, created_at TEXT)',
);
},
version: 1,
);
// 插入一条备忘录
Future<void> insertMemo(Memo memo) async {
final db = await database;
await db.insert(
'memos',
memo.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// 查询所有备忘录
Future<List<Memo>> getAllMemos() async {
final db = await database;
final List<Map<String, dynamic>> maps = await db.query('memos');
return List.generate(maps.length, (i) => Memo.fromMap(maps[i]));
}
// 使用示例
await insertMemo(Memo(
title: '购物清单',
content: '牛奶、鸡蛋、面包',
createdAt: DateTime.now().toString(),
));
print(await getAllMemos());
}
class Memo {
final int? id;
final String title;
final String content;
final String createdAt;
Memo({this.id, required this.title, required this.content, required this.createdAt});
Map<String, dynamic> toMap() {
return {
'id': id,
'title': title,
'content': content,
'created_at': createdAt,
};
}
factory Memo.fromMap(Map<String, dynamic> map) {
return Memo(
id: map['id'],
title: map['title'],
content: map['content'],
createdAt: map['created_at'],
);
}
}
进阶用法:事务处理
// 示例:使用事务批量插入数据
Future<void> batchInsert(List<Memo> memos) async {
final db = await database;
// 开启事务(要么全部成功,要么全部失败)
await db.transaction((txn) async {
for (var memo in memos) {
await txn.insert('memos', memo.toMap());
}
});
}
适用场景
- 需要本地缓存的结构化数据(如新闻、商品列表)
- 需要复杂查询的数据(如按时间排序的聊天记录)
- 数据量较大的场景(几百条以上)
注意事项
- 需要手动管理数据库版本升级
- 复杂查询需要编写SQL语句
- 在大数据量下要考虑性能优化
- iOS平台下单个数据库文件不能超过200MB
四、如何选择:对比与决策指南
技术对比表
| 特性 | SharedPreferences | SQLite |
|---|---|---|
| 数据类型 | 简单键值对 | 结构化表格 |
| 适合数据量 | 小(几十个键值对) | 中到大(几百条记录以上) |
| 查询能力 | 只能按键查询 | 支持复杂SQL查询 |
| 学习曲线 | 非常简单 | 需要SQL知识 |
| 性能 | 读写速度快 | 大数据量时需优化 |
| 适用场景 | 用户偏好、简单状态 | 结构化数据、复杂查询 |
决策流程图
- 数据是否简单?是 → SharedPreferences
- 需要复杂查询吗?是 → SQLite
- 数据量会很大吗?是 → SQLite
- 需要事务支持吗?是 → SQLite
- 其他情况 → SharedPreferences
混合使用案例
// 示例:同时使用两种存储方案
void saveUserData() async {
// 用户偏好用SharedPreferences
final prefs = await SharedPreferences.getInstance();
await prefs.setString('theme', 'dark');
// 用户历史记录用SQLite
final db = await openDatabase('user_db.db');
await db.insert('search_history', {
'keyword': 'Flutter存储',
'time': DateTime.now().toString()
});
}
五、常见问题与解决方案
SharedPreferences的坑
- 数据类型限制:无法直接存储对象
- 解决方案:将对象转为JSON字符串存储
// 示例:存储复杂对象
final user = {'name': '李雷', 'age': 25};
await prefs.setString('user', jsonEncode(user));
// 读取时
final userStr = prefs.getString('user');
final userObj = jsonDecode(userStr ?? '{}');
- 数据丢失问题:某些Android设备上可能被系统清理
- 解决方案:重要数据考虑备份或使用SQLite
SQLite的坑
- 数据库升级:新增字段时需要处理
- 解决方案:实现onUpgrade方法
openDatabase(
// ...其他参数
onUpgrade: (db, oldVersion, newVersion) async {
if (oldVersion < 2) {
await db.execute('ALTER TABLE memos ADD COLUMN priority INTEGER DEFAULT 0');
}
},
);
- 性能问题:大数据量查询慢
- 解决方案:添加索引、分页查询
六、总结与最佳实践
经验法则
- 能用SharedPreferences解决的,就不要用SQLite
- 当数据需要关系查询时,SQLite是唯一选择
- 考虑数据增长趋势,避免后期切换存储方案
性能优化建议
SharedPreferences:
- 批量操作使用commit()而不是await
- 避免存储过大的字符串
SQLite:
- 为常用查询字段创建索引
- 使用事务进行批量操作
- 考虑使用isolate处理大量数据
未来扩展
当应用规模扩大后,可以考虑:
- 使用Hive或ObjectBox等NoSQL解决方案
- 对于特别复杂的数据,考虑使用Firebase等云端方案
记住,没有最好的方案,只有最适合的方案。根据你的具体需求,选择最能解决问题的工具,这才是优秀开发者的标志。
评论