天天看點

深入了解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初始化的時序圖如下

深入了解SharedPrefences實作原理

Activity在啟動的時候會建立ContextImpl對象,并調用Activity的attach()方法将ContextImpl對象傳遞下去,最終給ContextWrapper的Context對象mBase指派。

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

深入了解SharedPrefences實作原理

下面我們就看下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()方法将資料儲存起來。我們先看下涉及到的類關系圖

深入了解SharedPrefences實作原理

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

深入了解SharedPrefences實作原理

首先建立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同步機制來保證寫入任務執行後才執行下一個任務。