简介
在APP的开发中,本地缓存是常见的需求之一。iOS有flutter_secure_storage,Android有SharedPreferences,这些都是系统提供的API,可以直接使用。
1. shared_preferences
-
Flutter没有提供系统的API来做本地缓存,社区出现了原生API的封装插件shared_preferences;点赞数还是很多的
很受欢迎
-
这么受欢迎的插件一般都会用到,我们也选了这个,并且还在外面包了一层:
设置,获取单例等方法是异步的,很不好用,经过下面这样包装一下,在程序启动的时候执行一下LocalStorage.getInstance();其他的地方就当做同步方法来用,只要不是set之后马上get,一般问题不大。
一直不明白,为什么要把这种简单的set和get缓存设计成异步的。
class LocalStorage {
static SharedPreferences? prefs;
static initSP() async {
prefs = await SharedPreferences.getInstance();
}
static save(String key, String value) {
prefs?.setString(key, value);
}
static get(String key) {
return prefs?.get(key);
}
static remove(String key) {
prefs?.remove(key);
}
}
2. GetStorage
-
这个插件有点奇怪,在Get的主页有介绍,但是直接用又不行
GetX中的介绍
-
估计是从GetX中独立出去的一个插件,在Pub中能够搜到get_storage;点赞数也还可以:
点赞数
-
像介绍中那样当做同步使用是可以的
GetStorage box = GetStorage();
box.write('quote', 'GetX is the best');
print(box.read('quote'));
- 不过本质上,它的初始化和write也是异步的,这点跟shared_preferences是一样的,只是封装得比较好,让你用起来好像是同步的set和get一样
Future<void> _init() async {
try {
await _concrete.init(_initialData);
} catch (err) {
throw err;
}
}
/// Reads a value in your container with the given key.
T? read<T>(String key) {
return _concrete.read(key);
}
/// Write data on your container
Future<void> write(String key, dynamic value) async {
writeInMemory(key, value);
// final _encoded = json.encode(value);
// await _concrete.write(key, json.decode(_encoded));
return _tryFlush();
}
- 命名空间,用名字区分storage;修改监听等功能只能算是锦上添花,算不上亮点功能。所以,目前大多数还是用了shared_preferences,用GetStorage的还是相对少数。
其实,相对来说GetStorage还是更简单一些,可以当做一个有命名空间的同步key vlaue本地缓存Map来用
3. 文件缓存
比如日志文件,就是文件系统的访问,把内容存在文件中。这个需求相对于key-value类的Map缓存来说要少的多,但也不是没有。
理论上来说,内容较多的时候,用key-value的Map是不合适的,存成文件更好一点。
-
插件path_provider是一个手机文件系统目录工具,比较有用,点赞数也很多:
文件目录工具
-
使用的时候,借助这个工具,封装一个文件读写的工具,需要的时候方便使用
/// 拿到存储路径
Future<String> getTemporaryDirectoryString()async{
final directory = await getTemporaryDirectory();
return directory.path;
}
Future<String> getApplicationDocumentsDirectoryString()async{
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<String> getExternalStorageDirectoryString()async{
final directory = await getExternalStorageDirectory();
return directory?.path ?? "";
}
// 创建对文件位置的引用
Future<File> get _localFile async {
final path = await getApplicationDocumentsDirectoryString();
return new File('$path/counter.txt');
}
// 将数据写入文件
Future<File> writeCounter(int counter) async {
final file = await _localFile;
// Write the file
return file.writeAsString('$counter');
}
// 从文件中读取数据
Future<int> readCounter() async {
try {
final file = await _localFile;
// Read the file
String contents = await file.readAsString();
return int.parse(contents);
} catch (e) {
// If we encounter an error, return 0
return 0;
}
}
4. 数据库
虽然一直反对在手机上用数据库,实际上在手机客户端上用数据库的场景还真不多,但是数据库是个基本功能,不用也需要备着。
-
没有可用的API接口,这里有一个插件sqflite;点赞数还是挺多的,基本上就是用它了。
点赞数
-
基本上都是SQL语句,还是要封装一下的,直接用不大合适
class MainDatabaseManager {
// 工厂模式
factory MainDatabaseManager() => _getInstance();
static MainDatabaseManager get instance => _getInstance();
static MainDatabaseManager? _instance;
static Database? tkDatabase;
static List<String> _nameList = [];
MainDatabaseManager._internal() {
// 初始化
}
static MainDatabaseManager _getInstance() {
if (_instance == null) {
_instance = new MainDatabaseManager._internal();
}
_instance!._createTable();
return _instance!;
}
///初始化数据库
initDB() {
_createTable();
}
///打开数据库,创建数据缓存表
_createTable() async {
var databasePath = await getDatabasesPath();
String path = join(databasePath, "tkDataBase.db");
///打开数据库
tkDatabase = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
/// 创建表
await db.execute(
"create table if not EXISTS TK_Main_Data (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,name TEXT UNIQUE,data BLOB)");
});
}
///操作事务Transaction
commitTransaction(Function(Transaction txn) action) async {
try {
await tkDatabase?.transaction((txn) async {
Batch batch = txn.batch();
await action(txn);
await batch.commit(noResult: true, continueOnError: true);
});
} catch (e) {
print('--- transaction failed -----');
}
}
/*
保存到数据库,区分当前用
- parameter name: 保存的名称(用于查询)
- parameter data: 数据
- parameter type: 类型
*/
_saveDataWith(
String name, bool containsMember, String data, Transaction txn) async {
String dataName = name + (containsMember ? UserStore().getMemberId() : "");
int id = await txn.rawInsert(
'INSERT or replace INTO TK_Main_Data(name,data) VALUES(?,?)',
[dataName, data]);
print('inserted2:$id');
}
///更新数据
updateData(
String name, bool containsMember, dynamic data, Transaction txn) async {
dynamic result = await selectData(name, containsMember, txn);
if (result == null || result == "") {
_nameList.add(name);
}
_saveDataWith(name, containsMember, data, txn);
}
/*
查询数据
- parameter name: 要查询的数据的名称
- parameter type: 类型
- returns: 返回的数据
*/
Future<dynamic> selectData(
String name, bool containsMember, Transaction txn) async {
String dataName = name + (containsMember ? UserStore().getMemberId() : "");
/// 查询
dynamic result = await txn
.rawQuery("select data from TK_Main_Data where name = '$dataName'");
if (result != null && result is List && result.length > 0) {
return result.first["data"] ?? "";
}
return "";
}
///退出登录时,删除与当前用户有关的缓存
deleteAllDataForMember(Transaction txn) async {
if (UserStore.instance.getMemberId() != "") {
for (String name in _nameList) {
String dataName =
name + UserStore.instance.getMemberId(); //与当前用户有关的,无关的不需要删除
await txn
.execute("delete from TK_Main_Data where name = (?)", [dataName]);
}
_nameList = [];
}
}
onDisposed() {
tkDatabase?.close();
}
}
5. 加密存储
印象中iOS中有Keychain也就是大名鼎鼎的钥匙串
这里也有一个插件flutter_secure_storage;点赞数也是很高的,用到的话,基本上就是它了。

网友评论