flutter_cache_manager本地缓存之CacheInfoRepository、CacheObjectProvider源码解析(二)

479 阅读8分钟

CacheInfoRepository抽象类

CacheInfoRepository是一个用于存储和检索缓存信息的抽象接口,CacheManager使用它来存储和管理缓存信息。

exists

/// Returns whether or not there is an existing data file with cache info.
Future<bool> exists();

该方法用于检查是否存在缓存信息的数据文件。如果存在,返回true,否则返回false

open

/// Opens the repository, or just returns true if the repo is already open.
Future<bool> open();

这个函数打开缓存信息存储库,或者如果存储库已经打开则直接返回true。

updateOrInsert

/// Updates a given [CacheObject], if it exists, or adds a new item to the repository
Future<dynamic> updateOrInsert(CacheObject cacheObject);

该方法用于更新给定的CacheObject,如果它存在于仓库中,则更新,否则添加新项到仓库。它返回一个动态对象,表示是否成功更新或插入。

insert

/// Inserts [cacheObject] into the repository
Future<CacheObject> insert(CacheObject cacheObject,
    {bool setTouchedToNow = true});

该函数将一个缓存对象插入到仓库中,如果该缓存对象已经存在于仓库中,则该函数不会执行任何操作。如果setTouchedToNow参数被设置为true(默认值),则该函数还会将缓存对象的touched属性设置为当前时间。该函数会返回该缓存对象。

get

/// Gets a [CacheObject] by [key]
Future<CacheObject?> get(String key);

这是一个异步方法,接受一个字符串类型的参数 key,用于查找相应的 CacheObject 对象。如果找到则返回该对象,否则返回 null

delete

/// Deletes a cache object by [id]
Future<int> delete(int id);

这个方法是用来通过 ID 删除缓存对象的,返回值是一个 Future<int>,表示删除的对象个数。

deleteAll

/// Deletes items with [ids] from the repository
Future<int> deleteAll(Iterable<int> ids);

该方法用于从存储库中删除多个缓存对象,参数ids是一个Iterable<int>,包含要删除的所有缓存对象的ID。该方法返回一个Future<int>,表示删除的缓存对象的数量。

update

/// Updates an existing [cacheObject]
Future<int> update(CacheObject cacheObject, {bool setTouchedToNow = true});

这个方法用于更新已经存在的缓存对象,传入要更新的 CacheObject 对象,如果需要将最后访问时间(touchedTime)设置为当前时间,则可以将 setTouchedToNow 参数设置为 true。返回一个 Future 对象,表示更新操作的结果,如果成功更新则返回 1,否则返回 0。

getAllObjects

/// Gets the list of all objects in the cache
Future<List<CacheObject>> getAllObjects();

该函数用于从缓存中获取所有的 CacheObject 对象,并以 List 的形式返回。可以用于展示所有已缓存的对象,或者用于其他的操作,比如删除所有对象。

getObjectsOverCapacity

/// Gets the list of [CacheObject] that can be removed if the repository is over capacity.
///
/// The exact implementation is up to the repository, but implementations should
/// return a preferred list of items. For example, the least recently accessed
Future<List<CacheObject>> getObjectsOverCapacity(int capacity);

获取可以从仓库中删除的 [CacheObject] 列表,如果仓库容量超过限制。

具体的实现取决于仓库,但实现应该返回首选项的项目列表。例如,最近最少访问的项目。

getOldObjects

/// Returns a list of [CacheObject] that are older than [maxAge]
Future<List<CacheObject>> getOldObjects(Duration maxAge);

该函数返回一个列表,其中包含所有创建时间早于 [maxAge] 的 [CacheObject] 对象。

close

/// Close the connection to the repository. If this is the last connection
/// to the repository it will return true and the repository is trully
/// closed. If there are still open connections it will return false;
Future<bool> close();

该方法关闭与存储库的连接。如果这是对存储库的最后一个连接,则它将返回true,并且存储库确实关闭。如果仍有打开的连接,则它将返回false。

deleteDataFile

/// Deletes the cache data file including all cache data.
Future<void> deleteDataFile();

该方法用于删除缓存数据文件及其中所有缓存数据。

CacheObjectProvider(继承CacheInfoRepository,也是一个抽象类)

