Flutter 全平台 | 从 shared_preferences 聊聊六端插件


theme: cyanosis

用过 Flutter 的小伙伴应该对 shared_preferences 并不陌生,它支持 六大平台,用于存储键值对,并以 xml 文件的形式将数据进行持久化。这种功能的实现会依赖各个平台的能力,而且功能点并不复杂。所以它是一个非常好的六端插件 研究对象。

image.png


1. 项目结构

进入 shared_preferences 插件源码中,可以看到它并非是一个简单的插件项目。而是: - 统一接口 shared_preferences_platform_interface; - 每个平台给出自己的实现包,比如安卓端通过 shared_preferences_android 实现; - 通过 shared_preferences 库整合各个平台包的功能。

image.png

这样对于开发者,既可以独立维护和发展每个平台的类库。对于使用者,又可以基于 shared_preferences 一个库来访问所有子系统的功能。是一种非常好的项目结构。


2. 依赖关系

通过各个类库的 pubspec.yaml,可以查看他们之间的依赖关系。如下所示,shared_preferences 库依赖了其他的五个分库:

image.png

在分库中,会依赖 sharedpreferencesplatform_interface 接口,对接口中定义的抽象功能进行具体实现。

image.png

通过下面的简图,可以更好的理解这六个类库之间的关系:

image.png


3. 接口包 sharedpreferencesplatform_interface

sharedpreferencesplatform_interface 包中定义了 SharedPreferencesStorePlatform 接口,其中声明了一些抽象方法:

image.png

并且通过静态变量 instance 得到 SharedPreferencesStorePlatform 实例对象,注意这里并非单例模式,首先它没有私有化构造;其次它可以通过 set 方法设置其他实例。可以看出,默认的实例是 MethodChannelSharedPreferencesStore 对象。

```dart abstract class SharedPreferencesStorePlatform extends PlatformInterface { SharedPreferencesStorePlatform() : super(token: _token);

static final Object _token = Object();

static SharedPreferencesStorePlatform get instance => _instance;

static set instance(SharedPreferencesStorePlatform instance) { if (!instance.isMock) { PlatformInterface.verify(instance, _token); } _instance = instance; }

static SharedPreferencesStorePlatform _instance = MethodChannelSharedPreferencesStore(); ```

其中 MethodChannelSharedPreferencesStore 必然是 SharedPreferencesStorePlatform 的实现类。可以看出定义了 MethodChannel 全局常量作为平台共同的渠道方法,在具体实现中通过 MethodChannel#invokeMethod 来触发平台方法:

image.png


3. windows 和 linux 平台的功能实现

windows 和 linux 平台本身并没有 xml 配置文件的写入和读取工具。所以对于这两个平台,会通过 shared_preferences.json 来存储数据,实现 SharedPreferencesStorePlatform 中定义的存取等接口功能:

| windows | linux | | --- | --- | | image.png|image.png |

下面是 windows 和 linux 对于 SharedPreferencesStorePlatform 的实现。可以看出在 registerWith 方法中,会将 SharedPreferencesStorePlatform 接口中的静态实例设为当前类对象:

```dart ---->[windows]---- class SharedPreferencesWindows extends SharedPreferencesStorePlatform { static void registerWith() { SharedPreferencesStorePlatform.instance = SharedPreferencesWindows(); } static const String _defaultPrefix = 'flutter.'; }

---->[linux]---- class SharedPreferencesLinux extends SharedPreferencesStorePlatform { static const String _defaultPrefix = 'flutter.'; static void registerWith() { SharedPreferencesStorePlatform.instance = SharedPreferencesLinux(); } ```

下面代码可以看出 windows 和 linux 会将配置文件放在 getApplicationSupportPath 之下,名称为 shared_preferences.json : ```dart ---->[windows]---- Future getLocalDataFile() async { if (localDataFilePath != null) { return localDataFilePath!; } final String? directory = await pathProvider.getApplicationSupportPath(); if (directory == null) { return null; } return _localDataFilePath = fs.file(path.join(directory, 'sharedpreferences.json')); }

---->[linux]---- Future getLocalDataFile() async { final String? directory = await pathProvider.getApplicationSupportPath(); if (directory == null) { return null; } return fs.file(path.join(directory, 'sharedpreferences.json')); } ```

