Android上最基本的一个存储方式就是SharedPreferences,flutter上也有一个基于sp的插件。
插件地址
该插件封装了NSUserDefaults
(IOS)和SharedPreferences
(Android),由于数据是异步存储到磁盘,不能保证在你return之后就生效,
所以尽量不要使用这个插件存储一些关键性数据。
既然是要分析源码,首先先把基本用法奉上。
基本用法
在项目的pubspec.yaml
文件中,添加以下内容:
dependencies:
shared_preferences: ^0.5.3+4
然后执行 packages get
。接下来新建一个dart文件,贴入如下代码:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
body: Center(
child: RaisedButton(
onPressed: _incrementCounter,
child: Text('Increment Counter'),
),
),
),
));
}
_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int counter = (prefs.getInt('counter') ?? 0) + 1;
print('Pressed $counter times.');
await prefs.setInt('counter', counter);
}
运行,点击屏幕中心按钮,会看到如下打印:
I/flutter (30837): Pressed 1 times.
I/flutter (30837): Pressed 2 times.
...
源码分析
好了,以上就是sp的用法了,是不是很简单? _
接下来一起看下源码,首先是获取sp的实例:
SharedPreferences prefs = await SharedPreferences.getInstance();//注意,await必须在async修饰的函数中使用,表示异步
对应的源码如下:
const MethodChannel _kChannel =
MethodChannel('plugins.flutter.io/shared_preferences');
class SharedPreferences {
//缓存data,会跟SharedPreferences或者NSUserDefaults通过setter方法保持数据同步
final Map<String, Object> _preferenceCache;
//_表示构造函数私有,这里是dart的实现单例的一种写法
SharedPreferences._(this._preferenceCache);
static const String _prefix = 'flutter.';
static SharedPreferences _instance;
//单例获取sp
static Future<SharedPreferences> getInstance() async {
if (_instance == null) {
final Map<String, Object> preferencesMap =
await _getSharedPreferencesMap();//这里异步获取map
_instance = SharedPreferences._(preferencesMap);
}
return _instance;
}
static Future<Map<String, Object>> _getSharedPreferencesMap() async {
final Map<String, Object> fromSystem =
await _kChannel.invokeMapMethod<String, Object>('getAll');//这里对应的是native的方法调用,对应的实现类是SharedPreferencesPlugin。
assert(fromSystem != null);
// Strip the flutter. prefix from the returned preferences.
final Map<String, Object> preferencesMap = <String, Object>{};
for (String key in fromSystem.keys) {
assert(key.startsWith(_prefix));
preferencesMap[key.substring(_prefix.length)] = fromSystem[key];
}
return preferencesMap;
}
...
}
MethodChannel
可用作native和flutter进行沟通,详细可看我的另一篇文章。
传送门
上面注释中对应的SharedPreferencesPlugin部门源码如下:
//MethodCallHandler 是 MethodChannel收到flutter端调用时回调的接口
public class SharedPreferencesPlugin implements MethodCallHandler {
private static final String SHARED_PREFERENCES_NAME = "FlutterSharedPreferences";
private final android.content.SharedPreferences preferences;
private SharedPreferencesPlugin(Context context) {
preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
}
//flutter 端调用都会到这里
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
switch (call.method) {
...
case "getAll":
result.success(getAllPrefs());
return;
case "setInt"://注意这里,待会会讲,先暂时略过
Number number = call.argument("value");
if (number instanceof BigInteger) {
BigInteger integerValue = (BigInteger) number;
commitAsync(
preferences
.edit()
.putString(
key, BIG_INTEGER_PREFIX + integerValue.toString(Character.MAX_RADIX)),
result);
} else {
commitAsync(preferences.edit().putLong(key, number.longValue()), result);
}
break;
...
}
...
}
//启用asynctask来执行sp的提交逻辑并通知flutter
private void commitAsync(final Editor editor, final MethodChannel.Result result) {
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... voids) {
return editor.commit();
}
@Override
protected void onPostExecute(Boolean value) {
result.success(value);
}
}.execute();
}
private Map<String, Object> getAllPrefs() throws IOException {
Map<String, ?> allPrefs = preferences.getAll();
Map<String, Object> filteredPrefs = new HashMap<>();
for (String key : allPrefs.keySet()) {
if (key.startsWith("flutter.")) {//过滤出来flutter打头的内容然后返回
...
}
...
return filteredPrefs;
}
到这里总结一下,flutter中的sp在初始化时候,从native端拿到了所有flutter存进去的data,并传递给实例 Map<String, Object> _preferenceCache
,最终拿到了flutter端SharedPreferences的实例。
接下来看一下如何获取指定类型数据以及如何设置数据,获取prefs.getInt('counter')
,设置prefs.setInt('counter', counter)
对应源码如下:
//SharedPreferences.dart源码
/// Reads a value from persistent storage, throwing an exception if it's not a
/// bool.
bool getBool(String key) => _preferenceCache[key];
/// Reads a value from persistent storage, throwing an exception if it's not
/// an int.
int getInt(String key) => _preferenceCache[key];//直接从缓存中获取内容
/// Reads a value from persistent storage, throwing an exception if it's not a
/// double.
double getDouble(String key) => _preferenceCache[key];
/// Reads a value from persistent storage, throwing an exception if it's not a
/// String.
String getString(String key) => _preferenceCache[key];
//设置数据
Future<bool> setInt(String key, int value) => _setValue('Int', key, value);
Future<bool> _setValue(String valueType, String key, Object value) {
final Map<String, dynamic> params = <String, dynamic>{
'key': '$_prefix$key',
};//这里的prefix其实就是static const String _prefix = 'flutter.',呼应前面看到的getAll
if (value == null) {
_preferenceCache.remove(key);
return _kChannel
.invokeMethod<bool>('remove', params)
.then<bool>((dynamic result) => result);
} else {
if (value is List<String>) {
// Make a copy of the list so that later mutations won't propagate
_preferenceCache[key] = value.toList();
} else {
_preferenceCache[key] = value;
}
params['value'] = value;//更新map内容
return _kChannel
.invokeMethod<bool>('set$valueType', params)//同步到native端执行setInt方法,这里可以回过头来看SharedPreferencesPlugin.java的`onMethodCall`中的`setInt`方法。
.then<bool>((dynamic result) => result);
}
}
至此,flutter的sp源码已经基本分析完毕。总结一下:
- 使用MethodChannel进行native和flutter的通讯
- 在flutter sp初始化时候获取了native端 name为 FlutterSharedPreferences的sp中的所有以
flutter.
作为前缀的data数据,并缓存到map - getXXX时直接使用map返回
- setXXX时通过methodchannel通知native端更新sp,并更新flutter中map缓存。native更新sp使用AsyncTask来实现异步。
网友评论