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來實現非同步。
如果你覺得這篇文章對你有益,還請幫忙轉發和點贊,萬分感謝。