一、为什么需要持久化存储?

想象一下你正在开发一个天气应用。用户设置了喜欢的城市和温度单位(比如摄氏度或华氏度),如果每次重启应用这些设置都消失,用户肯定会抓狂。这就是我们需要持久化存储的原因——把数据"记住"在设备上。

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 ? "深色" : "浅色"}');
}

适用场景

  • 用户设置(主题、语言偏好)
  • 简单的用户数据(用户名、是否已登录)
  • 应用状态标记(是否首次启动)

注意事项

  1. 只能存储基本数据类型(String, int, bool, double, StringList)
  2. 不适合存储大量数据或复杂结构
  3. 所有操作都是异步的,记得使用await
  4. 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());
    }
  });
}

适用场景

  • 需要本地缓存的结构化数据(如新闻、商品列表)
  • 需要复杂查询的数据(如按时间排序的聊天记录)
  • 数据量较大的场景(几百条以上)

注意事项

  1. 需要手动管理数据库版本升级
  2. 复杂查询需要编写SQL语句
  3. 在大数据量下要考虑性能优化
  4. iOS平台下单个数据库文件不能超过200MB

四、如何选择:对比与决策指南

技术对比表

特性 SharedPreferences SQLite
数据类型 简单键值对 结构化表格
适合数据量 小(几十个键值对) 中到大(几百条记录以上)
查询能力 只能按键查询 支持复杂SQL查询
学习曲线 非常简单 需要SQL知识
性能 读写速度快 大数据量时需优化
适用场景 用户偏好、简单状态 结构化数据、复杂查询

决策流程图

  1. 数据是否简单?是 → SharedPreferences
  2. 需要复杂查询吗?是 → SQLite
  3. 数据量会很大吗?是 → SQLite
  4. 需要事务支持吗?是 → SQLite
  5. 其他情况 → 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的坑

  1. 数据类型限制:无法直接存储对象
    • 解决方案:将对象转为JSON字符串存储
// 示例:存储复杂对象
final user = {'name': '李雷', 'age': 25};
await prefs.setString('user', jsonEncode(user));

// 读取时
final userStr = prefs.getString('user');
final userObj = jsonDecode(userStr ?? '{}');
  1. 数据丢失问题:某些Android设备上可能被系统清理
    • 解决方案:重要数据考虑备份或使用SQLite

SQLite的坑

  1. 数据库升级:新增字段时需要处理
    • 解决方案:实现onUpgrade方法
openDatabase(
  // ...其他参数
  onUpgrade: (db, oldVersion, newVersion) async {
    if (oldVersion < 2) {
      await db.execute('ALTER TABLE memos ADD COLUMN priority INTEGER DEFAULT 0');
    }
  },
);
  1. 性能问题:大数据量查询慢
    • 解决方案:添加索引、分页查询

六、总结与最佳实践

经验法则

  1. 能用SharedPreferences解决的,就不要用SQLite
  2. 当数据需要关系查询时,SQLite是唯一选择
  3. 考虑数据增长趋势,避免后期切换存储方案

性能优化建议

  1. SharedPreferences:

    • 批量操作使用commit()而不是await
    • 避免存储过大的字符串
  2. SQLite:

    • 为常用查询字段创建索引
    • 使用事务进行批量操作
    • 考虑使用isolate处理大量数据

未来扩展

当应用规模扩大后,可以考虑:

  1. 使用Hive或ObjectBox等NoSQL解决方案
  2. 对于特别复杂的数据,考虑使用Firebase等云端方案

记住,没有最好的方案,只有最适合的方案。根据你的具体需求,选择最能解决问题的工具,这才是优秀开发者的标志。