class CacheObjectProvider extends CacheInfoRepository
    with CacheInfoRepositoryHelperMethods
/// Either the path or the database name should be provided.
/// If the path is provider it should end with '{databaseName}.db',
/// for example: /data/user/0/com.example.example/databases/imageCache.db
CacheObjectProvider({String? path, this.databaseName}) : _path = path;

CacheObjectProvider是一个抽象类,它定义了获取和存储缓存对象的方法,包括从缓存中检索、更新、删除缓存对象等。它可以作为一个接口,提供给不同的缓存库来实现。

该类是一个抽象类,用于提供访问CacheObject数据的接口,具体实现应该由子类来完成。它包含了一个可选的path属性和一个可选的databaseName属性,path属性可以是缓存文件的路径,也可以为空;如果提供了path属性,则必须以'{databaseName}.db'结尾。如果未提供path属性,则必须提供databaseName属性。

open

Future<bool> open() async {
  if (!shouldOpenOnNewConnection()) {
    return openCompleter!.future;
  }
  var path = await _getPath();
  await File(path).parent.create(recursive: true);
  db = await openDatabase(path, version: 3,
      onCreate: (Database db, int version) async {
    await db.execute('''
    create table $_tableCacheObject (
      ${CacheObject.columnId} integer primary key,
      ${CacheObject.columnUrl} text,
      ${CacheObject.columnKey} text,
      ${CacheObject.columnPath} text,
      ${CacheObject.columnETag} text,
      ${CacheObject.columnValidTill} integer,
      ${CacheObject.columnTouched} integer,
      ${CacheObject.columnLength} integer
      );
      create unique index $_tableCacheObject${CacheObject.columnKey}
      ON $_tableCacheObject (${CacheObject.columnKey});
    ''');
  }, onUpgrade: (Database db, int oldVersion, int newVersion) async {
    // Migration for adding the optional key, does the following:
    // Adds the new column
    // Creates a unique index for the column
    // Migrates over any existing URLs to keys
    if (oldVersion <= 1) {
      var alreadyHasKeyColumn = false;
      try {
        await db.execute('''
          alter table $_tableCacheObject
          add ${CacheObject.columnKey} text;
          ''');
      } on DatabaseException catch (e) {
        if (!e.isDuplicateColumnError(CacheObject.columnKey)) rethrow;
        alreadyHasKeyColumn = true;
      }
      await db.execute('''
        update $_tableCacheObject
          set ${CacheObject.columnKey} = ${CacheObject.columnUrl}
          where ${CacheObject.columnKey} is null;
        ''');

      if (!alreadyHasKeyColumn) {
        await db.execute('''
          create index $_tableCacheObject${CacheObject.columnKey}
            on $_tableCacheObject (${CacheObject.columnKey});
          ''');
      }
    }
    if (oldVersion <= 2) {
      try {
        await db.execute('''
      alter table $_tableCacheObject
      add ${CacheObject.columnLength} integer;
      ''');
      } on DatabaseException catch (e) {
        if (!e.isDuplicateColumnError(CacheObject.columnLength)) rethrow;
      }
    }
  });
  return opened();
}

该方法是打开数据库连接的异步方法。它首先检查是否应该在新连接上打开数据库。如果不是,则返回以前保存的打开连接的 future。否则,该方法将获取数据库路径并在必要时创建该路径,然后使用 openDatabase 方法打开数据库。如果需要,该方法会在数据库打开后调用 opened 方法。

在创建数据库时,该方法使用 SQL 语句创建了一个名为 _tableCacheObject 的表,其中包含了 CacheObject 的各个属性,包括 id、url、key、path、ETag、validTill、touched 和 length。同时,它还创建了一个名为 _tableCacheObject${CacheObject.columnKey} 的唯一索引,确保每个 key 值都是唯一的。如果数据库版本更新,它会执行一些升级操作,例如添加新的 key 列、创建索引等。

最后,该方法返回一个布尔值表示数据库是否成功打开。如果打开的是以前保存的连接,则直接返回以前保存的打开连接的 future。

updateOrInsert

@override
Future<dynamic> updateOrInsert(CacheObject cacheObject) {
  if (cacheObject.id == null) {
    return insert(cacheObject);
  } else {
    return update(cacheObject);
  }
}

