SharePreference原理及跨進(jìn)程數(shù)據(jù)共享的問題

SharedPreferences是Android提供的數(shù)據(jù)持久化的一種手段固以,適合單進(jìn)程、小批量的數(shù)據(jù)存儲與訪問嘱巾。為什么這么說呢憨琳?因?yàn)镾haredPreferences的實(shí)現(xiàn)是基于單個(gè)xml文件實(shí)現(xiàn)的,并且旬昭,所有持久化數(shù)據(jù)都是一次性加載到內(nèi)存篙螟,如果數(shù)據(jù)過大,是不合適采用SharedPreferences存放的问拘。而適用的場景是單進(jìn)程的原因同樣如此遍略,由于Android原生的文件訪問并不支持多進(jìn)程互斥,所以SharePreferences也不支持骤坐,如果多個(gè)進(jìn)程更新同一個(gè)xml文件绪杏,就可能存在同不互斥問題,后面會(huì)詳細(xì)分析這幾個(gè)問題纽绍。

SharedPreferences的實(shí)現(xiàn)原理之:持久化數(shù)據(jù)的加載

首先蕾久,從基本使用簡單看下SharedPreferences的實(shí)現(xiàn)原理:

    mSharedPreferences = context.getSharedPreferences("test", Context.MODE_PRIVATE);
    SharedPreferences.Editor editor = mSharedPreferences.edit();
    editor.putString(key, value);
    editor.apply();

context.getSharedPreferences其實(shí)就是簡單的調(diào)用ContextImpl的getSharedPreferences,具體實(shí)現(xiàn)如下

       @Override
public SharedPreferences getSharedPreferences(String name, int mode) {
    SharedPreferencesImpl sp;
    synchronized (ContextImpl.class) {
        if (sSharedPrefs == null) {
            sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
        }

        final String packageName = getPackageName();
        ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
        if (packagePrefs == null) {
            packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
            sSharedPrefs.put(packageName, packagePrefs);
        }
        sp = packagePrefs.get(name);
        if (sp == null) {
        <!--讀取文件-->
            File prefsFile = getSharedPrefsFile(name);
            sp = new SharedPreferencesImpl(prefsFile, mode);
            <!--緩存sp對象-->
            packagePrefs.put(name, sp);
            return sp;
        }
    }
    <!--跨進(jìn)程同步問題-->
    if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
        getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
        sp.startReloadIfChangedUnexpectedly();
    }
    return sp;
}

以上代碼非常簡單拌夏,直接描述下來就是先去內(nèi)存中查詢與xml對應(yīng)的SharePreferences是否已經(jīng)被創(chuàng)建加載僧著,如果沒有那么該創(chuàng)建就創(chuàng)建履因,該加載就加載,在加載之后盹愚,要將所有的key-value保存到內(nèi)幕才能中去栅迄,當(dāng)然,如果首次訪問皆怕,可能連xml文件都不存在毅舆,那么還需要?jiǎng)?chuàng)建xml文件,與SharePreferences對應(yīng)的xml文件位置一般都在/data/data/包名/shared_prefs目錄下愈腾,后綴一定是.xml朗兵,數(shù)據(jù)存儲樣式如下

sp對應(yīng)的xml數(shù)據(jù)存儲模型

這里面數(shù)據(jù)的加載的地方需要看下,比如顶滩,SharePreferences數(shù)據(jù)的加載是同步還是異步?數(shù)據(jù)加載是new SharedPreferencesImpl對象時(shí)候開始的寸爆,

 SharedPreferencesImpl(File file, int mode) {
    mFile = file;
    mBackupFile = makeBackupFile(file);
    mMode = mode;
    mLoaded = false;
    mMap = null;
    startLoadFromDisk();
}

