Android SharedPreferences詳解

前言

???????Sharedpreferences是Android平臺上一個輕量級的存儲類沾凄,用來保存應(yīng)用程序的各種配置信息焚廊,其本質(zhì)是一個以“key-value”鍵值對的方式保存數(shù)據(jù)的xml文件,其文件保存在/data/data/com.xx.xxx/shared_prefs目錄下淋袖,Android其它四種存儲方式為:
???????a. ContentProvider:將應(yīng)用的私有數(shù)據(jù)提供給其他應(yīng)用使用鸿市;
???????b. 文件存儲:以IO流形式存放,可分為手機內(nèi)部和手機外部(sd卡等)存儲即碗,可存放較大數(shù)據(jù)焰情;
???????c. SQLiteDataBase:輕量級、跨平臺數(shù)據(jù)庫剥懒,將所有數(shù)據(jù)都是存放在手機上的單一文件內(nèi)内舟,占用內(nèi)存小初橘;
???????d. 網(wǎng)絡(luò)存儲:數(shù)據(jù)存儲在服務(wù)器上验游,通過連接網(wǎng)絡(luò)獲取數(shù)據(jù);

???????由于使用方式比較簡單保檐,平常的Android應(yīng)用開發(fā)中經(jīng)常用到Sharedpreferences耕蝉,本文結(jié)合源碼對平常使用及可能會出現(xiàn)的問題進行一一分析。

一.獲取Sharedpreferences

???????要想使用 SharedPreferences 來存儲數(shù)據(jù)夜只,首先需要獲取到 SharedPreferences 對象垒在,平常最常用的是通過以下方式來獲取Sharedpreferences對象,通過調(diào)用context的getSharedPreferences()方法:

SharedPreferences mPreference = mContext.getSharedPreferences(SharePreferenceUtils.PREFERENCE_NAME,
                Context.MODE_PRIVATE);

???????還有另外兩種方式扔亥,一種是在Activity內(nèi)部直接通過getPreferences()來獲取該Activity類名對應(yīng)name的SharedPreferences xml文件场躯;一種是通過 PreferenceManager中的getDefaultSharedPreferences()靜態(tài)方法,它接收一個 Context 參數(shù)旅挤,并自動使用當前應(yīng)用程序的包名作為前綴來命名SharedPreferences xml文件踢关;

二.使用方式

???????SharedPreferences對象本身只能獲取數(shù)據(jù)而不支持存儲和修改,存儲修改是通過SharedPreferences.edit()獲取的內(nèi)部接口Editor對象實現(xiàn)粘茄。使用Editor來存取數(shù)據(jù)签舞,用到了SharedPreferences接口和SharedPreferences的一個內(nèi)部接口SharedPreferences.Editor,使用方式如下:

a.寫入數(shù)據(jù):
//1.創(chuàng)建一個SharedPreferences對象
SharedPreferences sharedPreferences= getSharedPreferences(SharePreferenceUtils.PREFERENCE_NAME,
                Context.MODE_PRIVATE);
//2.實例化SharedPreferences.Editor對象
SharedPreferences.Editor editor = sharedPreferences.edit();
//3.將獲取過來的值放入文件
editor.putString("xxx", “xxx”);
editor.putInt("xxx", 10);
editor.putBoolean("xxx",false);
//4.提交               
editor.commit();
editor.apply();
b.讀取數(shù)據(jù):
SharedPreferences sharedPreferences = getSharedPreferences(SharePreferenceUtils.PREFERENCE_NAME,
                Context.MODE_PRIVATE);
String name = sharedPreferences.getString("xxx","");
c.刪除指定數(shù)據(jù)
editor.remove("xxx");
editor.commit();
d.清空數(shù)據(jù)
editor.clear();
editor.commit();

三.源碼分析

???????接下來根據(jù)調(diào)用關(guān)系柒瓣,深入源碼一起來詳細的了解一下瘪菌,看一下平時使用中可能遇到的問題及問題原因,本文主要對通過context.getSharedPreferences()方式來獲取時的調(diào)用順序及涉及到的類進行一一分析:

