深入理解SharedPrefences實現原理

SharedPreferences作爲Android常用的持久化組件,很常用,在系統設置應用中就大量地被使用。相信大家基本都是用過,但是在使用過程中,大家是否又知道它的實現原理呢?

基本使用

 SharedPreferences使用很簡單,比如我們要它保存某個字符串,在Activity使用如下:

/**
 * save number
 * @param num
 */
private void saveNumber(String num) {
    SharedPreferences sharedPreferences = getSharedPreferences("light_persist", MODE_PRIVATE);
    sharedPreferences.edit().putString("number", num).apply();
    //sharedPreferences.edit().putString("number", number).commit();
}

上面代碼執行後,會將變量num保存在/data/data/應用包名/shared_prefs/light_persist.xml。SharedPreferences使用很簡單,但這並不表示它本身也簡單,下面從三個角度來深入瞭解SharedPreferences的實現原理

對象初始化

 在上面的例子中,我們是在Activity通過調用getSharedPreferences()獲取SharedPreferences對象,我們看下它的具體實現是怎樣的。首先,我們要明白以下幾點:

  • Activity繼承了ContextThemeWrapper,ContextThemeWrapper繼承了ContextWrapper,而ContextWrapper繼承了抽象類Context,getSharedPreferences()爲Context中的抽象方法;ContextWrapper重寫了getSharedPreferences()方法,所以Activity中調用getSharedPreferences()方法,實際上調用的是ContextWrapper裏面的方法。
  • ContextWrapper中getSharedPreferences()方法的實現是調用了Context對象mBase的getSharedPreferences()。mBase具體對象爲ContextImpl,也就是說Context.getSharedPreferences()具體實現在ContextImpl中。

對於第一點,通過類的繼承關係很容易知道;對於第二點,這個會涉及到Activity的啓動過程,在ActivityThread的performLaunchActivity()方法中會初始化context,context初始化的時序圖如下
context創建

Activity在啓動的時候會創建ContextImpl對象,並調用Activity的attach()方法將ContextImpl對象傳遞下去,最終給ContextWrapper的Context對象mBase賦值。

在明白Activity中調用getSharedPreferences()方法實際上調用了CcontextImpl.getSharedPreferences()後,我們就很容易明白SharedPreferences對象是如何初始化的了。SharedPreferences對象初始化過程如下所示
SharedPreferences初始化
下面我們就看下SharedPreference初始化的代碼實現

@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
    // At least one application in the world actually passes in a null
    // name.  This happened to work because when we generated the file name
    // we would stringify it to "null.xml".  Nice.
    if (mPackageInfo.getApplicationInfo().targetSdkVersion <
            Build.VERSION_CODES.KITKAT) {
        if (name == null) {
            name = "null";
        }
    }

    File file;
    synchronized (ContextImpl.class) {
        if (mSharedPrefsPaths == null) {
            mSharedPrefsPaths = new ArrayMap<>();
        }
        file = mSharedPrefsPaths.get(name);
        //從緩存集合中取name對應的file,如果集合中沒有,則根據name創建對應的file並添加進集合
        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) {
	    //getSharedPreferencesCacheLocked()根據應用包名從緩存集合中取ArrayMap集合
        final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
        sp = cache.get(file);
        if (sp == null) {
		    //mode校驗,在Android7.0開始不再支持MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE,
			//也就是不支持其他應用讀寫該應用的SharedPreference文件。
            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");
                }
            }
			//創建SharedPreferences接口實現類對象SharedPreferencesImpl,並存儲在集合中。
            sp = new SharedPreferencesImpl(file, mode);
            cache.put(file, sp);
            return sp;
        }
    }
	//如果mode設置爲了MODE_MULTI_PROCESS且targetSdkVersion小於Android3.0,則從硬盤中加載SharedPreferences文件
	//MODE_MULTI_PROCESS是對應用多進程的一種支持,但是在Android3.0版本開始已經棄用了
    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;
}

