SharedPreference源碼分析

最近在復習规肴,發(fā)現(xiàn)了關于多線程 多進程的問題镊折,面試中常問的涉及到了SharedPreference的知識焕盟,決定去看看源碼秋秤,到底是如何實現(xiàn)的。這里就不介紹它的用法和一些基礎知識了脚翘,直接開始源碼分析灼卢。

源碼解析

ContextImpl#getSharedPreferences

這里主要是看context.getSharedPreferences()方法,context的實現(xiàn)類是ContextImpl来农。

public SharedPreferences getSharedPreferences(String name, int mode) {
    if (mPackageInfo.getApplicationInfo().targetSdkVersion <
            Build.VERSION_CODES.KITKAT) {
        if (name == null) {
            name = "null";
        }
    }
    File file;
    synchronized (ContextImpl.class) {
        if (mSharedPrefsPaths == null) {
            mSharedPrefsPaths = new ArrayMap<>();
        }
        //根據(jù)文件名獲取文件
        file = mSharedPrefsPaths.get(name);
        文件名為null,創(chuàng)建文件鞋真,并存入mSharedPrefsPaths集合中
        if (file == null) {
            file = getSharedPreferencesPath(name);
            mSharedPrefsPaths.put(name, file);
        }
    }
    //調用下面的方法
    return getSharedPreferences(file, mode);
}
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
    SharedPreferencesImpl sp;
    synchronized (ContextImpl.class) {
        // 獲取用于緩存的cache對象 
        cache = getSharedPreferencesCacheLocked();
        sp = cache.get(file);
        if (sp == null) {
            //檢查mode類型
            checkMode(mode);
            if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
                if (isCredentialProtectedStorage()
                        && !getSystemService(UserManager.class)
                                .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
                    throw new IllegalStateException("SharedPreferences in credential encrypted "
                            + "storage are not available until after user is unlocked");
                }
            }
            //可以看出SharedPreferencesImpl是SharedPreferences的實現(xiàn)類
            sp = new SharedPreferencesImpl(file, mode);
            cache.put(file, sp);
            return sp;
        }
    }
    if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
        getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
        //如果是多進程文件需要重新讀取文件
        sp.startReloadIfChangedUnexpectedly();
    }
    return sp;
}

private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
    //若第一次調用該方法,則對全局變量sSharedPrefsCache進行初始化
    if (sSharedPrefsCache == null) {
        sSharedPrefsCache = new ArrayMap<>();
    }
    //獲取包名
    final String packageName = getPackageName();
    //根據(jù)包名獲取packagePrefs
    ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
    //創(chuàng)建packagePrefs沃于,并放入緩存中
    if (packagePrefs == null) {
        packagePrefs = new ArrayMap<>();
        sSharedPrefsCache.put(packageName, packagePrefs);
    }
    return packagePrefs;
}

SharedPreferencesImpl

構造方法

SharedPreferencesImpl(File file, int mode) {
    mFile = file;
    //file的備份文件
    mBackupFile = makeBackupFile(file);
    mMode = mode;
    mLoaded = false;
    mMap = null;
    mThrowable = null;
    //從硬盤中加載數(shù)據(jù)
    startLoadFromDisk();
}

startLoadFromDisk()

private void startLoadFromDisk() {
    synchronized (mLock) {
        mLoaded = false;
    }
    //創(chuàng)建并啟動了一個名為“SharedPreferencesImpl-load”的線程
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            //從硬盤中加載數(shù)據(jù)
            loadFromDisk();
        }
    }.start();
}

loadFromDisk()