a.ContextImpl
private ArrayMap<String, File> mSharedPrefsPaths;
public SharedPreferences getSharedPreferences(String name, int mode) {
    ......
    File file;
    synchronized (ContextImpl.class) {
        if (mSharedPrefsPaths == null) {
            mSharedPrefsPaths = new ArrayMap<>();
        }
        file = mSharedPrefsPaths.get(name);
        if (file == null) {
            file = getSharedPreferencesPath(name);
            mSharedPrefsPaths.put(name, file);
        }
    }
    return getSharedPreferences(file, mode);
}

public File getSharedPreferencesPath(String name) {
    return makeFilename(getPreferencesDir(), name + ".xml");
}

???????以上可以看到嘹朗,在getSharedPreferences()內(nèi)部會有一個Map緩存师妙,如果Map緩存中有對應(yīng)name的File,直接獲纫倥唷默穴;否則的話怔檩,會通過getSharedPreferencesPath()來創(chuàng)建File,然后存入Map蓄诽;最后通過getSharedPreferences()來返回SharedPreferences薛训。

@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
    SharedPreferencesImpl sp;
    synchronized (ContextImpl.class) {
        final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
        sp = cache.get(file);
        if (sp == null) {
            checkMode(mode);
            ......
            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;
}

???????在getSharedPreferences內(nèi)部返回的是一個SharedPreferencesImpl對象,SharedPreferences是一個接口仑氛,SharedPreferencesImpl是接口的實現(xiàn)類乙埃。

b.SharedPreferencesImpl

???????通過獲取方式可以看到,最終返回的是SharedPreferencesImpl對象锯岖,那么所有的操作都是通過該類來實現(xiàn)的介袜,一起看一下:

private final File mFile;
private final File mBackupFile;
private final int mMode;
private Map<String, Object> mMap;

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

static File makeBackupFile(File prefsFile) {
    return new File(prefsFile.getPath() + ".bak");
}

???????在獲取SharedPreferencesImpl對象時,主要做了對應(yīng)的賦值操作出吹,執(zhí)行了makeBackupFile()來備份文件遇伞,重點來看一下startLoadFromDisk():

private void startLoadFromDisk() {
    synchronized (mLock) {
        mLoaded = false;
    }
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            loadFromDisk();
        }
    }.start();
}

???????在startLoadFromDisk()內(nèi)部現(xiàn)將mLoaded置為false,然后啟動異步線程來執(zhí)行l(wèi)oadFromDisk()捶牢,再看一下loadFromDisk():

private void loadFromDisk() {
    synchronized (mLock) {
        if (mLoaded) {
            return;
        }
        //檢查源文件的備份文件是否存在:mBackupFile.exists()鸠珠,如果存在,則將源文件刪除:mFile.delete()秋麸,
        //然后將備份文件修改為源文件:mBackupFile.renameTo(mFile)渐排。
        //后續(xù)操作就是從備份文件加載相關(guān)數(shù)據(jù)到內(nèi)存 mMap 容器中了
        if (mBackupFile.exists()) {
            mFile.delete();
            mBackupFile.renameTo(mFile);
        }
    }

    ......
    Map map = null;
    StructStat stat = null;
    try {
        stat = Os.stat(mFile.getPath());
        if (mFile.canRead()) {
            BufferedInputStream str = null;
            try {
                str = new BufferedInputStream(new FileInputStream(mFile), 16*1024);
                map = XmlUtils.readMapXml(str);
            } .......
        }
    }

    synchronized (mLock) {
        mLoaded = true;
        if (map != null) {
            mMap = map;
            ......
        } else {
            mMap = new HashMap<>();
        }
        mLock.notifyAll();
    }
}