拿设置值来说,会先通过 _readPreferences 得到 map 对象,然后添加键值对。通过 _writePreferences 将新的 map 对象写入到文件中:

```dart @override Future setValue(String valueType, String key, Object value) async { final Map preferences = await _readPreferences(); preferences[key] = value; return _writePreferences(preferences); }

Future _writePreferences(Map preferences) async { try { final File? localDataFile = await _getLocalDataFile(); if (localDataFile == null) { debugPrint('Unable to determine where to write preferences.'); return false; } if (!localDataFile.existsSync()) { localDataFile.createSync(recursive: true); } final String stringMap = json.encode(preferences); localDataFile.writeAsStringSync(stringMap); } catch (e) { debugPrint('Error saving preferences to disk: $e'); return false; } return true; } ```

最后看一下 windows 中 pubspec.yaml 的声明。它在 futter 节点下增加了 plugin 节点,来描述当前插件包。另外 sharedpreferenceswindows 是一个独立的包,他可以依赖其他的类库。比如这里的 path_provider_windows, 用于获取路径。 Linux 也是类似的:

image.png


4. shared_preferences 库

shared_preferences 是面向开发者的类库,其中提供了我们日常开发中所用到的所有方法。他通过 SharedPreferences._ 私有化构造,通过 getInstance 获取 SharedPreferences 实例。

```dart class SharedPreferences { SharedPreferences.(this.preferenceCache);

static Future getInstance() async { if ( completer == null) { final Completer completer = Completer (); _completer = completer; try { final Map preferencesMap = await _getSharedPreferencesMap(); completer.complete(SharedPreferences. (preferencesMap)); } catch (e) { completer.completeError(e); final Future sharedPrefsFuture = completer.future; _completer = null; return sharedPrefsFuture; } } return _completer!.future; } ```

该实例的核心是 _preferenceCache 映射数据,在构造时会作为入参。比如 getString 方法会从 _preferenceCache 中检索对应的值; setString 会更新 _preferenceCache 映射关系,并通过 _store 存储值。而这个 _store 正是各个平台提供的数据访问接口 SharedPreferencesStorePlatform

image.png

```dart final Map _preferenceCache;

String? getString(String key) => _preferenceCache[key] as String?;

Future setString(String key, String value) => _setValue('String', key, value);

Future setValue(String valueType, String key, Object value) { ArgumentError.checkNotNull(value, 'value'); final String prefixedKey = '$prefix$key'; if (value is List ) { _preferenceCache[key] = value.toList(); } else { _preferenceCache[key] = value; } return _store.setValue(valueType, prefixedKey, value); } ```

最后看一下 shared_preferences 中的 pubspec.yaml 文件。在 flutter 节点下对各个平台的类库实现进行描述。可以看出 iOS 和 Macos 都是通过 shared_preferences_foundation 首先的:

image.png


5. Android、iOS、MacOS 平台

Android、iOS、MacOS 平台有相关的 xml 配置数据的存取功能。比如 Android 中使用 SharedPreferences 对象,这也是该库名称的由来:

image.png

iOS、MacOS 平台的提供的功能是一样的,代码在 shared_preferences_foundation 中。可以看到是通过 UserDefaults 进行数据持久化的。

image.png

最后说一下,这三个平台涉及到渠道方法来沟通原生平台,这里使用了 pigeons 工具自动生成相关代码,这一点以后有机会再开一篇细讲一下:

image.png


6. Web 平台

最后看一下 Web 平台的实现,其中依赖了 web 类库:

image.png

作为存储的实现层,web 平台肯定也需要实现 SharedPreferencesStorePlatform 的接口功能。

image.png

从设置和存储值可以看出 web 平台是基于 localStorage 实现的:

image.png


到这里,shared_preferences 六端的插件的结构就已经分析完毕了。以后自己需要编写多平台插件也可以按照这种结构。每个类库职责分离,通过一个类库集成各个分库的功能。那本文就到这里,谢谢观看 ~

  • 25
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值