private void loadFromDisk() {
    synchronized (mLock) {
        //前面出現(xiàn)過涩咖,用于判斷是否從硬盤中加載過,是則返回
        if (mLoaded) {
            return;
        }
        //判斷備份文件是否存在
        if (mBackupFile.exists()) {
            //刪除源文件
            mFile.delete();
            //將備份文件中的內容復制到源文件繁莹,作了替換
            mBackupFile.renameTo(mFile);
        }
    }
    
    ...省略部分代碼
    //用于臨時保存磁盤文件中加載解析后的鍵值對數(shù)據(jù)
    Map<String, Object> map = null;
    //用于保存文件的信息
    StructStat stat = null;
    //用于保存加載數(shù)據(jù)中產(chǎn)生的異常
    Throwable thrown = null;
    try {
        //獲取文件的信息
        stat = Os.stat(mFile.getPath());
        //文件可讀檩互,則創(chuàng)建緩存輸入流
        if (mFile.canRead()) {
            BufferedInputStream str = null;
            try {
                str = new BufferedInputStream(
                        new FileInputStream(mFile), 16 * 1024);
                //將文件中的數(shù)據(jù)解析成鍵值對,保存在map中
                map = (Map<String, Object>) XmlUtils.readMapXml(str);
            } catch (Exception e) {
                Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
            } finally {
                //關閉緩存輸入流
                IoUtils.closeQuietly(str);
            }
        }
    } catch (ErrnoException e) {
       
    } catch (Throwable t) {
        thrown = t;
    }
    synchronized (mLock) {
        //設置狀態(tài)為加載完成
        mLoaded = true;
        mThrowable = thrown;
        try {
            if (thrown == null) {
                if (map != null) {
                    //將結果保存在全局變量中
                    mMap = map;
                    mStatTimestamp = stat.st_mtim;
                    mStatSize = stat.st_size;
                } else {
                    //若解析的數(shù)據(jù)為空咨演,即文件中沒有數(shù)據(jù)闸昨,對全局變量進行初始化
                    mMap = new HashMap<>();
                }
            }
        } catch (Throwable t) {
            mThrowable = t;
        } finally {
            //釋放鎖,隨機喚醒一個等待mLock鎖的線程
            mLock.notifyAll();
        }
    }
}

從SharedPreferences中獲取數(shù)據(jù)

以getString為例

getString

public String getString(String key, @Nullable String defValue) {
    synchronized (mLock) {
        // 讓當前的線程等待從磁盤加載數(shù)據(jù)的線程加載完數(shù)據(jù)
        awaitLoadedLocked();
        // 從全局變量mMap中獲取數(shù)據(jù)
        String v = (String)mMap.get(key);
        // 如果獲取到數(shù)據(jù),就返回數(shù)據(jù)饵较,否則返回方法參數(shù)中給定的默認值
        return v != null ? v : defValue;
    }
}

awaitLoadedLocked

private void awaitLoadedLocked() {
    // 如果從硬盤加載數(shù)據(jù)沒有完成
    if (!mLoaded) {
       //調用線程處理策略
        BlockGuard.getThreadPolicy().onReadFromDisk();
    }
    // 如果從硬盤加載數(shù)據(jù)沒有完成拍嵌,則循環(huán)執(zhí)行
    while (!mLoaded) {
        try {
            //釋放鎖等待
            mLock.wait();
        } catch (InterruptedException unused) {
        }
    }
    //若等待期間發(fā)生異常,則拋出異常
    if (mThrowable != null) {
        throw new IllegalStateException(mThrowable);
    }
}

寫數(shù)據(jù)

SharedPreferences.Editor

@Override
public Editor edit() {
    // 同步鎖循诉,鎖對象為mLock
    synchronized (mLock) {
    // 讓當前的線程等待從磁盤加載數(shù)據(jù)的線程加載完數(shù)據(jù)
    awaitLoadedLocked();
    }
    // 創(chuàng)建EditorImpl對象并返回
    return new EditorImpl();
}

EditorImpl類

Editor是一個接口横辆,它的具體實現(xiàn)類是EditorImpl類。EditorImpl類是SharedPreferencesImpl類的內部類茄猫,SharedPreferences中對數(shù)據(jù)的增 刪 改都是通過調用Editor的相關方法實現(xiàn)的龄糊。

putString

private final Map mModified = new HashMap();// 用于臨時存儲需要寫入磁盤的數(shù)據(jù)或需要移除的數(shù)據(jù),之后統(tǒng)一處理

@Override
public Editor putString(String key, @Nullable String value) {
    // 同步鎖募疮,鎖對象為mEditorLock
    synchronized (mEditorLock) {
    // 向全局變量mModified添加數(shù)據(jù)
    mModified.put(key, value);
    // 返回
    return this;
    }
}

提交數(shù)據(jù)到磁盤

commit()