至此SharedPreferencesImpl對象就創建完成了,至於SharedPreferencesImpl的構造方法,具體就不展開,它主要作用是變量初始化、創建備份文件和從硬盤中加載SharedPredference文件。最後我們看下SharedPreferences接口說明。

 * Interface for accessing and modifying preference data returned by {@link
 * Context#getSharedPreferences}.  For any particular set of preferences,
 * there is a single instance of this class that all clients share.
 * Modifications to the preferences must go through an {@link Editor} object
 * to ensure the preference values remain in a consistent state and control
 * when they are committed to storage.  Objects that are returned from the
 * various <code>get</code> methods must be treated as immutable by the application.
 *
 * <p><em>Note: This class does not support use across multiple processes.</em>

有兩點需要注意:一,對於任何特定的一組preferences,所有客戶端都共享一個此接口實現類(也就是SharedPreferencesImpl)的單個實例,這個一點可以在ContextImpl.getSharedPreferencesCacheLocked()方法中得到驗證,它是根據應用包名從緩存集合中取ArrayMap集合。二,不支持跨進程使用,這個在Android3.0已經棄用了。

寫入實現

 往SharedPreferences中保存數據,我們是通過Editor接口實現的,通過調用Editor.putXxx().apply()或Editor.putXxx().commit()方法將數據保存起來。我們先看下涉及到的類關係圖

SharedPreference類關係圖

Editor是SharedPreferences接口的嵌套接口,它的實現類是SharedPreferencesImpl的嵌套類EditorImpl,所以要看數據是怎麼寫入的,我們就需要分析EditorImpl裏面的putXxx() 和apply()或者commit()方法,大多數同學應該知道commit()和apply()方法的區別,前者是同步方法且運行在主線程中,後者是異步方法運行在子線程中,我們先看下apply()的實現方式,理解apply()之後,commit()也就不再話下。

Editor apply()時序圖

首先創建Editor接口實現類EditorImpl實例對象,然後調用EditorImpl.putString(),最後通過apply()提交一個寫入數據的異步任務。我們重點關注下apply()方法實現,它的實現分爲以下幾步:

  • 創建MemoryCommitResult對象,該對象中存儲了寫入SharedPreferences的數據。
  • 創建阻塞CountDownLatch(MemoryCommitReuslt.writtenToDiskLatch)的任務,調用QueueWork.addFinisher()方法緩存,緩存的任務不一定會在QueueWork執行。唯一可能會執行的情況是QueueWork.waitToFinish()方法中調用。而waitToFinish()方法將會在Activity的onPause()之後,BroadcastReceiver的onReceive之後,Service的stop的時候調用,具體調用的地方是在ActivityThread中,感興趣的同學可查閱AcaptivityThread。
  • 將寫入硬盤的操作的任務入QueueWork隊列,由QueueWork中的HandlerThread執行寫入的任務。
  • 通知監聽
@Override
public void apply() {
    final long startTime = System.currentTimeMillis();

    final MemoryCommitResult mcr = commitToMemory();
    final Runnable awaitCommit = new Runnable() {
            @Override
            public void run() {
                try {
                    //這裏作用是如果寫preference到硬盤沒有結束,就阻塞QueueWork中執行寫preference的線程,直到寫入完成。如果preference已經寫入硬盤,寫入過程中會調用mcr.writtenToDiskLatch.countDown()方法,而此時下面的await()方法不會阻塞,因爲writtenToDiskLatch初始化的count爲1.
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException ignored) {
                }

                if (DEBUG && mcr.wasWritten) {
                    Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                            + " applied after " + (System.currentTimeMillis() - startTime)
                            + " ms");
                }
            }
        };
    //addFinisher方法的實現是將Runnable對象加入到一個名爲sFinisher的集合,Finisher可以理解爲執行最後操作的管理者。
    QueuedWork.addFinisher(awaitCommit);

    Runnable postWriteRunnable = new Runnable() {
            @Override
            public void run() {
                awaitCommit.run();
                //將等待任務從Finisher中移除,因爲此Runnable是在寫入操作完成之後調用,所以就需要從         QueueWork.sFinisher集合中移除;
                QueuedWork.removeFinisher(awaitCommit);
            }
        };
    
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);

    // Okay to notify the listeners before it's hit disk
    // because the listeners should always get the same
    // SharedPreferences instance back, which has the
    // changes reflected in memory.
    notifyListeners(mcr);
}


private void enqueueDiskWrite(final MemoryCommitResult mcr,
                              final Runnable postWriteRunnable) {
    final boolean isFromSyncCommit = (postWriteRunnable == null);

    final Runnable writeToDiskRunnable = new Runnable() {
            @Override
            public void run() {
                synchronized (mWritingToDiskLock) {
                    //將preference寫入磁盤,並調用mcr.writtenToDiskLatch.countDown()釋放latch
                    //writtenToDiskLatch初始化的latch個數爲1;
                    writeToFile(mcr, isFromSyncCommit);
                }
                synchronized (mLock) {
                    mDiskWritesInFlight--;
                }
                if (postWriteRunnable != null) {
                    postWriteRunnable.run();
                }
            }
        };

    // Typical #commit() path with fewer allocations, doing a write on
    // the current thread.
    if (isFromSyncCommit) {
        boolean wasEmpty = false;
        synchronized (mLock) {
            wasEmpty = mDiskWritesInFlight == 1;
        }
        if (wasEmpty) {
            writeToDiskRunnable.run();
            return;
        }
    }
  
    //QueueWork.queue()最終實現是通過Queue中的HandlerThread執行writeToDiskRunnable任務。
    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}