???????在loadFromDisk()內(nèi)部主要干了五件事
???????1.如果備份文件存在,那么就先刪除mFile文件灸蟆,然后將備份文件命名為mFile驯耻;
???????2.通過XmlUtils.readMapXml將xml文件內(nèi)的鍵值對讀取為Map形式存在;
???????3.加鎖次乓,將mLoaded賦值為true吓歇,表示已經(jīng)加載完畢孽水;
???????4.如果map不為null票腰,將讀取的內(nèi)容賦值給mMap(構(gòu)造方法內(nèi)置為null);
???????5.調(diào)用mLock.notifyAll()來通知女气;
???????以上就是通過getSharedPreferences()來獲取SharedPreferences對象的過程杏慰,上述的3、4炼鞠、5是簡單的賦值或通知操作缘滥,為什么要標注出來呢,接著往下看谒主,看一下讀取操作:

public String getString(String key, @Nullable String defValue) {
    synchronized (mLock) {
        awaitLoadedLocked();
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}

???????以上可以看到朝扼,在獲取sp內(nèi)的值時,先進行加鎖操作霎肯,然后執(zhí)行了awaitLoadedLocked()擎颖,從字面意思來看榛斯,是等待加載完成,那看一下內(nèi)部實現(xiàn):

private void awaitLoadedLocked() {
    ......
    while (!mLoaded) {
        try {
            mLock.wait();
        } catch (InterruptedException unused) {
        }
    }
}

???????我們可以看到搂捧,如果mLoaded為false驮俗,那么會執(zhí)行mLock.wait(),即進行等待允跑,接下來就用到了上述講的在loadFromDisk()內(nèi)部最后賦值和通知操作了王凑,如果在get()時mLoaded為false,那么表示沒有加載完畢聋丝,就需要一直等待索烹,等在loadFromDisk()內(nèi)部加載完畢后才會結(jié)束等待,那么問題來了:為什么SharedPreference會造成卡頓甚至ANR?
???????第一次從SharedPreference獲取值的時候潮针,可能阻塞主線程术荤,造成卡頓/丟幀。獲取SharedPreference可以立刻返回每篷,耗時操作是在異步線程瓣戚,但是去獲取時如果異步線程沒有加載完畢,會一直等待焦读,造成卡頓子库,如果等待時間超過5s,甚至?xí)斐葾NR矗晃。注意:只有第一次才會仑嗅,后面不會,因為加載文件成功后會在內(nèi)存緩存數(shù)據(jù)张症,下次就不需要等待了仓技。
???????上面講到了獲取SharedPreference對象及獲取sp存儲的值,那么如何寫入值呢俗他?前面講到通過edit()獲取到Editor來進行寫操作:

public Editor edit() {
    synchronized (mLock) {
        awaitLoadedLocked();
    }
    return new EditorImpl();
}

???????每次執(zhí)行edit()也需要等待鎖脖捻,然后new一個EditorImpl對象,Editor是一個接口兆衅,內(nèi)部的邏輯最終是通過其實現(xiàn)類EditorImpl來完成的地沮,一起看一下:

c.EditorImpl
public final class EditorImpl implements Editor {
    private final Object mLock = new Object();

    @GuardedBy("mLock")
    private final Map<String, Object> mModified = Maps.newHashMap();

    public Editor putString(String key, @Nullable String value) {
        synchronized (mLock) {
            mModified.put(key, value);
            return this;
        }
    }
    ......

   public Editor remove(String key) {
        synchronized (mLock) {
            mModified.put(key, this);
            return this;
        }
    }

    public Editor clear() {
        synchronized (mLock) {
            mClear = true;
            return this;
        }
    }
}

???????在向sp存數(shù)據(jù)時,先執(zhí)行put相關(guān)的操作羡亩,接下來需要執(zhí)行apply()及commit()來進行寫入操作摩疑,那apply()和commit()有什么區(qū)別呢?
???????1.apply()是異步操作畏铆,commit()是同步操作雷袋;
???????2.apply()無返回值,commit()有boolean返回值辞居;
???????先看一下apply()

