簡介
在APP的開發(fā)中,本地緩存是常見的需求之一。iOS有flutter_secure_storage,Android有SharedPreferences,這些都是系統(tǒng)提供的API,可以直接使用。
1. shared_preferences
-
Flutter沒有提供系統(tǒng)的API來做本地緩存,社區(qū)出現(xiàn)了原生API的封裝插件shared_preferences;點贊數(shù)還是很多的
很受歡迎 這么受歡迎的插件一般都會用到,我們也選了這個,并且還在外面包了一層:
設(shè)置,獲取單例等方法是異步的,很不好用,經(jīng)過下面這樣包裝一下,在程序啟動的時候執(zhí)行一下LocalStorage.getInstance();其他的地方就當(dāng)做同步方法來用,只要不是set之后馬上get,一般問題不大。
一直不明白,為什么要把這種簡單的set和get緩存設(shè)計成異步的。
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;點贊數(shù)也還可以:
點贊數(shù) 像介紹中那樣當(dāng)做同步使用是可以的
GetStorage box = GetStorage();
box.write('quote', 'GetX is the best');
print(box.read('quote'));
- 不過本質(zhì)上,它的初始化和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();
}
- 命名空間,用名字區(qū)分storage;修改監(jiān)聽等功能只能算是錦上添花,算不上亮點功能。所以,目前大多數(shù)還是用了shared_preferences,用GetStorage的還是相對少數(shù)。
其實,相對來說GetStorage還是更簡單一些,可以當(dāng)做一個有命名空間的同步key vlaue本地緩存Map來用
3. 文件緩存
比如日志文件,就是文件系統(tǒng)的訪問,把內(nèi)容存在文件中。這個需求相對于key-value類的Map緩存來說要少的多,但也不是沒有。
理論上來說,內(nèi)容較多的時候,用key-value的Map是不合適的,存成文件更好一點。
-
插件path_provider是一個手機(jī)文件系統(tǒng)目錄工具,比較有用,點贊數(shù)也很多:
文件目錄工具 使用的時候,借助這個工具,封裝一個文件讀寫的工具,需要的時候方便使用
/// 拿到存儲路徑
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 ?? "";
}
// 創(chuàng)建對文件位置的引用
Future<File> get _localFile async {
final path = await getApplicationDocumentsDirectoryString();
return new File('$path/counter.txt');
}
// 將數(shù)據(jù)寫入文件
Future<File> writeCounter(int counter) async {
final file = await _localFile;
// Write the file
return file.writeAsString('$counter');
}
// 從文件中讀取數(shù)據(jù)
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. 數(shù)據(jù)庫
雖然一直反對在手機(jī)上用數(shù)據(jù)庫,實際上在手機(jī)客戶端上用數(shù)據(jù)庫的場景還真不多,但是數(shù)據(jù)庫是個基本功能,不用也需要備著。
-
沒有可用的API接口,這里有一個插件sqflite;點贊數(shù)還是挺多的,基本上就是用它了。
點贊數(shù) 基本上都是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!;
}
///初始化數(shù)據(jù)庫
initDB() {
_createTable();
}
///打開數(shù)據(jù)庫,創(chuàng)建數(shù)據(jù)緩存表
_createTable() async {
var databasePath = await getDatabasesPath();
String path = join(databasePath, "tkDataBase.db");
///打開數(shù)據(jù)庫
tkDatabase = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
/// 創(chuàng)建表
await db.execute(
"create table if not EXISTS TK_Main_Data (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,name TEXT UNIQUE,data BLOB)");
});
}
///操作事務(wù)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 -----');
}
}
/*
保存到數(shù)據(jù)庫,區(qū)分當(dāng)前用
- parameter name: 保存的名稱(用于查詢)
- parameter data: 數(shù)據(jù)
- 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');
}
///更新數(shù)據(jù)
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);
}
/*
查詢數(shù)據(jù)
- parameter name: 要查詢的數(shù)據(jù)的名稱
- parameter type: 類型
- returns: 返回的數(shù)據(jù)
*/
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 "";
}
///退出登錄時,刪除與當(dāng)前用戶有關(guān)的緩存
deleteAllDataForMember(Transaction txn) async {
if (UserStore.instance.getMemberId() != "") {
for (String name in _nameList) {
String dataName =
name + UserStore.instance.getMemberId(); //與當(dāng)前用戶有關(guān)的,無關(guān)的不需要刪除
await txn
.execute("delete from TK_Main_Data where name = (?)", [dataName]);
}
_nameList = [];
}
}
onDisposed() {
tkDatabase?.close();
}
}
5. 加密存儲
印象中iOS中有Keychain也就是大名鼎鼎的鑰匙串
這里也有一個插件flutter_secure_storage;點贊數(shù)也是很高的,用到的話,基本上就是它了。