public boolean commit() {
    long startTime = 0;
    if (DEBUG) {
        startTime = System.currentTimeMillis();
    }
    //對EditorImpl對象的操作(put remove clear等)進行整合處理
    MemoryCommitResult mcr = commitToMemory();
    //將數(shù)據(jù)寫入磁盤
    SharedPreferencesImpl.this.enqueueDiskWrite(
        mcr, null /* sync write on this thread okay */);
    try {
        //阻塞等待寫入過程完成
        mcr.writtenToDiskLatch.await();
    } catch (InterruptedException e) {
        return false;
    } finally {
        
    }
    //對注冊當前SharedPreferences對象的監(jiān)聽器進行回調
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}

commitToMemory

private MemoryCommitResult commitToMemory() {
    long memoryStateGeneration;
    List<String> keysModified = null;//用于存儲發(fā)生變化的鍵
    Set<OnSharedPreferenceChangeListener> listeners = null;//用于存儲監(jiān)聽器
    Map<String, Object> mapToWriteToDisk;//用于記錄需要寫入磁盤的所有的數(shù)據(jù)
    synchronized (SharedPreferencesImpl.this.mLock) {
       //若當前有線程在寫入
        if (mDiskWritesInFlight > 0) {
            //復制mMap對數(shù)據(jù)進行操作
            mMap = new HashMap<String, Object>(mMap);
        }
        //獲取全局變量
        mapToWriteToDisk = mMap;
        //當前進行寫入操作的線程數(shù)加一
        mDiskWritesInFlight++;
        //是否有監(jiān)聽器
        boolean hasListeners = mListeners.size() > 0;
        //若有監(jiān)聽器
        if (hasListeners) {
            //進行初始化
            keysModified = new ArrayList<String>();
            listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
        }
        synchronized (mEditorLock) {
            //表示內存數(shù)據(jù)是否發(fā)生變化
            boolean changesMade = false;
            //若用戶調用clear方法清空數(shù)據(jù)
            if (mClear) {
                //若寫入的數(shù)據(jù)不為空炫惩,即有需要清除的數(shù)據(jù)
                if (!mapToWriteToDisk.isEmpty()) {
                    changesMade = true;
                    mapToWriteToDisk.clear();
                }
                mClear = false;
            }
            //對提交到Editor中的數(shù)據(jù)進行遍歷
            for (Map.Entry<String, Object> e : mModified.entrySet()) {
                String k = e.getKey();
                Object v = e.getValue();
                if (v == this || v == null) {
                    if (!mapToWriteToDisk.containsKey(k)) {
                        continue;
                    }
                    mapToWriteToDisk.remove(k);
                } else {
                //若值不為空,也不為自身,說明添加了新鍵值對或修改了鍵值對的值
                //若打算寫入磁盤的數(shù)據(jù)包含k這個鍵阿浓,說明對值進行了修改
                    if (mapToWriteToDisk.containsKey(k)) {
                        Object existingValue = mapToWriteToDisk.get(k);
                        //若之前的值不為空他嚷,同時和現(xiàn)在的值相同說明實際沒有修改
                        if (existingValue != null && existingValue.equals(v)) {
                            continue;
                        }
                    }
                    mapToWriteToDisk.put(k, v);
                }
                changesMade = true;
                if (hasListeners) {
                    keysModified.add(k);
                }
            }
            mModified.clear();
            //內存數(shù)據(jù)發(fā)生變化,次數(shù)加一
            if (changesMade) {
                mCurrentMemoryStateGeneration++;
            }
            memoryStateGeneration = mCurrentMemoryStateGeneration;
        }
    }
    //創(chuàng)建MemoryCommitResult對象并返回
    return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
            mapToWriteToDisk);
}

enqueueDiskWrite()