以上就是通過apply()方式寫入preference的實現方式,相關說明已在代碼中註釋。補充一點,CountDownLatch是一種同步機制,作用類似於它的字面意思,可以理解它是一道門,門上有一個或者多個門閂,要想通過這道門只有所有門閂都打開後通過這道門,否則只能一直等待。

有一點需要注意,當我們通過Editor改變某個SharedPreferences文件的一項preference時,實際上最終會將該文件的所有preference重新寫入文件,這一點可以從commitToMemory()方法中看出,所以建議不要用SharedPreferences存儲數據量大的內容;另外頻繁改變的數據應和其他數據放在不同的SharedPreferences文件中。

// Returns true if any changes were made
private MemoryCommitResult commitToMemory() {
    long memoryStateGeneration;
    List<String> keysModified = null;
    Set<OnSharedPreferenceChangeListener> listeners = null;
    Map<String, Object> mapToWriteToDisk;

    synchronized (SharedPreferencesImpl.this.mLock) {
        // We optimistically don't make a deep copy until
        // a memory commit comes in when we're already
        // writing to disk.
		    //當前寫入磁盤的操作大於0,則先拷貝
        if (mDiskWritesInFlight > 0) {
            // We can't modify our mMap as a currently
            // in-flight write owns it.  Clone it before
            // modifying it.
            // noinspection unchecked
            mMap = new HashMap<String, Object>(mMap);
        }
        mapToWriteToDisk = mMap;//mMap就是所有的preference
        mDiskWritesInFlight++;//此變量會在真正寫入磁盤的操作完成後遞減

        boolean hasListeners = mListeners.size() > 0;
        if (hasListeners) {
            keysModified = new ArrayList<String>();
            listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
        }

        synchronized (mEditorLock) {
            boolean changesMade = false;
            //是否調用了Edit.clear()方法
            if (mClear) {
                if (!mapToWriteToDisk.isEmpty()) {
                    changesMade = true;
                    mapToWriteToDisk.clear();
                }
                mClear = false;
            }

            for (Map.Entry<String, Object> e : mModified.entrySet()) {
                String k = e.getKey();
                Object v = e.getValue();
                // "this" is the magic value for a removal mutation. In addition,
                // setting a value to "null" for a given key is specified to be
                // equivalent to calling remove on that key.
			          //如果value爲EditorImpl對象本身或者爲null,則將它從map中移除
                if (v == this || v == null) {
                    if (!mapToWriteToDisk.containsKey(k)) {
                        continue;
                    }
                    mapToWriteToDisk.remove(k);
                } else {
                    if (mapToWriteToDisk.containsKey(k)) {
                        Object existingValue = mapToWriteToDisk.get(k);
                        if (existingValue != null && existingValue.equals(v)) {
                            continue;
                        }
                    }
                    mapToWriteToDisk.put(k, v);
                }

                changesMade = true;
                if (hasListeners) {
                    keysModified.add(k);
                }
            }
            mModified.clear();
            if (changesMade) {
                mCurrentMemoryStateGeneration++;
            }
            memoryStateGeneration = mCurrentMemoryStateGeneration;
        }
    }
    return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
            mapToWriteToDisk);
}

至於Editor.commit()方法就不展開了,commit實現比apply()更簡單些。

讀取實現

 在SharedPrefrencesImpl的構造方法中會加載保存的preference,並存入集合mMap。當我們通過SharedPreferences.getXxx()方法是,就是從mMap中取出對應的值。

@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
    synchronized (mLock) {
        //判斷SharedPreferences文件是否加載,如果沒加載則阻塞知道文件加載完成
        awaitLoadedLocked();
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}

總結

  • SharedPreferences是接口,它的實現類是SharedPreferencesImpl。在Activity中調用getSharedPreferences()最終調用的是ContextImpl.getSharedPreferences()方法;

  • Eidtor.apply()將preference寫入磁盤的任務是在QueueWork中的HandlerThread線程中執行;而Eidtor.commit()將preference寫入磁盤的任務是運行在主線程中。兩者寫入的過程中都會利用CountDownLatch同步機制來保證寫入任務執行後才執行下一個任務。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章