Flutter for OpenHarmony三国杀攻略App实战 - 数据持久化与本地存储
数据持久化是移动应用开发中的核心技术之一,它确保用户数据在应用重启后仍然可用。final int?id;createdAt,?0,?0,数据模型类提供了对象关系映射(ORM)功能。toMap()方法将对象转换为数据库可存储的 Map,fromMap()工厂构造函数从 Map 创建对象。注意 SQLite 不支持布尔类型,需要转换为整数存储。

前言
数据持久化是移动应用开发中的核心技术之一,它确保用户数据在应用重启后仍然可用。本文将基于我们的三国杀攻略App项目,详细介绍如何实现完整的数据持久化方案,包括用户设置、战绩记录、收藏数据等多种存储需求。
数据存储需求分析
应用数据分类
在我们的三国杀攻略App中,需要持久化的数据主要包括:
- 用户设置:主题模式、语言设置、通知开关
- 战绩记录:游戏胜负记录、积分统计
- 收藏数据:收藏的武将、攻略文章
- 缓存数据:武将图片、网络请求缓存
- 用户状态:登录状态、个人信息
SharedPreferences 轻量存储
基础配置存储
import 'package:shared_preferences/shared_preferences.dart';
class PreferencesManager {
static SharedPreferences? _prefs;
static Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
// 主题设置
static bool get isDarkMode => _prefs?.getBool('dark_mode') ?? false;
static Future<void> setDarkMode(bool value) async {
await _prefs?.setBool('dark_mode', value);
}
// 语言设置
static String get language => _prefs?.getString('language') ?? 'zh_CN';
static Future<void> setLanguage(String value) async {
await _prefs?.setString('language', value);
}
}
SharedPreferences 是存储简单键值对数据的最佳选择。这里我们创建了一个管理类来统一处理应用设置。init() 方法在应用启动时调用,确保 SharedPreferences 实例可用。使用 静态方法 让全局访问更加便捷。
用户状态管理
class UserPreferences {
static const String _keyUserToken = 'user_token';
static const String _keyUserInfo = 'user_info';
static const String _keyLoginTime = 'login_time';
// 保存用户登录信息
static Future<void> saveUserLogin(String token, Map<String, dynamic> userInfo) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_keyUserToken, token);
await prefs.setString(_keyUserInfo, json.encode(userInfo));
await prefs.setInt(_keyLoginTime, DateTime.now().millisecondsSinceEpoch);
}
// 获取用户令牌
static Future<String?> getUserToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(_keyUserToken);
}
// 检查登录状态
static Future<bool> isLoggedIn() async {
final token = await getUserToken();
if (token == null) return false;
// 检查令牌是否过期(7天)
final prefs = await SharedPreferences.getInstance();
final loginTime = prefs.getInt(_keyLoginTime) ?? 0;
final now = DateTime.now().millisecondsSinceEpoch;
final daysPassed = (now - loginTime) / (1000 * 60 * 60 * 24);
return daysPassed < 7;
}
}
用户状态管理需要考虑 安全性和时效性。这里我们不仅存储了用户令牌和信息,还记录了登录时间用于检查令牌是否过期。使用 json.encode 将复杂对象转换为字符串存储。
SQLite 数据库存储
数据库初始化
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseHelper {
static Database? _database;
static const String _databaseName = 'sanguosha_app.db';
static const int _databaseVersion = 1;
static Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDatabase();
return _database!;
}
static Future<Database> _initDatabase() async {
String path = join(await getDatabasesPath(), _databaseName);
return await openDatabase(
path,
version: _databaseVersion,
onCreate: _onCreate,
onUpgrade: _onUpgrade,
);
}
static Future<void> _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE game_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date INTEGER NOT NULL,
is_win INTEGER NOT NULL,
identity TEXT NOT NULL,
score INTEGER DEFAULT 0,
duration INTEGER DEFAULT 0,
created_at INTEGER NOT NULL
)
''');
await db.execute('''
CREATE TABLE favorite_heroes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hero_id TEXT NOT NULL UNIQUE,
hero_name TEXT NOT NULL,
country TEXT NOT NULL,
added_at INTEGER NOT NULL
)
''');
}
}
SQLite 适合存储结构化的复杂数据。这里我们创建了游戏记录和收藏武将两个表。使用 INTEGER PRIMARY KEY AUTOINCREMENT 创建自增主键,UNIQUE 约束防止重复收藏。onCreate 方法在数据库首次创建时执行。
战绩记录管理
class GameRecordDao {
static Future<int> insertRecord(GameRecord record) async {
final db = await DatabaseHelper.database;
return await db.insert('game_records', record.toMap());
}
static Future<List<GameRecord>> getAllRecords() async {
final db = await DatabaseHelper.database;
final List<Map<String, dynamic>> maps = await db.query(
'game_records',
orderBy: 'created_at DESC',
);
return List.generate(maps.length, (i) {
return GameRecord.fromMap(maps[i]);
});
}
static Future<Map<String, int>> getStatistics() async {
final db = await DatabaseHelper.database;
final List<Map<String, dynamic>> result = await db.rawQuery('''
SELECT
COUNT(*) as total_games,
SUM(CASE WHEN is_win = 1 THEN 1 ELSE 0 END) as wins,
AVG(score) as avg_score
FROM game_records
''');
final stats = result.first;
return {
'totalGames': stats['total_games'] ?? 0,
'wins': stats['wins'] ?? 0,
'avgScore': (stats['avg_score'] ?? 0.0).round(),
};
}
}
数据访问对象(DAO)模式将数据库操作封装成方法。insertRecord 插入新记录,getAllRecords 获取所有记录并按时间倒序排列。getStatistics 使用 SQL聚合函数 计算统计数据,这比在应用层计算更高效。
数据模型定义
class GameRecord {
final int? id;
final DateTime date;
final bool isWin;
final String identity;
final int score;
final int duration;
final DateTime createdAt;
GameRecord({
this.id,
required this.date,
required this.isWin,
required this.identity,
this.score = 0,
this.duration = 0,
DateTime? createdAt,
}) : createdAt = createdAt ?? DateTime.now();
Map<String, dynamic> toMap() {
return {
'id': id,
'date': date.millisecondsSinceEpoch,
'is_win': isWin ? 1 : 0,
'identity': identity,
'score': score,
'duration': duration,
'created_at': createdAt.millisecondsSinceEpoch,
};
}
factory GameRecord.fromMap(Map<String, dynamic> map) {
return GameRecord(
id: map['id'],
date: DateTime.fromMillisecondsSinceEpoch(map['date']),
isWin: map['is_win'] == 1,
identity: map['identity'],
score: map['score'] ?? 0,
duration: map['duration'] ?? 0,
createdAt: DateTime.fromMillisecondsSinceEpoch(map['created_at']),
);
}
}
数据模型类提供了 对象关系映射(ORM)功能。toMap() 方法将对象转换为数据库可存储的 Map,fromMap() 工厂构造函数从 Map 创建对象。注意 SQLite 不支持布尔类型,需要转换为整数存储。
文件存储系统
图片缓存管理
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:crypto/crypto.dart';
class ImageCacheManager {
static String _getCacheKey(String url) {
var bytes = utf8.encode(url);
var digest = md5.convert(bytes);
return digest.toString();
}
static Future<String> getCacheDir() async {
final directory = await getApplicationDocumentsDirectory();
final cacheDir = Directory('${directory.path}/image_cache');
if (!await cacheDir.exists()) {
await cacheDir.create(recursive: true);
}
return cacheDir.path;
}
static Future<File?> getCachedImage(String url) async {
try {
final cacheDir = await getCacheDir();
final cacheKey = _getCacheKey(url);
final file = File('$cacheDir/$cacheKey.jpg');
if (await file.exists()) {
// 检查文件是否过期(7天)
final stat = await file.stat();
final age = DateTime.now().difference(stat.modified).inDays;
if (age < 7) {
return file;
} else {
await file.delete();
}
}
return null;
} catch (e) {
print('获取缓存图片失败: $e');
return null;
}
}
static Future<File> cacheImage(String url, List<int> bytes) async {
final cacheDir = await getCacheDir();
final cacheKey = _getCacheKey(url);
final file = File('$cacheDir/$cacheKey.jpg');
return await file.writeAsBytes(bytes);
}
}
图片缓存使用 文件系统存储,通过 MD5 哈希生成唯一的缓存键。path_provider 包提供了获取应用文档目录的方法。缓存文件设置了 7天过期时间,避免占用过多存储空间。
数据导出功能
class DataExportManager {
static Future<File> exportGameRecords() async {
final records = await GameRecordDao.getAllRecords();
final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/game_records_export.json');
final exportData = {
'export_time': DateTime.now().toIso8601String(),
'total_records': records.length,
'records': records.map((r) => r.toMap()).toList(),
};
final jsonString = json.encode(exportData);
return await file.writeAsString(jsonString);
}
static Future<void> importGameRecords(File file) async {
try {
final jsonString = await file.readAsString();
final data = json.decode(jsonString);
final recordsData = data['records'] as List;
for (final recordData in recordsData) {
final record = GameRecord.fromMap(recordData);
await GameRecordDao.insertRecord(record);
}
} catch (e) {
throw Exception('导入数据失败: $e');
}
}
}
数据导出功能将数据库数据转换为 JSON格式 保存到文件。导出文件包含时间戳和记录总数等元信息。导入功能支持从JSON文件恢复数据,实现了数据的备份和迁移。
状态管理集成
GetX 状态持久化
class SettingsController extends GetxController {
final isDarkMode = false.obs;
final language = 'zh_CN'.obs;
final notificationEnabled = true.obs;
void onInit() {
super.onInit();
loadSettings();
}
Future<void> loadSettings() async {
isDarkMode.value = PreferencesManager.isDarkMode;
language.value = PreferencesManager.language;
notificationEnabled.value = PreferencesManager.notificationEnabled;
}
Future<void> toggleDarkMode() async {
isDarkMode.value = !isDarkMode.value;
await PreferencesManager.setDarkMode(isDarkMode.value);
Get.changeTheme(isDarkMode.value ? ThemeData.dark() : ThemeData.light());
}
Future<void> setLanguage(String lang) async {
language.value = lang;
await PreferencesManager.setLanguage(lang);
// 触发语言切换
Get.updateLocale(Locale(lang.split('_')[0], lang.split('_')[1]));
}
}
GetX 控制器 将响应式状态与持久化存储结合。状态变化时自动保存到本地,应用启动时从本地恢复状态。这种模式确保了状态的一致性和持久性。
战绩数据控制器
class GameRecordsController extends GetxController {
final records = <GameRecord>[].obs;
final statistics = <String, int>{}.obs;
final isLoading = false.obs;
void onInit() {
super.onInit();
loadRecords();
}
Future<void> loadRecords() async {
isLoading.value = true;
try {
records.value = await GameRecordDao.getAllRecords();
statistics.value = await GameRecordDao.getStatistics();
} catch (e) {
Get.snackbar('错误', '加载战绩失败: $e');
} finally {
isLoading.value = false;
}
}
Future<void> addRecord(bool isWin, String identity) async {
final record = GameRecord(
date: DateTime.now(),
isWin: isWin,
identity: identity,
score: isWin ? 100 : 50,
);
try {
await GameRecordDao.insertRecord(record);
await loadRecords(); // 重新加载数据
Get.snackbar('成功', '战绩记录已保存');
} catch (e) {
Get.snackbar('错误', '保存战绩失败: $e');
}
}
}
战绩控制器管理游戏记录的增删改查操作。使用 响应式列表 让界面自动更新。错误处理通过 SnackBar 向用户反馈操作结果。
数据同步策略
云端同步实现
class DataSyncManager {
static const String _lastSyncKey = 'last_sync_time';
static Future<void> syncToCloud() async {
try {
final records = await GameRecordDao.getAllRecords();
final settings = await _getLocalSettings();
final syncData = {
'records': records.map((r) => r.toMap()).toList(),
'settings': settings,
'sync_time': DateTime.now().toIso8601String(),
};
// 上传到云端(示例)
final response = await http.post(
Uri.parse('https://api.example.com/sync'),
headers: {'Content-Type': 'application/json'},
body: json.encode(syncData),
);
if (response.statusCode == 200) {
await _updateLastSyncTime();
Get.snackbar('成功', '数据已同步到云端');
}
} catch (e) {
Get.snackbar('错误', '同步失败: $e');
}
}
static Future<void> syncFromCloud() async {
try {
final response = await http.get(
Uri.parse('https://api.example.com/sync'),
headers: await _getAuthHeaders(),
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
await _restoreFromSyncData(data);
Get.snackbar('成功', '数据已从云端恢复');
}
} catch (e) {
Get.snackbar('错误', '恢复数据失败: $e');
}
}
static Future<bool> shouldAutoSync() async {
final prefs = await SharedPreferences.getInstance();
final lastSync = prefs.getInt(_lastSyncKey) ?? 0;
final now = DateTime.now().millisecondsSinceEpoch;
final hoursSinceSync = (now - lastSync) / (1000 * 60 * 60);
return hoursSinceSync >= 24; // 24小时自动同步一次
}
}
云端同步功能实现了数据的备份和多设备共享。使用 HTTP请求 与服务器通信,支持上传和下载数据。自动同步策略避免了频繁的网络请求。
数据迁移方案
版本升级处理
class DatabaseMigration {
static Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
if (oldVersion < 2) {
// 版本1到版本2的升级
await db.execute('ALTER TABLE game_records ADD COLUMN score INTEGER DEFAULT 0');
await db.execute('ALTER TABLE game_records ADD COLUMN duration INTEGER DEFAULT 0');
}
if (oldVersion < 3) {
// 版本2到版本3的升级
await db.execute('''
CREATE TABLE user_achievements (
id INTEGER PRIMARY KEY AUTOINCREMENT,
achievement_id TEXT NOT NULL UNIQUE,
unlocked_at INTEGER NOT NULL
)
''');
}
}
static Future<void> migrateUserData() async {
final prefs = await SharedPreferences.getInstance();
final migrationVersion = prefs.getInt('migration_version') ?? 0;
if (migrationVersion < 1) {
// 迁移旧版本的设置数据
await _migrateSettingsV1();
await prefs.setInt('migration_version', 1);
}
if (migrationVersion < 2) {
// 迁移战绩数据格式
await _migrateRecordsV2();
await prefs.setInt('migration_version', 2);
}
}
}
数据迁移确保应用升级时的数据兼容性。数据库版本控制 通过 onUpgrade 回调处理表结构变更。用户数据迁移处理设置和数据格式的变化。
性能优化策略
批量操作优化
class BatchOperations {
static Future<void> batchInsertRecords(List<GameRecord> records) async {
final db = await DatabaseHelper.database;
final batch = db.batch();
for (final record in records) {
batch.insert('game_records', record.toMap());
}
await batch.commit(noResult: true);
}
static Future<void> cleanupOldData() async {
final db = await DatabaseHelper.database;
final thirtyDaysAgo = DateTime.now().subtract(Duration(days: 30));
await db.delete(
'game_records',
where: 'created_at < ?',
whereArgs: [thirtyDaysAgo.millisecondsSinceEpoch],
);
}
}
批量操作 提高了数据库操作的效率。使用 batch 对象可以将多个操作合并为一个事务。定期清理旧数据避免数据库过大影响性能。
缓存策略优化
class CacheManager {
static final Map<String, dynamic> _memoryCache = {};
static const int _maxCacheSize = 100;
static T? get<T>(String key) {
return _memoryCache[key] as T?;
}
static void set<T>(String key, T value) {
if (_memoryCache.length >= _maxCacheSize) {
// 移除最旧的缓存项
final firstKey = _memoryCache.keys.first;
_memoryCache.remove(firstKey);
}
_memoryCache[key] = value;
}
static void clear() {
_memoryCache.clear();
}
}
内存缓存 减少了重复的数据库查询。使用 LRU(最近最少使用)策略管理缓存大小,避免内存泄漏。
数据安全考虑
敏感数据加密
import 'package:crypto/crypto.dart';
class SecurityManager {
static String _encryptionKey = 'your_secret_key_here';
static String encryptData(String data) {
var key = utf8.encode(_encryptionKey);
var bytes = utf8.encode(data);
var hmacSha256 = Hmac(sha256, key);
var digest = hmacSha256.convert(bytes);
return digest.toString();
}
static Future<void> saveSecureData(String key, String value) async {
final prefs = await SharedPreferences.getInstance();
final encryptedValue = encryptData(value);
await prefs.setString(key, encryptedValue);
}
static Future<bool> verifySecureData(String key, String value) async {
final prefs = await SharedPreferences.getInstance();
final storedValue = prefs.getString(key);
if (storedValue == null) return false;
final encryptedValue = encryptData(value);
return storedValue == encryptedValue;
}
}
敏感数据需要 加密存储,这里使用 HMAC-SHA256 算法。虽然这不是完全的加密,但提供了数据完整性验证。对于更高安全要求,应使用专门的加密库。
总结
通过本文的详细实现,我们构建了一个完整的数据持久化系统。这个系统涵盖了轻量级的 SharedPreferences 存储、结构化的 SQLite 数据库、文件系统缓存、云端同步等多个层面。
核心技术要点:
- 使用 SharedPreferences 存储简单配置数据
- 通过 SQLite 管理复杂的结构化数据
- 实现文件系统缓存提升性能
- 集成状态管理确保数据一致性
- 提供数据迁移和同步机制
- 考虑安全性和性能优化
这套数据持久化方案为应用提供了可靠的数据存储基础,确保用户数据的安全性和可用性。在下一篇文章中,我们将探讨性能优化与最佳实践,进一步提升应用的用户体验。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)