3、SharedPreferences基础

好,咱们今天来聊聊 SharedPreferences。说实话,这是 Flutter 本地存储里最基础、最常用的一个东西。你想想看,很多时候我们只需要存点简单的配置信息——比如用户登录状态、主题颜色、音量大小——根本用不上数据库那种重型武器。这时候,SharedPreferences 就是你的最佳选择。

什么是 SharedPreferences?

简单来说,SharedPreferences 就是一个轻量级的键值对存储方案。它把数据以 XML 文件的形式存在设备本地。每个键对应一个值,就像你手机里的通讯录——名字是键,电话号码是值。

我个人习惯把它叫做「小纸条存储」。为什么?因为它只能存一些简单的数据类型:字符串、整数、布尔值、浮点数、还有字符串列表。你想存个复杂对象?不好意思,得先序列化成 JSON 字符串才行。

核心特点:

  • 基于键值对(Key-Value)
  • 数据存储在应用沙盒内
  • 应用卸载时数据自动清除
  • 读写速度极快(毫秒级)

适用场景与限制

我在项目中遇到过不少同学,一上来就想用 SharedPreferences 存所有东西。嗯,这里要注意——它可不是万能的。

适用场景 不适用场景
用户偏好设置(主题、语言) 大量结构化数据(如用户列表)
登录状态标记 需要复杂查询的数据
缓存简单的配置信息 敏感信息(密码、Token)
应用首次启动标记 超过 100KB 的数据

说白了,SharedPreferences 适合存「配置」,不适合存「数据」。你想想看,谁会拿小纸条去记一本小说呢?

避坑指南:我曾经在一个项目里用 SharedPreferences 存了上千条商品缓存数据,结果应用启动时卡了足足 3 秒。后来才意识到,这玩意儿加载时会把整个文件读进内存。数据量一大,性能就崩了。

基本读写操作

好,咱们直接上代码。Flutter 里使用 SharedPreferences 需要先引入包:

dependencies:
  shared_preferences: ^2.2.0

然后导入:

import 'package:shared_preferences/shared_preferences.dart';

写操作(set 方法)

写数据很简单,调用对应的 set 方法就行:

// 获取实例
final prefs = await SharedPreferences.getInstance();

// 存储不同类型的数据
await prefs.setString('username', '张三');
await prefs.setInt('age', 25);
await prefs.setBool('isLoggedIn', true);
await prefs.setDouble('volume', 0.8);
await prefs.setStringList('tags', ['flutter', 'dart']);

注意看,每个 set 方法前面都有 await。为什么?因为 SharedPreferences 的写操作是异步的。我刚开始学的时候老忘记加 await,结果数据死活存不进去——嗯,这个坑我替你们踩过了。

读操作(get 方法)

读数据同样简单:

final prefs = await SharedPreferences.getInstance();

String? username = prefs.getString('username');
int? age = prefs.getInt('age');
bool? isLoggedIn = prefs.getBool('isLoggedIn');
double? volume = prefs.getDouble('volume');
List<String>? tags = prefs.getStringList('tags');

看到没?返回值都是可空类型(带问号)。这是因为如果键不存在,get 方法会返回 null。我个人习惯在读取后加个默认值:

String username = prefs.getString('username') ?? '默认用户';

小技巧:你可以用 prefs.containsKey('key') 先检查键是否存在,避免频繁使用空合并运算符。

同步与异步方法

这里有个重要的点要讲清楚。SharedPreferences 提供了两种调用方式:

方法类型 示例 说明
异步方法 await prefs.setString() 推荐使用,不会阻塞 UI
同步方法 prefs.getString()(读取时) 读取操作实际上是同步的

你可能会问:「为什么读取是同步的,写入却是异步的?」

原因很简单。SharedPreferences 在初始化时会把整个文件加载到内存缓存里。所以读取操作直接从内存拿数据,快得很,不需要异步。但写入操作需要把数据写回磁盘文件,这个 I/O 操作是耗时的,所以设计成了异步。

我记得有一次在面试里被问到这个问题,当时没答上来。后来翻源码才搞明白——其实 getString() 内部就是直接返回缓存里的值,根本没有异步操作。

注意:虽然读取是同步的,但获取实例 SharedPreferences.getInstance() 是异步的。你必须在获取实例后才能读写数据。

完整示例

最后,给你一个完整的实战代码。假设我们要做一个简单的主题切换功能:

class ThemePreferences {
  static const String _keyTheme = 'theme_mode';

  // 保存主题模式
  static Future<void> saveThemeMode(String mode) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString(_keyTheme, mode);
  }

  // 读取主题模式
  static Future<String> getThemeMode() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getString(_keyTheme) ?? 'light';
  }

  // 删除主题设置
  static Future<void> clearThemeMode() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.remove(_keyTheme);
  }
}

使用起来也很直观:

// 保存
await ThemePreferences.saveThemeMode('dark');

// 读取
String mode = await ThemePreferences.getThemeMode();

// 删除
await ThemePreferences.clearThemeMode();

好了,SharedPreferences 的基础内容就这些。说白了,它就是个小而美的工具——适合存配置,别拿来存大数据。下一节我们会聊到如何用它实现更复杂的场景,比如用户登录状态的持久化。到时候再细聊。