SharedPreferences高级用法:Flutter在鸿蒙平台实现复杂数据存储


概述
在上一篇文章中,我们学习了SharedPreferences的基本用法,包括存储和读取基本数据类型。但在实际项目中,我们经常需要存储更复杂的数据结构,如对象、列表、嵌套数据等。
本文将深入探讨SharedPreferences的高级用法,包括如何通过JSON序列化存储复杂对象、实现自定义对象的序列化、管理数据版本、添加内存缓存等实用技巧。这些技巧将帮助你在实际项目中更高效地使用SharedPreferences。
一、JSON序列化存储复杂对象
1.1 为什么需要JSON序列化
SharedPreferences只支持基本数据类型的存储,但实际应用中我们经常需要存储复杂的数据结构。JSON序列化提供了一种将复杂对象转换为字符串的方法,从而可以利用setString方法存储任意复杂的数据。
1.2 基本用法
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
Future<void> saveComplexData() async {
final prefs = await SharedPreferences.getInstance();
Map<String, dynamic> user = {
'id': '12345',
'name': '张三',
'age': 25,
'email': 'zhangsan@example.com',
'tags': ['Flutter', 'HarmonyOS', 'Dart'],
'settings': {
'theme': 'dark',
'notifications': true,
'fontSize': 16.0
}
};
// 序列化
String userJson = jsonEncode(user);
await prefs.setString('user_data', userJson);
// 反序列化
String? storedJson = prefs.getString('user_data');
if (storedJson != null) {
Map<String, dynamic> decodedUser = jsonDecode(storedJson);
print(decodedUser['name']); // 输出: 张三
}
}
1.3 存储列表数据
Future<void> saveListData() async {
final prefs = await SharedPreferences.getInstance();
// 方法一:使用setStringList(仅支持字符串列表)
List<String> favoriteColors = ['红色', '蓝色', '绿色'];
await prefs.setStringList('favorite_colors', favoriteColors);
// 方法二:使用JSON序列化(支持任意类型列表)
List<Map<String, dynamic>> products = [
{'id': 'p001', 'name': '手机', 'price': 3999},
{'id': 'p002', 'name': '电脑', 'price': 8999},
];
String productsJson = jsonEncode(products);
await prefs.setString('products', productsJson);
// 读取
List<String>? colors = prefs.getStringList('favorite_colors');
String? productsStored = prefs.getString('products');
List<dynamic> decodedProducts = jsonDecode(productsStored!);
}
1.4 存储时间戳
Future<void> saveTimestamp() async {
final prefs = await SharedPreferences.getInstance();
// 存储时间戳(毫秒)
int timestamp = DateTime.now().millisecondsSinceEpoch;
await prefs.setInt('last_access_time', timestamp);
// 读取并转换
int? storedTime = prefs.getInt('last_access_time');
if (storedTime != null) {
DateTime accessTime = DateTime.fromMillisecondsSinceEpoch(storedTime);
print('上次访问时间: $accessTime');
}
}
二、自定义对象序列化
2.1 创建可序列化的类
对于复杂的业务对象,我们可以创建专门的类来处理序列化和反序列化:
import 'dart:convert';
class UserProfile {
final String id;
final String name;
final int age;
final bool isVip;
final DateTime? createdAt;
UserProfile({
required this.id,
required this.name,
required this.age,
required this.isVip,
this.createdAt,
});
// 转换为JSON字符串
String toJson() {
return jsonEncode({
'id': id,
'name': name,
'age': age,
'isVip': isVip,
'createdAt': createdAt?.toIso8601String(),
});
}
// 从JSON字符串创建对象
static UserProfile fromJson(String jsonStr) {
Map<String, dynamic> data = jsonDecode(jsonStr);
return UserProfile(
id: data['id'],
name: data['name'],
age: data['age'],
isVip: data['isVip'],
createdAt: data['createdAt'] != null
? DateTime.parse(data['createdAt'])
: null,
);
}
}
2.2 使用自定义对象
Future<void> saveUserProfile() async {
final prefs = await SharedPreferences.getInstance();
UserProfile profile = UserProfile(
id: '67890',
name: '李四',
age: 30,
isVip: true,
createdAt: DateTime.now(),
);
// 存储
await prefs.setString('user_profile', profile.toJson());
// 读取
String? stored = prefs.getString('user_profile');
if (stored != null) {
UserProfile loadedProfile = UserProfile.fromJson(stored);
print('用户名: ${loadedProfile.name}');
print('VIP状态: ${loadedProfile.isVip}');
}
}
2.3 使用json_serializable包
对于更复杂的对象,可以使用json_serializable包来自动生成序列化代码:
dependencies:
json_annotation: ^4.8.1
dev_dependencies:
build_runner: ^2.4.6
json_serializable: ^6.7.0
import 'package:json_annotation/json_annotation.dart';
part 'user_profile.g.dart';
()
class UserProfile {
final String id;
final String name;
final int age;
final bool isVip;
UserProfile({
required this.id,
required this.name,
required this.age,
required this.isVip,
});
factory UserProfile.fromJson(Map<String, dynamic> json) =>
_$UserProfileFromJson(json);
Map<String, dynamic> toJson() => _$UserProfileToJson(this);
}
运行命令生成代码:
flutter pub run build_runner build
三、数据版本管理
3.1 为什么需要版本管理
随着应用的迭代更新,数据结构可能会发生变化。为了确保旧版本的数据能够正确迁移到新版本,我们需要实现数据版本管理机制。
3.2 实现版本管理
class DataMigrationManager {
static const String _versionKey = 'data_version';
Future<void> checkAndMigrate() async {
final prefs = await SharedPreferences.getInstance();
int currentVersion = prefs.getInt(_versionKey) ?? 0;
if (currentVersion < 1) {
await _migrateFromVersion0To1(prefs);
}
if (currentVersion < 2) {
await _migrateFromVersion1To2(prefs);
}
if (currentVersion < 3) {
await _migrateFromVersion2To3(prefs);
}
await prefs.setInt(_versionKey, 3);
}
Future<void> _migrateFromVersion0To1(SharedPreferences prefs) async {
// 版本1新增了用户ID字段
String? username = prefs.getString('username');
if (username != null) {
await prefs.setString('user_id', 'default_${DateTime.now().millisecondsSinceEpoch}');
}
}
Future<void> _migrateFromVersion1To2(SharedPreferences prefs) async {
// 版本2将theme字段从字符串改为Map
String? oldTheme = prefs.getString('theme');
if (oldTheme != null) {
Map<String, dynamic> newTheme = {
'mode': oldTheme,
'accentColor': '#FF5722',
};
await prefs.setString('theme', jsonEncode(newTheme));
}
}
Future<void> _migrateFromVersion2To3(SharedPreferences prefs) async {
// 版本3新增了通知设置
if (!prefs.containsKey('notifications_enabled')) {
await prefs.setBool('notifications_enabled', true);
}
}
}
3.3 在应用启动时检查
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 检查并执行数据迁移
DataMigrationManager migrationManager = DataMigrationManager();
await migrationManager.checkAndMigrate();
runApp(const MyApp());
}
四、内存缓存优化
4.1 为什么需要缓存
SharedPreferences的读取操作虽然很快,但频繁读取仍然会产生磁盘IO开销。通过添加内存缓存层,可以显著提高读取性能。
4.2 实现缓存类
class CachedPreferences {
late SharedPreferences _prefs;
final Map<String, dynamic> _cache = {};
int _hitCount = 0;
int _missCount = 0;
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
Future<String?> getString(String key) async {
if (_cache.containsKey(key)) {
_hitCount++;
return _cache[key];
}
_missCount++;
String? value = _prefs.getString(key);
_cache[key] = value;
return value;
}
Future<void> setString(String key, String value) async {
_cache[key] = value;
await _prefs.setString(key, value);
}
Future<int?> getInt(String key) async {
if (_cache.containsKey(key)) {
_hitCount++;
return _cache[key];
}
_missCount++;
int? value = _prefs.getInt(key);
_cache[key] = value;
return value;
}
Future<void> setInt(String key, int value) async {
_cache[key] = value;
await _prefs.setInt(key, value);
}
void clearCache() {
_cache.clear();
_hitCount = 0;
_missCount = 0;
}
double get cacheHitRate {
int total = _hitCount + _missCount;
return total == 0 ? 0.0 : _hitCount / total;
}
void printStats() {
print('缓存命中: $_hitCount');
print('缓存未命中: $_missCount');
print('命中率: ${(cacheHitRate * 100).toStringAsFixed(2)}%');
}
}
4.3 使用缓存类
void main() async {
CachedPreferences cachedPrefs = CachedPreferences();
await cachedPrefs.init();
// 第一次读取(缓存未命中)
String? value1 = await cachedPrefs.getString('username');
// 第二次读取(缓存命中)
String? value2 = await cachedPrefs.getString('username');
// 输出统计信息
cachedPrefs.printStats();
}
五、高级应用场景
5.1 计数器实现
class CounterService {
static const String _key = 'app_counter';
Future<int> getCount() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getInt(_key) ?? 0;
}
Future<int> increment() async {
final prefs = await SharedPreferences.getInstance();
int current = prefs.getInt(_key) ?? 0;
int next = current + 1;
await prefs.setInt(_key, next);
return next;
}
Future<void> reset() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt(_key, 0);
}
}
5.2 用户设置管理
class UserSettings {
static const String _themeKey = 'settings_theme';
static const String _languageKey = 'settings_language';
static const String _notificationsKey = 'settings_notifications';
static const String _soundKey = 'settings_sound';
Future<void> saveTheme(String theme) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_themeKey, theme);
}
Future<String> getTheme() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(_themeKey) ?? 'light';
}
Future<void> saveLanguage(String language) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_languageKey, language);
}
Future<String> getLanguage() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(_languageKey) ?? 'zh';
}
Future<void> setNotificationsEnabled(bool enabled) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_notificationsKey, enabled);
}
Future<bool> getNotificationsEnabled() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool(_notificationsKey) ?? true;
}
Future<Map<String, dynamic>> getAllSettings() async {
final prefs = await SharedPreferences.getInstance();
return {
'theme': prefs.getString(_themeKey) ?? 'light',
'language': prefs.getString(_languageKey) ?? 'zh',
'notifications': prefs.getBool(_notificationsKey) ?? true,
'sound': prefs.getBool(_soundKey) ?? true,
};
}
Future<void> resetToDefaults() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_notificationsKey, true);
await prefs.setBool(_soundKey, true);
await prefs.setString(_themeKey, 'light');
await prefs.setString(_languageKey, 'zh');
}
}
5.3 应用状态追踪
class AppStateTracker {
static const String _firstLaunchKey = 'first_launch';
static const String _launchCountKey = 'launch_count';
static const String _lastLaunchKey = 'last_launch';
static const String _sessionStartTimeKey = 'session_start_time';
Future<bool> isFirstLaunch() async {
final prefs = await SharedPreferences.getInstance();
bool firstLaunch = prefs.getBool(_firstLaunchKey) ?? true;
if (firstLaunch) {
await prefs.setBool(_firstLaunchKey, false);
}
return firstLaunch;
}
Future<int> incrementLaunchCount() async {
final prefs = await SharedPreferences.getInstance();
int count = prefs.getInt(_launchCountKey) ?? 0;
count++;
await prefs.setInt(_launchCountKey, count);
await prefs.setString(_lastLaunchKey, DateTime.now().toIso8601String());
return count;
}
Future<int> getLaunchCount() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getInt(_launchCountKey) ?? 0;
}
Future<void> startSession() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt(_sessionStartTimeKey, DateTime.now().millisecondsSinceEpoch);
}
Future<int> getSessionDuration() async {
final prefs = await SharedPreferences.getInstance();
int? startTime = prefs.getInt(_sessionStartTimeKey);
if (startTime == null) return 0;
return DateTime.now().millisecondsSinceEpoch - startTime;
}
}
六、性能优化技巧
6.1 批量写入优化
当需要存储多个键值对时,可以使用Future.wait来并行执行,提高性能:
Future<void> batchSave(Map<String, dynamic> data) async {
final prefs = await SharedPreferences.getInstance();
List<Future<void>> operations = [];
data.forEach((key, value) {
if (value is String) {
operations.add(prefs.setString(key, value));
} else if (value is int) {
operations.add(prefs.setInt(key, value));
} else if (value is double) {
operations.add(prefs.setDouble(key, value));
} else if (value is bool) {
operations.add(prefs.setBool(key, value));
} else if (value is List<String>) {
operations.add(prefs.setStringList(key, value));
} else {
operations.add(prefs.setString(key, jsonEncode(value)));
}
});
await Future.wait(operations);
}
6.2 延迟写入策略
对于频繁更新的数据,可以实现延迟写入策略,减少磁盘IO次数:
class DebouncedPreferences {
late SharedPreferences _prefs;
final Map<String, dynamic> _pendingWrites = {};
Timer? _timer;
static const int _debounceDelay = 500; // 500毫秒延迟
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
Future<void> setString(String key, String value) async {
_pendingWrites[key] = value;
_scheduleWrite();
}
void _scheduleWrite() {
_timer?.cancel();
_timer = Timer(const Duration(milliseconds: _debounceDelay), () async {
await _flushPendingWrites();
});
}
Future<void> _flushPendingWrites() async {
if (_pendingWrites.isEmpty) return;
List<Future<void>> operations = [];
_pendingWrites.forEach((key, value) {
if (value is String) {
operations.add(_prefs.setString(key, value));
} else if (value is int) {
operations.add(_prefs.setInt(key, value));
} else if (value is bool) {
operations.add(_prefs.setBool(key, value));
}
});
await Future.wait(operations);
_pendingWrites.clear();
}
Future<void> dispose() async {
_timer?.cancel();
await _flushPendingWrites();
}
}
6.3 数据压缩
对于大型JSON数据,可以使用gzip压缩来减少存储空间:
import 'dart:convert';
import 'dart:io';
class CompressedPreferences {
late SharedPreferences _prefs;
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
Future<void> setCompressedString(String key, String value) async {
List<int> bytes = utf8.encode(value);
List<int> compressed = gzip.encode(bytes);
String base64 = base64Encode(compressed);
await _prefs.setString(key, base64);
}
Future<String?> getCompressedString(String key) async {
String? base64 = _prefs.getString(key);
if (base64 == null) return null;
List<int> compressed = base64Decode(base64);
List<int> bytes = gzip.decode(compressed);
return utf8.decode(bytes);
}
}
七、错误处理与容错
7.1 异常捕获
class SafePreferences {
late SharedPreferences _prefs;
bool _initialized = false;
Future<void> init() async {
try {
_prefs = await SharedPreferences.getInstance();
_initialized = true;
} catch (e) {
print('SharedPreferences初始化失败: $e');
}
}
Future<String?> getString(String key, {String? defaultValue}) async {
if (!_initialized) return defaultValue;
try {
return _prefs.getString(key) ?? defaultValue;
} catch (e) {
print('读取String失败: $e');
return defaultValue;
}
}
Future<void> setString(String key, String value) async {
if (!_initialized) return;
try {
await _prefs.setString(key, value);
} catch (e) {
print('写入String失败: $e');
}
}
Future<void> setObject(String key, Object value) async {
if (!_initialized) return;
try {
String json = jsonEncode(value);
await _prefs.setString(key, json);
} catch (e) {
print('写入Object失败: $e');
}
}
Future<T?> getObject<T>(String key, T Function(Map<String, dynamic>) fromJson) async {
if (!_initialized) return null;
try {
String? json = _prefs.getString(key);
if (json == null) return null;
return fromJson(jsonDecode(json));
} catch (e) {
print('读取Object失败: $e');
return null;
}
}
}
7.2 数据校验
class ValidatedPreferences {
late SharedPreferences _prefs;
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
Future<bool> saveUser(UserProfile user) async {
if (user.id.isEmpty || user.name.isEmpty) {
return false;
}
if (user.age < 0 || user.age > 150) {
return false;
}
try {
await _prefs.setString('user_profile', user.toJson());
return true;
} catch (e) {
return false;
}
}
Future<UserProfile?> getUser() async {
try {
String? json = _prefs.getString('user_profile');
if (json == null) return null;
UserProfile user = UserProfile.fromJson(json);
// 校验数据
if (user.id.isEmpty || user.name.isEmpty) {
return null;
}
return user;
} catch (e) {
return null;
}
}
}
八、在鸿蒙平台的特殊考虑
8.1 存储路径
在鸿蒙平台上,SharedPreferences的数据存储在应用的沙箱目录中:
/data/app/el1/base/[包名]/haps/entry/files/shared_preferences/
8.2 权限配置
在鸿蒙平台上,不需要额外的权限配置,但需要确保应用具有文件读写权限。
8.3 性能差异
鸿蒙平台的文件系统性能与Android有所不同,建议:
- 避免在主线程执行大量的SharedPreferences操作
- 使用异步操作并配合
FutureBuilder或StreamBuilder - 考虑使用内存缓存来减少磁盘IO
8.4 数据备份
在鸿蒙平台上,可以通过鸿蒙的云备份功能来备份SharedPreferences数据。需要在应用配置中开启云备份支持。
九、最佳实践总结
9.1 封装原则
- 将SharedPreferences操作封装为独立的服务类
- 使用常量定义键名,避免魔法字符串
- 提供默认值,处理键不存在的情况
9.2 性能优化
- 合并多次写入操作
- 使用内存缓存减少读取次数
- 对于频繁更新的数据使用延迟写入策略
- 对于大型数据使用压缩存储
9.3 数据管理
- 实现数据版本管理,支持数据迁移
- 对复杂对象进行序列化和反序列化
- 添加数据校验和错误处理
9.4 安全考虑
- 不要存储敏感信息(密码、Token等)
- 对于敏感信息使用加密存储方案
- 定期清理过期数据
十、总结
SharedPreferences虽然是一个简单的键值对存储方案,但通过合理的设计和优化,可以满足大多数应用的数据持久化需求。本文介绍的高级用法包括:
- JSON序列化:存储复杂对象和列表数据
- 自定义对象序列化:创建可序列化的业务类
- 数据版本管理:支持应用升级时的数据迁移
- 内存缓存:提高读取性能
- 性能优化:批量写入、延迟写入、数据压缩
- 错误处理:异常捕获和数据校验
通过这些技巧,你可以在实际项目中更高效、更可靠地使用SharedPreferences。在鸿蒙平台上,这些技巧同样适用,可以帮助你构建高性能的跨平台应用。
更多推荐




所有评论(0)