该方法用于更新或插入一个缓存对象。如果传入的缓存对象已经有一个 ID(即 cacheObject.id 不为 null),则会调用 update 方法进行更新。否则,会调用 insert 方法进行插入。该方法返回一个 Future 对象,表示更新或插入操作的结果。

insert

@override
Future<CacheObject> insert(CacheObject cacheObject,
    {bool setTouchedToNow = true}) async {
  var id = await db!.insert(
    _tableCacheObject,
    cacheObject.toMap(setTouchedToNow: setTouchedToNow),
  );
  return cacheObject.copyWith(id: id);
}

这段代码是一个实现了CacheObjectProvider接口中的insert方法的具体实现。它接收一个CacheObject对象作为参数,并将它插入到数据库中的_tableCacheObject表中。如果setTouchedToNow参数设置为true,则会在CacheObject对象中设置touched时间戳。最后,它返回一个包含新插入的对象id的CacheObject对象的副本。

get

@override
Future<CacheObject?> get(String key) async {
  List<Map> maps = await db!.query(_tableCacheObject,
      columns: null, where: '${CacheObject.columnKey} = ?', whereArgs: [key]);
  if (maps.isNotEmpty) {
    return CacheObject.fromMap(maps.first.cast<String, dynamic>());
  }
  return null;
}

该方法实现了根据缓存键获取对应缓存对象的功能。它通过调用query方法查询数据库表中是否存在对应缓存键的数据,如果查询结果不为空,则返回查询结果中的第一条数据对应的CacheObject对象;否则返回null。其中columns参数为null表示返回所有列的数据,where参数指定了查询条件,即根据缓存键进行查询,whereArgs参数则指定了where中占位符所对应的值。最终,通过调用CacheObject.fromMap方法将查询结果转换成CacheObject对象并返回。

delete

@override
Future<int> delete(int id) {
  return db!.delete(_tableCacheObject,
      where: '${CacheObject.columnId} = ?', whereArgs: [id]);
}

这段代码实现了CacheObjectProvider接口中的delete方法。这个方法接收一个整数id参数,表示要删除的CacheObject的id。方法内部使用SQLite的delete方法来删除相应的行,该方法返回一个Future,表示被删除的行数。

deleteAll

@override
Future<int> deleteAll(Iterable<int> ids) {
  return db!.delete(_tableCacheObject,
      where: '${CacheObject.columnId} IN (' + ids.join(',') + ')');
}

该方法用于删除数据库中多个CacheObject对象。它接收一个int类型的可迭代对象ids作为参数,表示要删除的CacheObject对象的id集合。它使用SQLite的delete方法从数据库中删除满足条件的对象。其中where参数使用IN操作符和ids的集合创建,表示只删除id在ids集合中的对象。函数返回一个Future,它会在完成时返回一个int类型的值,表示被删除的对象数量。

update

@override
Future<int> update(CacheObject cacheObject, {bool setTouchedToNow = true}) {
  return db!.update(
    _tableCacheObject,
    cacheObject.toMap(setTouchedToNow: setTouchedToNow),
    where: '${CacheObject.columnId} = ?',
    whereArgs: [cacheObject.id],
  );
}

该函数是CacheObjectProvider类中的一个实现CacheStore接口的方法,用于更新给定的缓存对象。

该方法需要传入一个CacheObject类型的参数cacheObject,同时也可以传入一个布尔类型的参数setTouchedToNow。如果setTouchedToNowtrue,则会在缓存对象的lastAccess字段中记录当前时间戳。接着,该方法会调用update()方法来更新缓存对象在数据库中的对应记录。update()方法需要传入一个map类型的参数来更新对应的字段值,而这个map类型的参数是通过CacheObject对象的toMap()方法生成的。toMap()方法将CacheObject对象转换为一个map,以方便存储到数据库中。

该方法会返回一个Future类型的结果,表示更新操作是否成功完成。返回的值是一个表示更新行数的整数。

getAllObjects

@override
Future<List<CacheObject>> getAllObjects() async {
  return CacheObject.fromMapList(
    await db!.query(_tableCacheObject, columns: null),
  );
}