private void enqueueDiskWrite(final MemoryCommitResult mcr,
                              final Runnable postWriteRunnable) {
    //通過判斷參數(shù)postWriteRunnable是否為空芭毙,表示需要同步寫入磁盤還是異步寫入磁盤
    final boolean isFromSyncCommit = (postWriteRunnable == null);
    //創(chuàng)建writeToRunnable對象筋蓖,封裝寫入磁盤的核心操作
    final Runnable writeToDiskRunnable = new Runnable() {
            @Override
            public void run() {
                synchronized (mWritingToDiskLock) {
                   //寫入磁盤
                    writeToFile(mcr, isFromSyncCommit);
                }
                synchronized (mLock) {
                    //當前寫入磁盤的線程數(shù)量減一
                    mDiskWritesInFlight--;
                }
                if (postWriteRunnable != null) {
                    postWriteRunnable.run();
                }
            }
        };
    // 若為同步寫入磁盤
    if (isFromSyncCommit) {
        //表示是否有其他線程正在寫入
        boolean wasEmpty = false;
        synchronized (mLock) {
            //若當前寫入磁盤的線程數(shù)量為1 說明除了本線程沒有其他線程寫入,wasEmpty為true
            wasEmpty = mDiskWritesInFlight == 1;
        }
        //若沒有其他線程正在寫入
        if (wasEmpty) {
            //執(zhí)行線程
            writeToDiskRunnable.run();
            return;
        }
    }
    //若為異步寫入或者同步寫入時有其他線程正在寫入退敦,則調用本方法異步寫入
    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}

apply()

public void apply() {
    final long startTime = System.currentTimeMillis();
    //對EditorImpl對象的操作進行整合處理
    final MemoryCommitResult mcr = commitToMemory();
    final Runnable awaitCommit = new Runnable() {
            @Override
            public void run() {
                try {
                    //阻塞等待數(shù)據(jù)寫入磁盤完成
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException ignored) {
                }
                if (DEBUG && mcr.wasWritten) {
                    Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                            + " applied after " + (System.currentTimeMillis() - startTime)
                            + " ms");
                }
            }
        };
    //將awaitCommit對象保存到QueuedWork 當QueuedWork在執(zhí)行任務時需要阻塞時會調用
    QueuedWork.addFinisher(awaitCommit);
    Runnable postWriteRunnable = new Runnable() {
            @Override
            public void run() {
                awaitCommit.run();
                //將awaitCommit對象從QueuedWork中移除 QueuedWork.removeFinisher(awaitCommit);
            }
        };
    //將數(shù)據(jù)寫入磁盤
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);

    notifyListeners(mcr);
}

從此可以看出粘咖,這兩個方法都是首先修改內存中緩存的mMap的值,然后將數(shù)據(jù)寫到磁盤中侈百。它們的主要區(qū)別是commit會等待寫入磁盤后再返回瓮下,而apply則在調用寫磁盤操作后就直接返回了,但是這時候可能磁盤中數(shù)據(jù)還沒有被修改钝域。

參考文章

有關SharedPreferences面試

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末讽坏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子例证,更是在濱河造成了極大的恐慌路呜,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件织咧,死亡現(xiàn)場離奇詭異胀葱,居然都是意外死亡,警方通過查閱死者的電腦和手機笙蒙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門抵屿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人手趣,你說我怎么就攤上這事晌该》世螅” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵朝群,是天一觀的道長燕耿。 經(jīng)常有香客問我,道長姜胖,這世上最難降的妖魔是什么誉帅? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮右莱,結果婚禮上蚜锨,老公的妹妹穿的比我還像新娘。我一直安慰自己慢蜓,他們只是感情好亚再,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著晨抡,像睡著了一般氛悬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耘柱,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天如捅,我揣著相機與錄音,去河邊找鬼调煎。 笑死镜遣,一個胖子當著我的面吹牛,可吹牛的內容都是我干的士袄。 我是一名探鬼主播悲关,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼窖剑!你這毒婦竟也來了坚洽?” 一聲冷哼從身側響起戈稿,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤西土,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鞍盗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體需了,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年般甲,在試婚紗的時候發(fā)現(xiàn)自己被綠了肋乍。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡敷存,死狀恐怖墓造,靈堂內的尸體忽然破棺而出堪伍,到底是詐尸還是另有隱情,我是刑警寧澤觅闽,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布帝雇,位于F島的核電站,受9級特大地震影響蛉拙,放射性物質發(fā)生泄漏尸闸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一孕锄、第九天 我趴在偏房一處隱蔽的房頂上張望吮廉。 院中可真熱鬧,春花似錦畸肆、人聲如沸宦芦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽踪旷。三九已至,卻和暖如春豁辉,著一層夾襖步出監(jiān)牢的瞬間令野,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工徽级, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留气破,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓餐抢,卻偏偏與公主長得像现使,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子旷痕,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355