key-value 数据库在很多时候都是用来作为缓存使用的。而大部分 key-value 数据库都能配置缓存过期的时,但是在 Flutter 中常用的 shared_preferences
却没有相应的缓存过期策略,不过我们可以自己手动实现。
封装 shared_preferences
shared_preferences
本身使用起来已经非常方便了,所以只要进行简单的封装就可以,这里使用泛型的方式。
import 'package:shared_preferences/shared_preferences.dart';
class PrefUtil {
late final SharedPreferencesWithCache _prefs;
Future<void> initPref() async {
_prefs = await SharedPreferencesWithCache.create(
cacheOptions: const SharedPreferencesWithCacheOptions(allowList: <String>{
}));
}
Future<void> reset() async {
await _prefs.clear();
}
Future<void> setValue<T>(String key, T value) async {
if (T == int) {
await _prefs.setInt(key, value as int);
} else if (T == bool) {
await _prefs.setBool(key, value as bool);
} else if (T == double) {
await _prefs.setDouble(key, value as double);
} else if (T == String) {
await _prefs.setString(key, value as String);
} else if (T == List<String>) {
await _prefs.setStringList(key, value as List<String>);
} else {
throw ArgumentError('Unsupported type: $T');
}
}
T? getValue<T>(String key) {
if (T == int) {
return _prefs.getInt(key) as T?;
} else if (T == bool) {
return _prefs.getBool(key) as T?;
} else if (T == double) {
return _prefs.getDouble(key) as T?;
} else if (T == String) {
return _prefs.getString(key) as T?;
} else if (T == List<String>) {
return _prefs.getStringList(key) as T?;
} else {
throw ArgumentError('Unsupported type: $T');
}
}
Future<void> removeValue(String key) async {
await _prefs.remove(key);
}
}
需要注意的时,在 shared_preferences
的 2.3.0 版本之后,由于底层实现方式的改变,原有的 SharedPreferences
类型被替换为了 SharedPreferencesAsync
和 SharedPreferencesWithCache
,简单的说就是为了安全考虑,分为了异步调用,和同步调用,当使用 SharedPreferencesAsync
时,用于取值的 get 方法返回值是一个 Future
,也就是必须异步调用。而 SharedPreferencesWithCache
则仍可以使用同步调用的方式,和名字一样,原理是通过缓存的方式来实现的,出于安全考虑,现在要使用同步调用的话,必须要预设好 allowList
,也就是将来要存储数据的 key
,对不在列表中的 key
将会抛出异常。也就是说,如果你不知道将来要存储内容的 key
值是多少,就要使用 SharedPreferencesAsync
进行异步调用。
缓存控制类
其实实现的原理也非常简单,只要在存储原有数据的同时,记录下存储时的时间戳,下次取数据的时候,对比一下时间戳的差值,如果过期了,就可以进行进一步的操作。因为 shared_perferences
可以直接存储 List<String>
,所以我们直接将要存储的值放入一个列表中即可。
import 'dart:async';
import 'package:mood_diary/utils/utils.dart';
class CacheUtil {
Future<List<String>?> getCacheList(String key, Future<List<String>?> Function() fetchData,
{int maxAgeMillis = 900000}) async {
var cachedData = Utils().prefUtil.getValue<List<String>>(key);
// 检查缓存是否有效,如果无效则更新缓存
if (cachedData == null || _isCacheExpired(cachedData, maxAgeMillis)) {
await _updateCacheList(key, fetchData);
cachedData = Utils().prefUtil.getValue<List<String>>(key); // 获取更新后的缓存数据
}
return cachedData;
}
bool _isCacheExpired(List<String> cachedData, int maxAgeMillis) {
if (cachedData.length < 2) {
return true; // 缓存数据格式不正确,视为过期
}
int timestamp = int.parse(cachedData.last);
return DateTime.now().millisecondsSinceEpoch - timestamp >= maxAgeMillis;
}
Future<void> _updateCacheList(String key, Future<List<String>?> Function() fetchData) async {
var newData = await fetchData();
if (newData != null) {
await Utils()
.prefUtil
.setValue<List<String>>(key, newData..add(DateTime.now().millisecondsSinceEpoch.toString()));
}
}
}
使用也非常简单,要取值时只需要调用 getCacheList
方法即可,fetchData
是一个函数,用于更新数据,这样就可以实现数据的自动更新。