该函数的作用是从数据库中获取所有的 CacheObject,即获取数据库中存储的所有缓存对象。它会执行一个查询操作,查询所有的列,然后将查询结果转换成 CacheObject 列表并返回。

getOldObjects

@override
Future<List<CacheObject>> getOldObjects(Duration maxAge) async {
  return CacheObject.fromMapList(await db!.query(
    _tableCacheObject,
    where: '${CacheObject.columnTouched} < ?',
    columns: null,
    whereArgs: [DateTime.now().subtract(maxAge).millisecondsSinceEpoch],
    limit: 100,
  ));
}

该方法用于获取所有缓存对象中超时的对象,即 CacheObject.columnTouched 时间早于 maxAge 的对象列表。它接受一个 Duration 类型的参数 maxAge,表示允许的最大时间差,以毫秒为单位。

在实现上,它会使用 db!.query() 方法查询表 _tableCacheObject,并使用 where 参数指定筛选条件为 ${CacheObject.columnTouched} < ?,即最后访问时间早于 maxAge 的对象。查询结果将转换为 CacheObject 对象列表并返回。

如果查询结果过多,方法还接受一个 limit 参数,可以指定返回的最大行数。在这种情况下,查询将按最后访问时间的升序排序,并返回前 limit 行。

close

@override
Future<bool> close() async {
  if (!shouldClose()) return false;
  await db!.close();
  return true;
}

该方法实现了关闭数据库连接的功能。如果数据库已经关闭或没有打开,则返回false,否则返回true。

deleteDataFile

@override
Future deleteDataFile() async {
  await _getPath();
}

exists(数据库文件是否存在)

@override
Future<bool> exists() async {
  final path = await _getPath();
  return File(path).exists();
}

说明:获取数据文件的路径,然后检查对应的文件是否存在。返回一个Future<bool>类型,表示数据文件是否存在。

_getPath(获取数据库文件路径)

Future<String> _getPath() async {
  Directory directory;
  if (_path != null) {
    directory = File(_path!).parent;
  } else {
    directory = (await getApplicationSupportDirectory());
  }
  await directory.create(recursive: true);
  if (_path == null || !_path!.endsWith('.db')) {
    _path = join(directory.path, '$databaseName.db');
  }
  await _migrateOldDbPath(_path!);
  return _path!;
}

该函数的功能是获取数据库文件的路径。首先,它会检查是否已经设置了文件路径,如果设置了,则获取该路径的父目录。如果没有设置,则获取应用程序支持目录并将其作为根目录。接着,它创建该目录(如果不存在),然后检查文件路径是否以".db"结尾。如果没有,则使用数据库名称创建新路径。最后,它通过调用 _migrateOldDbPath 函数迁移旧的数据库路径(如果存在)。最终,该函数返回数据库文件的路径。

_migrateOldDbPath

// Migration for pre-V2 path on iOS and macOS
Future _migrateOldDbPath(String newDbPath) async {
  final oldDbPath = join((await getDatabasesPath()), '$databaseName.db');
  if (oldDbPath != newDbPath && await File(oldDbPath).exists()) {
    try {
      await File(oldDbPath).rename(newDbPath);
    } on FileSystemException {
      // If we can not read the old db, a new one will be created.
    }
  }
}

该方法实现了从旧的数据库路径迁移到新的数据库路径的功能。在 iOS 和 macOS 上,Flutter 的 sqflite 插件的默认数据库路径在应用程序的文档目录下,路径形式为“/path/to/app/Documents/app.db”。在插件的版本 V2 中,数据库路径默认被更改为应用程序的应用支持目录下,路径形式为“/path/to/app/Library/Application Support/app.db”。

如果在应用程序中使用了版本 V1 的 sqflite 插件,那么默认的数据库路径将是旧的路径“/path/to/app/Documents/app.db”。如果应用程序在升级到版本 V2 后需要继续使用旧的路径,则可以调用此方法,该方法会将旧的数据库路径迁移到新的路径。

该方法首先获取旧的数据库路径,如果旧的路径与新的路径不同且旧的路径存在,则将旧的数据库路径重命名为新的路径。如果在重命名过程中出现了错误,则会在新路径上创建一个新的数据库。