public void apply() {
   final MemoryCommitResult mcr = commitToMemory();
   final Runnable awaitCommit = new Runnable() {
            public void run() {
                try {
                    //阻塞等待寫入文件完成,否則阻塞在這
                    //利用CountDownLatch來等待任務(wù)的完成
                    //后面執(zhí)行enqueueDiskWrite寫入文件成功后會把writtenToDiskLatch多線程計數(shù)器減1楷怒,
                    //這樣的話下面的阻塞代碼就可以通過了.
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException ignored) {
                }
                .......
            }
        };
    //QueuedWork是用來確保SharedPrefenced的寫操作在Activity銷毀前執(zhí)行完的一個全局隊列. 
    QueuedWork.addFinisher(awaitCommit);

    Runnable postWriteRunnable = new Runnable() {
            public void run() {
                //執(zhí)行阻塞任務(wù)
                awaitCommit.run();
                //阻塞完成之后寨腔,從隊列中移除任務(wù)
                QueuedWork.removeFinisher(awaitCommit);
            }
       };

    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
    notifyListeners(mcr);
}

???????以上可以看到,在apply()中執(zhí)行的操作為: 
???????1.通過commitToMemory()創(chuàng)建MemoryCommitResult率寡;
???????2.創(chuàng)建一個Runnable awaitCommit迫卢;
???????3.將awaitCommit執(zhí)行addFinisher()[此處會有問題];
???????4.創(chuàng)建一個Runnable postWriteRunnable冶共;
???????5.執(zhí)行enqueueDiskWrite()乾蛤,真正的寫入操作;
???????6.執(zhí)行notifyListeners()通知sp數(shù)據(jù)變化捅僵;
???????接下來看一下commit()

public boolean commit() {
    .......
    MemoryCommitResult mcr = commitToMemory();

    SharedPreferencesImpl.this.enqueueDiskWrite( mcr, null /* sync write on this thread okay */);
    try {
        mcr.writtenToDiskLatch.await();
    }.......
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}

???????以上可以看到家卖,在commit()中執(zhí)行的操作為: 
???????1.通過commitToMemory()創(chuàng)建MemoryCommitResult;
???????2.執(zhí)行enqueueDiskWrite()庙楚;
???????3.執(zhí)行mcr.writtenToDiskLatch.await()[apply是在子線程中完成的]上荡;
???????4.執(zhí)行notifyListeners()通知sp數(shù)據(jù)變化;
???????5.最后返回writeToDiskResult馒闷;
???????上述可以看到酪捡,在apply()及commit()中都通過commitToMemory()創(chuàng)建了MemoryCommitResult,看一下commitToMemory()內(nèi)部執(zhí)行邏輯:

private MemoryCommitResult commitToMemory() {
    ......
    Map<String, Object> mapToWriteToDisk;

    synchronized (SharedPreferencesImpl.this.mLock) {
        if (mDiskWritesInFlight > 0) {
            mMap = new HashMap<String, Object>(mMap);
        }
        mapToWriteToDisk = mMap;
        mDiskWritesInFlight++;
        .........

        synchronized (mLock) {
            boolean changesMade = false;

            if (mClear) {
                if (!mMap.isEmpty()) {
                    changesMade = true;
                    mMap.clear();
                }
                mClear = false;
            }

            for (Map.Entry<String, Object> e : mModified.entrySet()) {
                String k = e.getKey();
                Object v = e.getValue();
                if (v == this || v == null) {
                    if (!mMap.containsKey(k)) {
                        continue;
                    }
                    mMap.remove(k);
                 } else {
                    if (mMap.containsKey(k)) {
                        Object existingValue = mMap.get(k);
                        if (existingValue != null && existingValue.equals(v)) {
                            continue;
                        }
                    }
                    mMap.put(k, v);
               }
                //數(shù)據(jù)有變化纳账,將changesMade置為true逛薇;
                changesMade = true;
               ....
            }

            mModified.clear();
            if (changesMade) {
                //將該值+1,后續(xù)writeToFile()更新會用到
                mCurrentMemoryStateGeneration++;
             }

            memoryStateGeneration = mCurrentMemoryStateGeneration;
        }
    }
    return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
                    mapToWriteToDisk);
}

