流行框架源码分析(6)-多进程的sharedprefrence解决方案DPreference

主目录见:Android高级进阶知识(这是总目录索引)
 我们都知道sharedpreference在使用的时候是不支持多进程操作数据的,不同进程间操作数据的读取,存取或者并发操作数据都会出现问题,所以我们需要自己去控制跨进程操作,现在我们看看官方文档对shareprefrence中MODE_MULTI_PROCESS的描述:

int MODE_MULTI_PROCESS
This constant was deprecated in API level 23.
MODE_MULTI_PROCESS does not work reliably in some versions of Android, and furthermore does not provide any mechanism for reconciling concurrent modifications across processes. Applications should not attempt to use it. Instead, they should use an explicit cross-process data management approach such as ContentProvider.

这段英文的意思就是这个常量在api 23就已经废弃了,MODE_MULTI_PROCESS在一些android版本中不能稳定运行,而且不提供任何机制来记录跨进程的修改,不建议应用使用他。作为替代,应该使用更加明确的跨进程数据管理办法比如ContentProvider。所以这里我们必须寻求一个更好的解决这个问题的办法,我们今天这里选择ContentProvider+sharedpreference的办法即框架[DPreference],当然还有框架ContentProvider+sqLite的[Tray],但是看验证性能不理想,而且需要升级相关问题,所以我们这里选择DPreference来讲解,好啦,看了这么多放松一下。。。

《流行框架源码分析(6)-多进程的sharedprefrence解决方案DPreference》 清醒一下

一.目标

 今天这篇文章也是我在用跨进程框架时候遇到需要存储数据,然后多个进程操作的时候遇到的问题,其实有很多种办法解决,但是这种方法我觉得是比较方便的。学习今天的文章有几个目标:
1.复习ContentProvider的使用,因为这个平常用的确实没有很多;
2.可以多一个多进程存取数据的解决方案。

二.源码分析

首先还是跟其他源码的入手点一样,我们先来看看最基本的使用方法,其实这个使用方法跟shareprefrence是一模一样的:

 DPreference dPreference = new DPreference(context, "default");
dPreference.setPrefString( "key", "value");

 DPreference dPreference = new DPreference(context, "default");
dPreference.getPrefString( "key");

我们看到确实用法跟shareprefrence无异,甚至更简单有没有,不用commit。现在我们先从存储开始讲解。

1.存储 setPrefString

首先我们从DPreference的构造函数开始看:

  public DPreference(Context context, String name) {
        this.mContext = context;
        this.mName = name;
    }

我们看到构造函数啥也没有,只是简单地赋值一下,那么我们来看此类的setPrefString方法吧:

    public void setPrefString(final String key, final String value) {
        PrefAccessor.setString(mContext, mName, key, value);
    }

这个方法里面又调用了PrefAccessor类的setString方法,我们跟进去看下:

   public static void setString(Context context, String name, String key, String value) {
        Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_STRING);
        ContentValues cv = new ContentValues();
        cv.put(PreferenceProvider.PREF_KEY, key);
        cv.put(PreferenceProvider.PREF_VALUE, value);
        context.getContentResolver().update(URI, cv, null, null);
    }

我们看到这里面是典型的访问ContentProvider的方法,这里我们看下PreferenceProvider中的buildUri方法。PreferenceProvider是个ContentProvider对象,我们首先看下Uri是什么:

  private static final String AUTHORITY = "me.dozen.dpreference.PreferenceProvider";

    public static final String CONTENT_PREF_BOOLEAN_URI = "content://" + AUTHORITY + "/boolean/";
    public static final String CONTENT_PREF_STRING_URI = "content://" + AUTHORITY + "/string/";
    public static final String CONTENT_PREF_INT_URI = "content://" + AUTHORITY + "/integer/";
    public static final String CONTENT_PREF_LONG_URI = "content://" + AUTHORITY + "/long/";


    public static final String PREF_KEY = "key";
    public static final String PREF_VALUE = "value";

    public static final int PREF_BOOLEAN = 1;
    public static final int PREF_STRING = 2;
    public static final int PREF_INT = 3;
    public static final int PREF_LONG = 4;

    private static final UriMatcher sUriMatcher;

    static {
        sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        sUriMatcher.addURI(AUTHORITY, "boolean/*/*", PREF_BOOLEAN);
        sUriMatcher.addURI(AUTHORITY, "string/*/*", PREF_STRING);
        sUriMatcher.addURI(AUTHORITY, "integer/*/*", PREF_INT);
        sUriMatcher.addURI(AUTHORITY, "long/*/*", PREF_LONG);

    }

