简介
SharedPreferences 是一个轻量级的数据持久化方案,适合存储少量的键值对数据,如用户设置、缓存等。
基本配置
添加依赖
dependencies:
shared_preferences: ^2.2.0
基本使用
// 获取实例
final prefs = await SharedPreferences.getInstance();
// 存储数据
await prefs.setString('name', 'John');
await prefs.setInt('age', 25);
await prefs.setBool('isLogin', true);
await prefs.setStringList('tags', ['flutter', 'dart']);
// 读取数据
final name = prefs.getString('name') ?? 'Unknown';
final age = prefs.getInt('age') ?? 0;
final isLogin = prefs.getBool('isLogin') ?? false;
final tags = prefs.getStringList('tags') ?? [];
// 删除数据
await prefs.remove('name');
// 清空所有数据
await prefs.clear();
封装工具类
class PreferencesUtil {
static SharedPreferences? _prefs;
static Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
// 存储方法
static Future<bool> setString(String key, String value) async {
return await _prefs?.setString(key, value) ?? false;
}
static Future<bool> setInt(String key, int value) async {
return await _prefs?.setInt(key, value) ?? false;
}
static Future<bool> setBool(String key, bool value) async {
return await _prefs?.setBool(key, value) ?? false;
}
static Future<bool> setStringList(String key, List<String> value) async {
return await _prefs?.setStringList(key, value) ?? false;
}
// 读取方法
static String getString(String key, {String defaultValue = ''}) {
return _prefs?.getString(key) ?? defaultValue;
}
static int getInt(String key, {int defaultValue = 0}) {
return _prefs?.getInt(key) ?? defaultValue;
}
static bool getBool(String key, {bool defaultValue = false}) {
return _prefs?.getBool(key) ?? defaultValue;
}
static List<String> getStringList(String key, {List<String> defaultValue = const []}) {
return _prefs?.getStringList(key) ?? defaultValue;
}
// 删除方法
static Future<bool> remove(String key) async {
return await _prefs?.remove(key) ?? false;
}
static Future<bool> clear() async {
return await _prefs?.clear() ?? false;
}
// 检查是否存在
static bool containsKey(String key) {
return _prefs?.containsKey(key) ?? false;
}
}
完整示例
// 用户设置管理
class UserSettings {
static const String keyThemeMode = 'theme_mode';
static const String keyLanguage = 'language';
static const String keyFontSize = 'font_size';
static const String keyNotifications = 'notifications';
// 主题设置
static Future<void> setThemeMode(String mode) async {
await PreferencesUtil.setString(keyThemeMode, mode);
}
static String getThemeMode() {
return PreferencesUtil.getString(keyThemeMode, defaultValue: 'system');
}
// 语言设置
static Future<void> setLanguage(String language) async {
await PreferencesUtil.setString(keyLanguage, language);
}
static String getLanguage() {
return PreferencesUtil.getString(keyLanguage, defaultValue: 'en');
}
// 字体大小
static Future<void> setFontSize(double size) async {
await PreferencesUtil.setString(keyFontSize, size.toString());
}
static double getFontSize() {
final size = PreferencesUtil.getString(keyFontSize, defaultValue: '14.0');
return double.tryParse(size) ?? 14.0;
}
// 通知设置
static Future<void> setNotificationsEnabled(bool enabled) async {
await PreferencesUtil.setBool(keyNotifications, enabled);
}
static bool getNotificationsEnabled() {
return PreferencesUtil.getBool(keyNotifications, defaultValue: true);
}
}
// 设置页面
class SettingsPage extends StatefulWidget {
@override
_SettingsPageState createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
String _themeMode = UserSettings.getThemeMode();
String _language = UserSettings.getLanguage();
double _fontSize = UserSettings.getFontSize();
bool _notificationsEnabled = UserSettings.getNotificationsEnabled();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Settings')),
body: ListView(
children: [
ListTile(
title: Text('Theme Mode'),
subtitle: Text(_themeMode),
onTap: _showThemePicker,
),
ListTile(
title: Text('Language'),
subtitle: Text(_language),
onTap: _showLanguagePicker,
),
ListTile(
title: Text('Font Size'),
subtitle: Text('${_fontSize.toStringAsFixed(1)}'),
onTap: _showFontSizePicker,
),
SwitchListTile(
title: Text('Notifications'),
value: _notificationsEnabled,
onChanged: (value) {
setState(() {
_notificationsEnabled = value;
UserSettings.setNotificationsEnabled(value);
});
},
),
],
),
);
}
void _showThemePicker() async {
final result = await showDialog<String>(
context: context,
builder: (context) => SimpleDialog(
title: Text('Select Theme Mode'),
children: [
SimpleDialogOption(
child: Text('System'),
onPressed: () => Navigator.pop(context, 'system'),
),
SimpleDialogOption(
child: Text('Light'),
onPressed: () => Navigator.pop(context, 'light'),
),
SimpleDialogOption(
child: Text('Dark'),
onPressed: () => Navigator.pop(context, 'dark'),
),
],
),
);
if (result != null) {
setState(() {
_themeMode = result;
UserSettings.setThemeMode(result);
});
}
}
// 其他选择器实现...
}
最佳实践
- key 集中定义常量(文中
UserSettings已是这个做法),别在业务代码里散落手写字符串:拼错一个字母就是读不到默认值,而且不报错,查起来极其痛苦。 init()在main里 await 一次,之后全同步读:getString本身是同步的,别再给它包一层async/await脱裤子放屁。- 存枚举、小配置转字符串,别塞大 JSON:单个 value 建议别超过几十 KB——Android 底层是整个 XML 文件读进内存再解析,value 越大每次
getInstance越慢;大文本、长列表走文件或 SQLite。
注意事项
- Token、密码别存这里:Android 上是 XML 明文文件,root 机直接可读;敏感信息走
flutter_secure_storage(Keystore/Keychain),API 啰嗦一点但安全没有”差不多”。 setXxx返回 true 只代表写进了内存,落盘是异步的:App 被系统杀掉时,最后一次写入可能丢失——别拿它存”钱、订单状态”这种丢不起的数据。- 别当高频计数器用:每秒记一次步数、滚动时实时存位置,每次
set都会触发全量 XML 重写,费电还可能掉帧;高频数据放内存,退出时写一次。 - iOS 上数据进
NSUserDefaults,会跟随 iCloud 备份:别存设备相关的临时大块数据,用户换机恢复时会把垃圾也带过去,白白占用备份空间。
实战案例:Token 千万别明文塞进去
SharedPreferences 用起来太顺手,顺手到容易犯错。我在看 某物流冷链项目(一个货运 App)代码时发现,它的 lib/common/utils/storage.dart 把 SharedPreferences 封装得很漂亮,但登录 Token 是明文存进去的。这就是个现成的反面教材:SharedPreferences 在 Android 上是 XML 明文文件,root 过的设备或备份导出都能直接读到,Token 泄露等于账号泄露。我的取舍是:主题、语言、字体大小这种非敏感配置放心走 SharedPreferences(flutter_brightness 里也是这么用的);Token、refreshToken 一律走 flutter_secure_storage(Keystore/Keychain),读写是异步的、API 稍啰嗦,但安全这件事没有”差不多”。顺手提一句:封装工具类时给 key 加统一前缀(比如 app_),能避免和插件自己写的 key 撞车,这个坑我踩过。
总结
SharedPreferences 是一个简单但实用的数据持久化方案,适合存储应用配置和小型数据。通过合理封装和使用,可以方便地实现数据的本地存储功能。
