美文网首页
flutter插件源码分析--shared_preference

flutter插件源码分析--shared_preference

作者: RidingWind2023 | 来源:发表于2020-09-17 14:02 被阅读0次

    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来实现异步。

    相关文章

      网友评论

          本文标题:flutter插件源码分析--shared_preference

          本文链接:https://www.haomeiwen.com/subject/cknhyctx.html