startLoadFromDisk很簡單礁鲁,就是讀取xml配置,如果其他線程想要在讀取之前就是用的話赁豆,就會(huì)被阻塞仅醇,一直wait等待,直到數(shù)據(jù)讀取完成魔种。

    private void loadFromDiskLocked() {
   ...
    Map map = null;
    StructStat stat = null;
    try {
        stat = Os.stat(mFile.getPath());
        if (mFile.canRead()) {
            BufferedInputStream str = null;
            try {
    <!--讀取xml中配置-->
                str = new BufferedInputStream(
                        new FileInputStream(mFile), 16*1024);
                map = XmlUtils.readMapXml(str);
            }...
    mLoaded = true;
    ...
    <!--喚起其他等待線程-->
    notifyAll();
}

可以看到其實(shí)就是直接使用xml解析工具XmlUtils析二,直接在當(dāng)前線程讀取xml文件,所以节预,如果xml文件稍大叶摄,盡量不要在主線程讀取,讀取完成之后安拟,xml中的配置項(xiàng)都會(huì)被加載到內(nèi)存蛤吓,再次訪問的時(shí)候,其實(shí)訪問的是內(nèi)存緩存糠赦。

SharedPreferences的實(shí)現(xiàn)原理之:持久化數(shù)據(jù)的更新

通常更新SharedPreferences的時(shí)候是首先獲取一個(gè)SharedPreferences.Editor会傲,利用它緩存一批操作,之后當(dāng)做事務(wù)提交拙泽,有點(diǎn)類似于數(shù)據(jù)庫的批量更新:

    SharedPreferences.Editor editor = mSharedPreferences.edit();
    editor.putString(key1, value1);
    editor.putString(key2, value2);
    editor.putString(key3, value3);
    editor.apply();//或者commit

Editor是一個(gè)接口淌山,這里的實(shí)現(xiàn)是一個(gè)EditorImpl對象,它首先批量預(yù)處理更新操作顾瞻,之后再提交更新泼疑,在提交事務(wù)的時(shí)候有兩種方式,一種是apply朋其,另一種commit王浴,兩者的區(qū)別在于:何時(shí)將數(shù)據(jù)持久化到xml文件脆炎,前者是異步的,后者是同步的氓辣。Google推薦使用前一種秒裕,因?yàn)椋蛦芜M(jìn)程而言钞啸,只要保證內(nèi)存緩存正確就能保證運(yùn)行時(shí)數(shù)據(jù)的正確性几蜻,而持久化,不必太及時(shí)体斩,這種手段在Android中使用還是很常見的梭稚,比如權(quán)限的更新也是這樣,況且絮吵,Google并不希望SharePreferences用于多進(jìn)程弧烤,因?yàn)椴话踩窒驴ㄒ幌耡pply與commit的區(qū)別

    public void apply() {
    <!--添加到內(nèi)存-->
        final MemoryCommitResult mcr = commitToMemory();
        final Runnable awaitCommit = new Runnable() {
                public void run() {
                    try {
                        mcr.writtenToDiskLatch.await();
                    } catch (InterruptedException ignored) {
                    }
                }
            };

        QueuedWork.add(awaitCommit);
        Runnable postWriteRunnable = new Runnable() {
                public void run() {
                    awaitCommit.run();
                    QueuedWork.remove(awaitCommit);
                }
            };
        <!--延遲寫入到xml文件-->
        SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
        <!--通知數(shù)據(jù)變化-->
        notifyListeners(mcr);
    }
 
 public boolean commit() {
        MemoryCommitResult mcr = commitToMemory();
        SharedPreferencesImpl.this.enqueueDiskWrite(
            mcr, null /* sync write on this thread okay */);
        try {
            mcr.writtenToDiskLatch.await();
        } catch (InterruptedException e) {
            return false;
        }
        notifyListeners(mcr);
        return mcr.writeToDiskResult;
    }     

從上面可以看出兩者最后都是先調(diào)用commitToMemory蹬敲,將更改提交到內(nèi)存暇昂,在這一點(diǎn)上兩者是一致的,之后又都調(diào)用了enqueueDiskWrite進(jìn)行數(shù)據(jù)持久化任務(wù)伴嗡,不過commit函數(shù)一般會(huì)在當(dāng)前線程直接寫文件急波,而apply則提交一個(gè)事務(wù)到已給線程池,之后直接返回瘪校,實(shí)現(xiàn)如下:

 private void enqueueDiskWrite(final MemoryCommitResult mcr,
                              final Runnable postWriteRunnable) {
    final Runnable writeToDiskRunnable = new Runnable() {
            public void run() {
                synchronized (mWritingToDiskLock) {
                    writeToFile(mcr);
                }
                synchronized (SharedPreferencesImpl.this) {
                    mDiskWritesInFlight--;
                }
                if (postWriteRunnable != null) {
                    postWriteRunnable.run();
                }
            }
        };
   final boolean isFromSyncCommit = (postWriteRunnable == null);
    if (isFromSyncCommit) {
        boolean wasEmpty = false;
        synchronized (SharedPreferencesImpl.this) {
            wasEmpty = mDiskWritesInFlight == 1;
        }
        <!--如果沒有其他線程在寫文件澄暮,直接在當(dāng)前線程執(zhí)行-->
        if (wasEmpty) {
            writeToDiskRunnable.run();
            return;
        }
    }
   QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}

