流行框架源码分析(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来讲解,好啦,看了这么多放松一下。。。

清醒一下

一.目标

 今天这篇文章也是我在用跨进程框架时候遇到需要存储数据,然后多个进程操作的时候遇到的问题,其实有很多种办法解决,但是这种方法我觉得是比较方便的。学习今天的文章有几个目标:
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到技能。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容