???????以上可以看到疏虫,在commitToMemory()中執(zhí)行的操作為: 
???????1.將mMap賦值給局部變量mapToWriteToDisk[執(zhí)行文件寫入時會用到]永罚,mDiskWritesInFlight++[commit操作會調(diào)用到]
???????2.如果clear為true卧秘,即當執(zhí)行了clear()操作呢袱,會將mMap清空;
???????3.遍歷mModified翅敌,當v==this羞福,說明執(zhí)行了remove()操作,則將該值在mMap中移除哼御,其他情況坯临,即執(zhí)行put()操作焊唬,先判斷是否包含key恋昼,然后對比value,最后確定是否需要將值put到mMap中赶促;
???????4.將mModified清空液肌,準備下次put操作;
???????5.最后new MemoryCommitResult()返回鸥滨;
???????通過以上我們可以看到嗦哆,當執(zhí)行put()谤祖、remove()、clear()操作時老速,mMap是實時更新的粥喜,那么什么時候?qū)懭離ml中的呢?在apply()及commit()中都執(zhí)行了SharedPreferencesImpl.this.enqueueDiskWrite(mcr橘券,postWriteRunnable)额湘,commit()時傳入的postWriteRunnable為NULL,一起看一下enqueueDiskWrite()這個方法:

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

    final Runnable writeToDiskRunnable = new Runnable() {
            public void run() {
                synchronized (mWritingToDiskLock) {
                    writeToFile(mcr, isFromSyncCommit);
                }
                synchronized (mLock) {
                    mDiskWritesInFlight--;
                }
                if (postWriteRunnable != null) {
                    postWriteRunnable.run();
                }
            }
        };

    if (isFromSyncCommit) {
        boolean wasEmpty = false;
        synchronized (mLock) {
            wasEmpty = mDiskWritesInFlight == 1;
        }
        if (wasEmpty) {
            writeToDiskRunnable.run();
            return;
        }
    }

    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}

???????以上可以看到旁舰,在enqueueDiskWrite()中執(zhí)行的操作為: 
???????1.先判斷postWriteRunnable是否為null锋华,如果為null,說明是同步操作箭窜,即執(zhí)行的commit()操作毯焕;
???????2.創(chuàng)建了writeToDiskRunnable,在內(nèi)部執(zhí)行了writeToFile()磺樱,然后執(zhí)行了postWriteRunnable纳猫;

private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
        .......
        if (fileExists) {
            boolean needsWrite = false;
            //判斷是否需要寫入文件,如果值沒有變化的話竹捉,就不需要寫入续担,在commitToMemory()中有對
          //memoryStateGeneration相關(guān)的操作
            if (mDiskStateGeneration < mcr.memoryStateGeneration) {
                if (isFromSyncCommit) {
                    needsWrite = true;
                } else {
                    synchronized (mLock) {
                        if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
                            needsWrite = true;
                        }
                    }
                }
            }

            if (!needsWrite) {
                //不需要wirte,直接setDiskWriteResult()活孩,減少IO操作物遇,commit()時會用來返回值,正常下返回true憾儒;
                mcr.setDiskWriteResult(false, true);
                return;
            }
            //判斷備份文件是否存在
            boolean backupFileExists = mBackupFile.exists();
            //備份文件不存在询兴,則在寫入前需要先備份文件
            if (!backupFileExists) {
                if (!mFile.renameTo(mBackupFile)) {
                    //備份失敗,直接返回
                    mcr.setDiskWriteResult(false, false);
                    return;
                }
            } else {
                //如果備份文件存在起趾,將源文件刪除[正常下備份文件是不應(yīng)該存在的]
                mFile.delete();
            }
        }

        try {
            //創(chuàng)建mFile文件的輸入流
            FileOutputStream str = createFileOutputStream(mFile);

            if (str == null) {
                mcr.setDiskWriteResult(false, false);
                return;
            }
            //向xml中寫入值
            XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
            //強制更新到xml文件
            FileUtils.sync(str);
            str.close();
            ........
            //寫入成功诗舰,將備份文件刪除
            mBackupFile.delete();
            //賦值,為了下次的判斷
            mDiskStateGeneration = mcr.memoryStateGeneration;
            mcr.setDiskWriteResult(true, true);
            mNumSync++;
            return;
        } ......
        //寫入失敗训裆,將源文件刪除
        if (mFile.exists()) {
            if (!mFile.delete()) {
                Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
            }
        }
        mcr.setDiskWriteResult(false, false);
    }

