流行框架源碼分析(6)-多進程的sharedprefrence解決方案DPreference

主目錄見:Android高級進階知識(這是總目錄索引)
?我們都知道sharedpreference在使用的時候是不支持多進程操作數(shù)據(jù)的,不同進程間操作數(shù)據(jù)的讀取,存取或者并發(fā)操作數(shù)據(jù)都會出現(xiàn)問題,所以我們需要自己去控制跨進程操作,現(xiàn)在我們看看官方文檔對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就已經(jīng)廢棄了,MODE_MULTI_PROCESS在一些android版本中不能穩(wěn)定運行,而且不提供任何機制來記錄跨進程的修改,不建議應(yīng)用使用他。作為替代,應(yīng)該使用更加明確的跨進程數(shù)據(jù)管理辦法比如ContentProvider。所以這里我們必須尋求一個更好的解決這個問題的辦法,我們今天這里選擇ContentProvider+sharedpreference的辦法即框架[DPreference],當然還有框架ContentProvider+sqLite的[Tray],但是看驗證性能不理想,而且需要升級相關(guān)問題,所以我們這里選擇DPreference來講解,好啦,看了這么多放松一下。。。

清醒一下

一.目標

?今天這篇文章也是我在用跨進程框架時候遇到需要存儲數(shù)據(jù),然后多個進程操作的時候遇到的問題,其實有很多種辦法解決,但是這種方法我覺得是比較方便的。學(xué)習(xí)今天的文章有幾個目標:
1.復(fù)習(xí)ContentProvider的使用,因為這個平常用的確實沒有很多;
2.可以多一個多進程存取數(shù)據(jù)的解決方案。

二.源碼分析

首先還是跟其他源碼的入手點一樣,我們先來看看最基本的使用方法,其實這個使用方法跟shareprefrence是一模一樣的:

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

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

我們看到確實用法跟shareprefrence無異,甚至更簡單有沒有,不用commit。現(xiàn)在我們先從存儲開始講解。

1.存儲 setPrefString

首先我們從DPreference的構(gòu)造函數(shù)開始看:

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

我們看到構(gòu)造函數(shù)啥也沒有,只是簡單地賦值一下,那么我們來看此類的setPrefString方法吧:

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

這個方法里面又調(diào)用了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給外部訪問,具體的細節(jié)相信大家知道了,如果不知道我這里推薦一篇文章[Android開發(fā)之內(nèi)容提供者——創(chuàng)建自己的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);
    }

我們看到這里代碼會根據(jù)type來獲取對應(yīng)的Uri,到這里我們已經(jīng)獲取到了Uri,我們就可以用ContentResolver調(diào)用ContentProvider里面相應(yīng)的方法了,我們看到我們這里的setString()方法最后調(diào)用了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;
    }

這個方法其實就是要保存我們設(shè)置進來的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);
    }

其中g(shù)etPathSegments方法的getPathSegments得到uri的path部分,并拆分,去掉"/",取到第一個元素(從第0個開始),
//比如:
content://"+FirstProvierMetaData.AUTHORIY+"/users /1"
//getPathSegments()得到的是users 和1,get(1)會得到1

所以這里的get(1)得到的name(就是對應(yīng)于DPreference(context, "default")這里面的default,也就是這個DPreference的名字),get(2)就是獲取到key就是setString()方法里面的key,然后會匹配Uri調(diào)用相應(yīng)方法,我們字符串會匹配到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);
    }

我們這里看到會根據(jù)name來獲取到一個對應(yīng)的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對應(yīng)的PreferenceImpl對象,如果不存在則保存,然后返回PreferenceImpl對象,接著就是調(diào)用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();
    }

我們看到方法最終還是調(diào)用了sharedpreference的方法,非常的簡單,到這里我們的存儲方法已經(jīng)完畢了,是不是很簡單,其實的確是很簡單。

2.獲取 getPrefString

上面已經(jīng)講完怎么存了,就是利用ContentProvider機制,但是最終還是用shareprefrence進行存儲,那么我們這里取肯定最終也是從shareprefrence中獲取的嘛,我們開始驗證:

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

這里套路是一樣的,也是調(diào)用的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調(diào)用了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, 而此時又沒有數(shù)據(jù)庫返回一個Cursor,此時可以通過MatrixCursor來返回一個Cursor,因為我們這里底層是用sharedpreference,不是用的sqlite,所以我們要返回搜索的結(jié)果Cursor,我們就只能用這個類了。我們程序匹配的時候還是會調(diào)用到preferenceToCursor(當然在調(diào)用之前是會判斷key在不在的這個name對應(yīng)的sharedpreference中),我們首先看到preferenceToCursor里面的參數(shù)就是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);
    }

我們看到這里調(diào)用了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,就可以像操作數(shù)據(jù)庫返回的Cursor那樣了。到這里我們的數(shù)據(jù)也就取完畢了。整個流程是比較容易的,就是ContentProvider的基本知識。
總結(jié):這篇文章主要就是利用ContentProvider來包裝SharedPereference來操作key和value的值,整體來說是比較簡單,但是這是一個解決問題的辦法,還是很不錯的,希望大家有g(shù)et到技能。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內(nèi)容