美文网首页
Flutter之本地缓存 2024-06-28 周五

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

作者: 勇往直前888 | 来源:发表于2024-06-27 15:28 被阅读0次

简介

在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;点赞数也是很高的,用到的话,基本上就是它了。

参考文章

Flutter中的几种本地持久化存储

相关文章

网友评论

      本文标题:Flutter之本地缓存 2024-06-28 周五

      本文链接:https://www.haomeiwen.com/subject/qeqecjtx.html