???????3.如果是同步操作眶根,直接在主線程執(zhí)行postWriteRunnable,返回边琉;
???????4.如果是異步操作属百,即apply(),會將postWriteRunnable加入到QueuedWork隊列中变姨;

d.QueuedWork
private static final LinkedList<Runnable> sWork = new LinkedList<>();
public static void queue(Runnable work, boolean shouldDelay) {
    Handler handler = getHandler();

    synchronized (sLock) {
        sWork.add(work);

        if (shouldDelay && sCanDelay) {
            handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
        } else {
            handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
        }
    }
}

???????以上可以看到族扰,在執(zhí)行QueuedWork的queue()操作: 
???????1.通過getHandler()獲取Handler;
???????2.將Runnable work加入到sWork鏈表中;
???????3.delay 100ms執(zhí)行MSG_RUN消息渔呵;
???????此時還沒有看到什么時候執(zhí)行的Runnable怒竿,接著往下看:

private static Handler getHandler() {
    synchronized (sLock) {
        if (sHandler == null) {
            HandlerThread handlerThread = new HandlerThread("queued-work-looper",
                        Process.THREAD_PRIORITY_FOREGROUND);
            handlerThread.start();

            sHandler = new QueuedWorkHandler(handlerThread.getLooper());
        }
        return sHandler;
    }
}

private static class QueuedWorkHandler extends Handler {
    static final int MSG_RUN = 1;

    QueuedWorkHandler(Looper looper) {
        super(looper);
    }

    public void handleMessage(Message msg) {
        if (msg.what == MSG_RUN) {
            processPendingWork();
        }
    }
}

private static void processPendingWork() {
    ......

    synchronized (sProcessingWork) {
        LinkedList<Runnable> work;

        synchronized (sLock) {
            work = (LinkedList<Runnable>) sWork.clone();
            sWork.clear();

            // Remove all msg-s as all work will be processed now
            getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
        }

        if (work.size() > 0) {
            for (Runnable w : work) {
                w.run();
            }

        }
    }
}

???????以上可以看到,在getHandler()內(nèi)部會創(chuàng)建HandlerThread扩氢,通過消息隊列耕驰,最終會執(zhí)行Runnable的run()方法,然后里面執(zhí)行耗時的WriteToFile()操作录豺。
???????至此耍属,SharedPreferences相關(guān)的原理就分析完了。

四.總結(jié)

a.加載緩慢

???????SharedPreferences文件的加載使用了異步線程巩检,而且加載線程并沒有設(shè)置優(yōu)先級厚骗,如果在加載時讀取數(shù)據(jù)就需要等待文件加載線程的結(jié)束。這就導(dǎo)致主線程等待低優(yōu)先線程鎖的問題兢哭,建議提前用預(yù)加載啟動過程用到的SP文件领舰。

b.commit和apply有什么區(qū)別?

???????commit()是同步且有返回值迟螺;apply()方法是異步?jīng)]有返回值冲秽;
???????commit()在主線程寫入文件,會造成UI卡頓矩父;apply()在子線程寫入文件锉桑,也有可能卡UI;
???????apply()是在子線程寫文件并不會造成UI線程卡頓窍株,但是在ActivityThread的handlePauseActivity()民轴、handleStopActivity()等方法中都會調(diào)用到:

QueuedWork.waitToFinish();

???????看一下QueuedWork的waitToFinish()方法:

public static void waitToFinish() {
.......
    try {
        while (true) {
            Runnable finisher;

            synchronized (sLock) {
                finisher = sFinishers.poll();
            }

            if (finisher == null) {
                break;
            }

            finisher.run();
        }
    } finally {
         sCanDelay = true;
    }
}
......

