Flutter之本地緩存 2024-06-28 周五

簡介

在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ù)也是很高的,用到的話,基本上就是它了。

參考文章

Flutter中的幾種本地持久化存儲

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容