原生应用集成 Flutter SharedPreferences 的正确姿势

SharedPreferences 是什么?

Andorid 开发的同学对 SharedPreferences 应该不陌生,在键值对中存储私有原始数据,适用于一些轻量的数据存储。

为什么要使用 SharedPreferences ?

有这样的开发场景:一些单独的业务用 flutter 写成 module 嵌入到已有的 iOS & Android 原生 App 中,flutter 端需要拿到一些配置信息,如用户登录后的 Authorization token, 当前 App 版本号, 以及该设备 UDID 等。所以需要由 Native(iOS & Android) 将这些配置信息传递给 Flutter 使用。

  • 一种方法是, 使用 MethodChannel;

  • 一种是让 Native 端将配置存入缓存中,Flutter 直接读取缓存,Flutter 官方就提供了 shared_preferences 组件库, 封装了 iOS 的 NSUserDefaults 和 Android 的 SharedPreferences, 可以持久化简单的数据,不过 README 中说到

Neither platform can guarantee that writes will be persisted to disk after returning and this plugin must not be used for storing critical data.

不保证返回后数据一定会持久化到磁盘,并且这个插件不建议拿来存储关键信息。

那么 Authorization 存在 NSUserDefaults/SharedPreferences 是否安全,先按下不表,后文再续,刚起来先。

如果有如此类似场景,即需要在 Native 和 Flutter 代码间通过NSUserDefaults/SharedPreferences 存储查询数据, 那下面或多或少可以帮到你。

Flutter 使用 SharedPreferences 的注意项

1. Native 存储数据的 key 前缀需要要加 flutter.

前期嵌入 iOS 时,发现获取 X-Client-Type 始终为 null,但打印 getKeys() 发现存在{X-Client-Type} 这个 key, 那么问题出在哪儿呢?

SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.getString('X-Client-Type');
print(prefs.getKeys());
  • 没有读取到X-Client-Type的值,但 xcode 断点调试之后发现的确已经存入了的;
  • 是不是在获取过程中做了什么妖,查看 Flutter SharedPreferences 源代码,果然 GET/SET 时添加了前缀 flutter., 在 Andorid & iOS 存数据时, key 加上前缀 flutter. 即可搞定,如下所示:
SharedPreferencesUtil.setString("flutter.X-Client-Type", "android");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"ios" forKey:@"flutter.X-Client-Type"];
_prefix
定义 _prefix='flutter.'
1
所有 key 带有前缀 _prefix
image.png
读取所有key前缀为 'flutter.'的值

2. Android 需指定 SharedPreferences 文件名称为 FlutterSharedPreferences

在前端视角中,Android 的 SharedPreferences 类似 localStorage 或 NSUserDefaults 一样应是独一份供全局调用,只能说 too young! Android 小哥草草把一堆数据扔进 SharedPreferences, 但却再 flutter 中始终读取不到,且 getKeys()也什么也没有打出来。 但在 flutter 里 set 的数据却可以读到。再一次让我怀疑人生,然后问了下我司前 Senior Android Developer,只道一句:

是不是存储文件是不同的?

一语惊醒梦中人,打开 Android Studio 右下角的 Device File Explorer, 如果你没有找到,请将头右转 90°,或者将显示器左转 90°,便能看见, 找到data/data 目录,打开当前安卓项目的包名目录,再打开 shared_prefs, 你会看见一堆 xml 文件。

image.png

读写不是同一个文件,自然读不到咯,再查看下 Flutter SharedPreferences 插件源代码,也印证了这一点。

image.png

Java/Kotlin 代码改起来:

private static SharedPreferences getInstance( ) {
    return Application.getInstance().getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE);
 }

一些思考

在使用 Flutter Plugin 还是应该去查看 lib 下 dart 代码如何实现,再去查看对应的 iOS & Androrid 代码如何实现,相信看过 Flutter SharedPreferences Plugin 源代码的同学,也已经知道其底层也是局域 MethodChannel 实现的。

这篇文章主要也是为了提醒其他同学在使用 Flutter 的 SharedPreferences plugin 时需要注意的一些小细节,以及如何正确使用 Flutter 的 plugin。

细细想来将 API 所需要的一些信息存在 SharedPreferences/NSUserDefaults 中并不是最佳实践,那什么是最佳呢,且听下回分解。

后续可以再详细剖析 Flutter shared_preferences plugin 的源代码以及 Android 的 SharedPreferences 和 iOS 的 NSUserDefaults 应用场景、区别等。

Reference