???????以上可以看到,循環(huán)地從sFinishers這個隊列中取任務(wù)執(zhí)行球订,直到任務(wù)為空后裸。這個任務(wù)就是之前apply()中的awaitCommit,它是用來等待寫入文件的線程執(zhí)行完畢的冒滩。如果因為多次執(zhí)行apply()微驶,在onPause()時,那就意味著寫入任務(wù)會在這里排隊开睡,但是寫入文件那里只有一個HandlerThread在串行的執(zhí)行因苹,那是不是就卡頓了?

c.合并操作

???????每次執(zhí)行put()都會通過edit()創(chuàng)建一個EditorImpl對象篇恒,然后都會執(zhí)行writeToFile()寫文件操作扶檐,所以如果執(zhí)行多次操作的話,建議合并為一次婚度。

SharedPreferences.Editor editor = mPreference.edit();
editor.putInt(key,status).putString(key1,status1).putboolean(key2,status1).apply();
d.SharedPreference如何跨進程通信蘸秘?
* @see #getSharedPreferences
*
* @deprecated 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 {@link android.content.ContentProvider ContentProvider}.
*/
@Deprecated
public static final int MODE_MULTI_PROCESS = 0x0004;
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();
}

???????在初始化sp的時候,設(shè)置flag為MODE_MULTI_PROCESS來跨進程通信蝗茁,但是很遺憾醋虏,這種方式已經(jīng)被廢棄。

e.全量寫入

???????無論是 commit() 還是 apply()哮翘,即使我們只改動其中一個條目颈嚼,都會把整個內(nèi)容全部寫到文件。而且即使我們多次寫同一個文件饭寺,SP也沒有將多次修改合并為一次阻课,這也是性能差的重要原因之一。

???????總而言之艰匙,SharedPreferences是用來存儲一些非常簡單限煞、輕量的數(shù)據(jù)。我們不要使用它存儲過于復(fù)雜的數(shù)據(jù)员凝,例如 HTML署驻、JSON 等。而且 SharedPreferences 的文件存儲性能與文件大小有關(guān)健霹,每個 SP 文件不能過大旺上,不要將毫無關(guān)聯(lián)的配置項保存在同一個文件中,同時考慮將頻繁修改的條目單獨隔離出來糖埋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宣吱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瞳别,更是在濱河造成了極大的恐慌征候,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祟敛,死亡現(xiàn)場離奇詭異倍奢,居然都是意外死亡,警方通過查閱死者的電腦和手機垒棋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門卒煞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人叼架,你說我怎么就攤上這事畔裕。” “怎么了乖订?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵扮饶,是天一觀的道長。 經(jīng)常有香客問我乍构,道長甜无,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮岂丘,結(jié)果婚禮上陵究,老公的妹妹穿的比我還像新娘。我一直安慰自己奥帘,他們只是感情好铜邮,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著寨蹋,像睡著了一般松蒜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上已旧,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天秸苗,我揣著相機與錄音,去河邊找鬼运褪。 笑死难述,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的吐句。 我是一名探鬼主播胁后,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嗦枢!你這毒婦竟也來了攀芯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤文虏,失蹤者是張志新(化名)和其女友劉穎侣诺,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氧秘,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡年鸳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了丸相。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搔确。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖灭忠,靈堂內(nèi)的尸體忽然破棺而出膳算,到底是詐尸還是另有隱情,我是刑警寧澤弛作,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布涕蜂,位于F島的核電站,受9級特大地震影響映琳,放射性物質(zhì)發(fā)生泄漏机隙。R本人自食惡果不足惜蜘拉,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望有鹿。 院中可真熱鬧旭旭,春花似錦、人聲如沸印颤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽年局。三九已至,卻和暖如春咸产,著一層夾襖步出監(jiān)牢的瞬間矢否,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工脑溢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留僵朗,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓屑彻,卻偏偏與公主長得像验庙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子社牲,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

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