我们看到这个就是我们ContentProvider典型的做法,暴露几个Uri给外部访问,具体的细节相信大家知道了,如果不知道我这里推荐一篇文章[Android开发之内容提供者——创建自己的ContentProvider(详解) ],相信看了大家就会明白的。然后我们来看buildUri方法:

  public static Uri buildUri(String name, String key, int type) {
        return Uri.parse(getUriByType(type) + name + "/" + key);
    }

    private static String getUriByType(int type) {
        switch (type) {
            case PreferenceProvider.PREF_BOOLEAN:
                return PreferenceProvider.CONTENT_PREF_BOOLEAN_URI;
            case PreferenceProvider.PREF_INT:
                return PreferenceProvider.CONTENT_PREF_INT_URI;
            case PreferenceProvider.PREF_LONG:
                return PreferenceProvider.CONTENT_PREF_LONG_URI;
            case PreferenceProvider.PREF_STRING:
                return PreferenceProvider.CONTENT_PREF_STRING_URI;
        }
        throw new IllegalStateException("unsupport preftype : " + type);
    }

我们看到这里代码会根据type来获取对应的Uri,到这里我们已经获取到了Uri,我们就可以用ContentResolver调用ContentProvider里面相应的方法了,我们看到我们这里的setString()方法最后调用了ContentProvider的update()方法,所以我们进一步就是看这个PreferenceProvider的update()方法:

 @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        PrefModel model = getPrefModelByUri(uri);
        if(model == null) {
            throw new IllegalArgumentException("update prefModel is null");
        }
        switch (sUriMatcher.match(uri)) {
            case PREF_BOOLEAN:
                persistBoolean(model.getName(), values);
                break;
            case PREF_LONG:
                persistLong(model.getName(), values);
                break;
            case PREF_STRING:
                persistString(model.getName(), values);
                break;
            case PREF_INT:
                persistInt(model.getName(), values);
                break;
            default:
                throw new IllegalStateException("update unsupported uri : " + uri);
        }
        return 0;
    }

这个方法其实就是要保存我们设置进来的value,我们先看这个方法最开始会查找PrefModel的对象,所以我们看下这个getPrefModelByUri()方法:

 private PrefModel getPrefModelByUri(Uri uri) {
        if (uri == null || uri.getPathSegments().size() != 3) {
            throw new IllegalArgumentException("getPrefModelByUri uri is wrong : " + uri);
        }
        String name = uri.getPathSegments().get(1);
        String key = uri.getPathSegments().get(2);
        return new PrefModel(name, key);
    }

其中getPathSegments方法的getPathSegments得到uri的path部分,并拆分,去掉”/”,取到第一个元素(从第0个开始),
//比如:
content://”+FirstProvierMetaData.AUTHORIY+”/users /1″
//getPathSegments()得到的是users 和1,get(1)会得到1

所以这里的get(1)得到的name(就是对应于DPreference(context, “default”)这里面的default,也就是这个DPreference的名字),get(2)就是获取到key就是setString()方法里面的key,然后会匹配Uri调用相应方法,我们字符串会匹配到persistString()方法:

    private void persistString(String name, ContentValues values) {
        if (values == null) {
            throw new IllegalArgumentException(" values is null!!!");
        }
        String kString = values.getAsString(PREF_KEY);
        String vString = values.getAsString(PREF_VALUE);
        getDPreference(name).setPrefString(kString, vString);
    }

我们这里看到会根据name来获取到一个对应的shareprefrence,具体我们看getDPreference方法:

    private IPrefImpl getDPreference(String name) {
        if (TextUtils.isEmpty(name)) {
            throw new IllegalArgumentException("getDPreference name is null!!!");
        }
        if (sPreferences.get(name) == null) {
            IPrefImpl pref = new PreferenceImpl(getContext(), name);
            sPreferences.put(name, pref);
        }
        return sPreferences.get(name);
    }

我们看到这里会先在sPreferences(private static Map<String, IPrefImpl> sPreferences = new ArrayMap<>())中获取到这个name对应的PreferenceImpl对象,如果不存在则保存,然后返回PreferenceImpl对象,接着就是调用setPrefString()方法,那么这个方法就是PreferenceImpl的setPrefString方法:

  public void setPrefString(final String key, final String value) {
        final SharedPreferences settings =
                mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
        settings.edit().putString(key, value).apply();
    }