不過如果有線程在寫文件,那么就不能直接寫阱扬,這個(gè)時(shí)候就跟apply函數(shù)一致了泣懊,但是,如果直觀說兩者的區(qū)別的話麻惶,直接說commit同步嗅定,而apply異步應(yīng)該也是沒有多大問題的

SharePreferences多進(jìn)程使用問題

SharePreferences在新建的有個(gè)mode參數(shù)用踩,可以指定它的加載模式渠退,MODE_MULTI_PROCESS是Google提供的一個(gè)多進(jìn)程模式,但是這種模式并不是我們說的支持多進(jìn)程同步更新等脐彩,它的作用只會(huì)在getSharedPreferences的時(shí)候碎乃,才會(huì)重新從xml重加載,如果我們在一個(gè)進(jìn)程中更新xml惠奸,但是沒有通知另一個(gè)進(jìn)程梅誓,那么另一個(gè)進(jìn)程的SharePreferences是不會(huì)自動(dòng)更新的。

@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
    SharedPreferencesImpl sp;
    ...
    if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
        getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
        // If somebody else (some other process) changed the prefs
        // file behind our back, we reload it.  This has been the
        // historical (if undocumented) behavior.
        sp.startReloadIfChangedUnexpectedly();
    }
    return sp;
}

也就是說MODE_MULTI_PROCESS只是個(gè)雞肋Flag,對于多進(jìn)程的支持幾乎為0梗掰,下面是Google文檔嵌言,簡而言之,就是:不要用及穗。

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摧茴。

響應(yīng)的Google為多進(jìn)程提供了一個(gè)數(shù)據(jù)同步互斥方案,那就是基于Binder實(shí)現(xiàn)的ContentProvider埂陆,關(guān)于ContentProvider后文分析苛白。

總結(jié)

  • SharePreferences是Android基于xml實(shí)現(xiàn)的一種數(shù)據(jù)持久話手段
  • SharePreferences不支持多進(jìn)程
  • SharePreferences的commit與apply一個(gè)是同步一個(gè)是異步(大部分場景下)
  • 不要使用SharePreferences存儲太大的數(shù)據(jù)

作者:看書的小蝸牛
原文鏈接:SharePreference原理及跨進(jìn)程數(shù)據(jù)共享的問題
僅供參考,歡迎指正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末焚虱,一起剝皮案震驚了整個(gè)濱河市购裙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鹃栽,老刑警劉巖躏率,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異民鼓,居然都是意外死亡禾锤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門摹察,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人倡鲸,你說我怎么就攤上這事供嚎。” “怎么了峭状?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵克滴,是天一觀的道長。 經(jīng)常有香客問我优床,道長劝赔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任胆敞,我火速辦了婚禮着帽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘移层。我一直安慰自己仍翰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布观话。 她就那樣靜靜地躺著予借,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上灵迫,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天秦叛,我揣著相機(jī)與錄音,去河邊找鬼瀑粥。 笑死挣跋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的利凑。 我是一名探鬼主播浆劲,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼哀澈!你這毒婦竟也來了牌借?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤割按,失蹤者是張志新(化名)和其女友劉穎膨报,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體适荣,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡现柠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弛矛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片够吩。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丈氓,靈堂內(nèi)的尸體忽然破棺而出周循,到底是詐尸還是另有隱情,我是刑警寧澤万俗,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布湾笛,位于F島的核電站,受9級特大地震影響闰歪,放射性物質(zhì)發(fā)生泄漏嚎研。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一库倘、第九天 我趴在偏房一處隱蔽的房頂上張望临扮。 院中可真熱鬧,春花似錦教翩、人聲如沸公条。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽靶橱。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間关霸,已是汗流浹背传黄。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留队寇,地道東北人膘掰。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像佳遣,于是被迫代替她去往敵國和親识埋。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

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