在这里插入图片描述
在这里插入图片描述

概述

在上一篇文章中,我们学习了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操作
  • 使用异步操作并配合FutureBuilderStreamBuilder
  • 考虑使用内存缓存来减少磁盘IO

8.4 数据备份

在鸿蒙平台上,可以通过鸿蒙的云备份功能来备份SharedPreferences数据。需要在应用配置中开启云备份支持。

九、最佳实践总结

9.1 封装原则

  • 将SharedPreferences操作封装为独立的服务类
  • 使用常量定义键名,避免魔法字符串
  • 提供默认值,处理键不存在的情况

9.2 性能优化

  • 合并多次写入操作
  • 使用内存缓存减少读取次数
  • 对于频繁更新的数据使用延迟写入策略
  • 对于大型数据使用压缩存储

9.3 数据管理

  • 实现数据版本管理,支持数据迁移
  • 对复杂对象进行序列化和反序列化
  • 添加数据校验和错误处理

9.4 安全考虑

  • 不要存储敏感信息(密码、Token等)
  • 对于敏感信息使用加密存储方案
  • 定期清理过期数据

十、总结

SharedPreferences虽然是一个简单的键值对存储方案,但通过合理的设计和优化,可以满足大多数应用的数据持久化需求。本文介绍的高级用法包括:

  1. JSON序列化:存储复杂对象和列表数据
  2. 自定义对象序列化:创建可序列化的业务类
  3. 数据版本管理:支持应用升级时的数据迁移
  4. 内存缓存:提高读取性能
  5. 性能优化:批量写入、延迟写入、数据压缩
  6. 错误处理:异常捕获和数据校验

通过这些技巧,你可以在实际项目中更高效、更可靠地使用SharedPreferences。在鸿蒙平台上,这些技巧同样适用,可以帮助你构建高性能的跨平台应用。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