我们看到方法最终还是调用了sharedpreference的方法,非常的简单,到这里我们的存储方法已经完毕了,是不是很简单,其实的确是很简单。

2.获取 getPrefString

上面已经讲完怎么存了,就是利用ContentProvider机制,但是最终还是用shareprefrence进行存储,那么我们这里取肯定最终也是从shareprefrence中获取的嘛,我们开始验证:

  public String getPrefString(final String key, final String defaultValue) {
        return PrefAccessor.getString(mContext, mName, key, defaultValue);
    }

这里套路是一样的,也是调用的PrefAccessor里面的getString方法:

 public static String getString(Context context, String name, String key, String defaultValue) {
        Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_STRING);
        String value = defaultValue;
        Cursor cursor = context.getContentResolver().query(URI, null, null, null, null);
        if (cursor != null && cursor.moveToFirst()) {
            value = cursor.getString(cursor.getColumnIndex(PreferenceProvider.PREF_VALUE));
        }
        IOUtils.closeQuietly(cursor);
        return value;
    }

我们看到第一句也是获取Uri,这个跟前面讲过的是一样的,这里就不赘述,然后我们看利用ContentResolver调用了query方法,我们就看ContentProvider的query方法到底做了啥:

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        MatrixCursor cursor = null;
        PrefModel model = getPrefModelByUri(uri);
        switch (sUriMatcher.match(uri)) {
            case PREF_BOOLEAN:
                if (getDPreference(model.getName()).hasKey(model.getKey())) {
                    cursor = preferenceToCursor(getDPreference(model.getName()).getPrefBoolean(model.getKey(), false) ? 1 : 0);
                }
                break;
            case PREF_STRING:
                if (getDPreference(model.getName()).hasKey(model.getKey())) {
                    cursor = preferenceToCursor(getDPreference(model.getName()).getPrefString(model.getKey(), ""));
                }
                break;
            case PREF_INT:
                if (getDPreference(model.getName()).hasKey(model.getKey())) {
                    cursor = preferenceToCursor(getDPreference(model.getName()).getPrefInt(model.getKey(), -1));
                }
                break;
            case PREF_LONG:
                if (getDPreference(model.getName()).hasKey(model.getKey())) {
                    cursor = preferenceToCursor(getDPreference(model.getName()).getPrefLong(model.getKey(), -1));
                }
                break;
        }
        return cursor;
    }

首先看上面这段代码之前,我们需要知道一个概念,那就是MatrixCursor类的作用,如果想得到一个Cursor, 而此时又没有数据库返回一个Cursor,此时可以通过MatrixCursor来返回一个Cursor,因为我们这里底层是用sharedpreference,不是用的sqlite,所以我们要返回搜索的结果Cursor,我们就只能用这个类了。我们程序匹配的时候还是会调用到preferenceToCursor(当然在调用之前是会判断key在不在的这个name对应的sharedpreference中),我们首先看到preferenceToCursor里面的参数就是getDPreference(model.getName()).getPrefString(model.getKey(), “”)获取得到的,我们首先看下PreferenceImpl的getPrefString方法:

  public String getPrefString(final String key,
                                final String defaultValue) {
        final SharedPreferences settings =
                mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
        return settings.getString(key, defaultValue);
    }

我们看到这里调用了sharedpreference来获取值,然后传给preferenceToCursor方法:

private <T> MatrixCursor preferenceToCursor(T value) {
        MatrixCursor matrixCursor = new MatrixCursor(PREFERENCE_COLUMNS, 1);
        MatrixCursor.RowBuilder builder = matrixCursor.newRow();
        builder.add(value);
        return matrixCursor;
    }

然后我们看到这个value被添加进MatrixCursor 的对象中,然后返回这个Cursor,就可以像操作数据库返回的Cursor那样了。到这里我们的数据也就取完毕了。整个流程是比较容易的,就是ContentProvider的基本知识。
总结:这篇文章主要就是利用ContentProvider来包装SharedPereference来操作key和value的值,整体来说是比较简单,但是这是一个解决问题的办法,还是很不错的,希望大家有get到技能。

    原文作者:ZJ_Rocky
    原文地址: https://www.jianshu.com/p/39e5651f0ea8
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