SharedPreference用法及源碼分析

什么是SharedPreference

SharedPreference(以下簡(jiǎn)稱SP)是Android提供的一個(gè)輕量級(jí)的持久化存儲(chǔ)框架撕攒,主要用于保存一些比較小的數(shù)據(jù)己英,例如配置數(shù)據(jù)吱七。SP是以“健-值”對(duì)的形式來(lái)保存數(shù)據(jù)契邀,其實(shí)質(zhì)是把這些數(shù)據(jù)保存到XML文件中伴挚,每個(gè)“健-值”對(duì)就是XML文件的一個(gè)節(jié)點(diǎn)阎抒,通過(guò)調(diào)用SP生成的文件最終會(huì)放在手機(jī)內(nèi)部存儲(chǔ)的/data/data/<package name>/shared_prefs目錄下醒叁。

如何使用SharedPreference

獲取SharedPreference

使用SP的第一步是獲取SP對(duì)象司浪。在Android中,我們可以通過(guò)以下三種方式來(lái)獲取SP對(duì)象把沼。

1.Context類中的getSharedPreferences方法
    public SharedPreferences getSharedPreferences(String name, int mode) {
          ...
    }

這個(gè)方法接收兩個(gè)參數(shù)啊易,第一個(gè)參數(shù)是SP的文件名,我們知道SP是以XML文件的形式進(jìn)行存儲(chǔ)的饮睬,每一個(gè)SharedPreference實(shí)例都對(duì)應(yīng)了一個(gè)XML文件租谈,這里的name就是XML文件的名字。第二個(gè)參數(shù)用于指定文件的操作模式捆愁,最開(kāi)始的時(shí)候SP是可以跨進(jìn)程訪問(wèn)的割去,所以SP有MODE_PRIVATE,MODE_WORLD_READABLE昼丑,MODE_MULTI_PROCESS等多種操作模式呻逆,只不過(guò)出于安全性考慮,谷歌目前只保留了MODE_PRIVATE這一種模式菩帝,其他模式均已被廢棄咖城。在MODE_PRIVATE模式下茬腿,只有應(yīng)用本身可以訪問(wèn)SharedPreference文件,其他應(yīng)用無(wú)權(quán)訪問(wèn)宜雀。

2.Activity的getPreferences方法
   public SharedPreferences getPreferences(int mode) {
       return getSharedPreferences(getLocalClassName(), mode);
   }

這個(gè)方法只接收一個(gè)參數(shù)切平,即SP文件的操作模式,那SharedPreference的名字是啥呢州袒,通過(guò)源碼可以看到揭绑,這里使用了當(dāng)前類的類名來(lái)作為SP的文件名。例如郎哭,當(dāng)前類名為MainActivity他匪,那么對(duì)應(yīng)SP的文件名就是MainActivity.xml。

3.PreferenceManager的getDefaultSharedPreferences方法
    public static SharedPreferences getDefaultSharedPreferences(Context context) {
        return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
       getDefaultSharedPreferencesMode());
    }

    private static int getDefaultSharedPreferencesMode() {
        return Context.MODE_PRIVATE;
    }

    public static String getDefaultSharedPreferencesName(Context context) {
        return context.getPackageName() + "_preferences";
    }

這個(gè)方法接收Context參數(shù)夸研,并使用當(dāng)前包名_preferences作為SP的文件名邦蜜。使用MODE_PRIVATE作為操作模式。
以上三個(gè)方法其實(shí)大同小異亥至,主要區(qū)別在于最終生成的SP的文件名有差異悼沈。

使用SharedPreference進(jìn)行讀寫(xiě)數(shù)據(jù)
1.讀取數(shù)據(jù)

使用SharedPreference讀取數(shù)據(jù)很簡(jiǎn)單,分為兩個(gè)步驟:
(1) 獲取SharedPreference對(duì)象(使用上述的三種方式)
(2) 調(diào)用SharedPreference對(duì)象的get方法讀取對(duì)應(yīng)類型的數(shù)據(jù)姐扮。

2.寫(xiě)入數(shù)據(jù)

使用SharedPreference寫(xiě)入數(shù)據(jù)分為四步:
(1) 獲取SharedPreference對(duì)象
(2) 獲取SharedPreferences.Editor對(duì)象
(3) 調(diào)用SharedPreferences.Editor對(duì)象的put方法寫(xiě)入數(shù)據(jù)
(4) 調(diào)用SharedPreferences.Editor對(duì)象的apply/commit方法提交更改

示例

我們平常在登陸賬號(hào)的時(shí)候一般都會(huì)有一個(gè)記住密碼的功能絮供,下面就用SP來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的記住登陸密碼的功能。代碼很簡(jiǎn)單茶敏,不做過(guò)多解釋來(lái)壤靶。

<!--activity_main.xml-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:textSize="18sp"
            android:text="Account" />

        <EditText
            android:id="@+id/account"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:textSize="18sp"
            android:text="Password" />

        <EditText
            android:id="@+id/password"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight = "1"
            android:layout_gravity = "center_vertical"
            android:inputType="textPassword"/>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <CheckBox
            android:id="@+id/remember_password"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="remember password"/>

    </LinearLayout>

    <Button
        android:id="@+id/login"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="login"/>
</LinearLayout>
//MainActivity.java
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {
    private EditText mAccountEdit;
    private EditText mPasswordEdit;
    private Button mLoginBtn;
    private CheckBox mRememberPasswordCbx;
    private SharedPreferences mSharedPreferences;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAccountEdit = findViewById(R.id.account);
        mPasswordEdit = findViewById(R.id.password);
        mLoginBtn = findViewById(R.id.login);
        mRememberPasswordCbx = findViewById(R.id.remember_password);

        mSharedPreferences = getSharedPreferences("admin",MODE_PRIVATE);
        boolean isRememberPassword = mSharedPreferences.getBoolean("RememberPassword",false);
        if(isRememberPassword){
            String account = mSharedPreferences.getString("Account","");
            String password = mSharedPreferences.getString("Password","");
            mAccountEdit.setText(account);
            mPasswordEdit.setText(password);
            mRememberPasswordCbx.setChecked(true);
        }

        mLoginBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String account = mAccountEdit.getText().toString();
                String password = mPasswordEdit.getText().toString();
                SharedPreferences.Editor edit = mSharedPreferences.edit();
                if (account.equals("admin") && password.equals("888888")) {
                    if(mRememberPasswordCbx.isChecked()){
                        edit.putString("Account",account);
                        edit.putString("Password",password);
                        edit.putBoolean("RememberPassword",true);
                    }else {
                        edit.clear();
                    }
                    edit.apply();
                    Intent intent = new Intent(MainActivity.this, UserActivity.class);
                    startActivity(intent);
                }else {
                    Toast.makeText(MainActivity.this,"賬號(hào)或者密碼錯(cuò)誤",Toast.LENGTH_LONG).show();
                }
            }
        });
    }
}

<!--activity_user.xml-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:text="login success"/>

</LinearLayout>
//UserActivity
import android.app.Activity;
import android.os.Bundle;

public class UserActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user);
    }

}
SharedPreference源碼分析
1.獲取對(duì)象

首先看下獲取SP對(duì)象的源碼。無(wú)論使用哪種方式來(lái)獲取SP對(duì)象惊搏,最終都是通過(guò)調(diào)用SharedPreferencesImpl來(lái)構(gòu)建SP對(duì)象的贮乳。創(chuàng)建SP對(duì)象代碼如下。

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

這個(gè)方法主要就是定義了一個(gè)備份文件對(duì)象恬惯,然后調(diào)用了startLoadFromDisk方法向拆,繼續(xù)來(lái)看startLoadFromDisk方法。

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

這里調(diào)用了loadFromDisk方法酪耳,開(kāi)啟了一個(gè)異步線程浓恳,因?yàn)榧虞dSP文件是IO耗時(shí)操作,不能放在主線程碗暗,否則會(huì)導(dǎo)致主線程阻塞颈将。繼續(xù)看loadFromDisk方法。

    private void loadFromDisk() {
        synchronized (mLock) {
            if (mLoaded) {
                return;
            }
            if (mBackupFile.exists()) {
                mFile.delete();
                mBackupFile.renameTo(mFile);
            }
        }
        Map<String, Object> map = null;
        try {
            if (mFile.canRead()) {
                BufferedInputStream str = null;
                try {
                    str = new BufferedInputStream(new FileInputStream(mFile), 16 * 1024);
                    map = (Map<String, Object>) XmlUtils.readMapXml(str);
                } 
                ...
            }
        } 
        ...
        synchronized (mLock) {
            mLoaded = true;
            try {   
                if (map != null) {
                    mMap = map;
                } else {
                    mMap = new HashMap<>();
                }              
            } catch (Throwable t) {
       
            } finally {
                mLock.notifyAll();
            }
        }
    }

這里省略了部分代碼讹堤,可以看到如果有備份文件,SP會(huì)優(yōu)先使用備份文件厨疙,然后就是讀取并解析XML文件洲守,通過(guò) XmlUtils.readMapXml方法讀取XML文件并解析成Map對(duì)象疑务,這里就是創(chuàng)建SP對(duì)象的關(guān)鍵,也就是說(shuō)創(chuàng)建SP對(duì)象的過(guò)程其實(shí)就是把SP文件加載到Map(內(nèi)存)中的過(guò)程梗醇。加載完成之后知允,會(huì)調(diào)用mLock同步鎖的notifyAll方法,來(lái)使其他阻塞在這個(gè)同步鎖的線程解除阻塞叙谨。同時(shí)温鸽,把mLoaded置為true,表示加載文件完成手负。到此涤垫,創(chuàng)建SP對(duì)象的過(guò)程就結(jié)束了,我們最終得到了一個(gè)Map竟终,后續(xù)的讀取操作都會(huì)基于這個(gè)Map來(lái)進(jìn)行蝠猬。
從上面過(guò)程可以看到SharedPreference最終會(huì)以Map的形式加載到內(nèi)存中,所以SharedPreference適合用于存儲(chǔ)小數(shù)據(jù)统捶,并不適合存儲(chǔ)較大的數(shù)據(jù)榆芦。否則一方面會(huì)消耗內(nèi)存,一方面在加載文件的過(guò)程可能導(dǎo)致主線程阻塞喘鸟。

2.讀取數(shù)據(jù)

創(chuàng)建SP對(duì)象完成后匆绣,我們實(shí)際上獲得來(lái)一個(gè)裝載SP數(shù)據(jù)的Map,讀取數(shù)據(jù)的過(guò)程實(shí)際就是從Map取數(shù)據(jù)的過(guò)程什黑,以getString方法為例崎淳。

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

這里讀取數(shù)據(jù)不難理解,就是Map的get操作兑凿,有一個(gè)地方需要注意的凯力,就是awaitLoadedLocked方法。我們看一下這個(gè)方法礼华。

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

從上面分析我們可以知道咐鹤,mLoaded表示SP文件是加載完成,如果沒(méi)有加載完成圣絮,這個(gè)方法就會(huì)進(jìn)入while循環(huán)祈惶,并調(diào)用mLock.wait()來(lái)阻塞當(dāng)前線程。在loadFromDisk方法中我們可以看到扮匠,當(dāng)加載文件完成后捧请,會(huì)調(diào)用 mLock.notifyAll()來(lái)使其他阻塞在mLock同步鎖的線程解除阻塞。所以棒搜,等到SP文件加載完成后疹蛉,這個(gè)方法就會(huì)解除阻塞,如果沒(méi)有讀取完成力麸,調(diào)用getString的線程會(huì)阻塞在這個(gè)同步鎖上可款。這也解釋來(lái)為什么在第一次從SP讀取數(shù)據(jù)的時(shí)候有可能會(huì)耗時(shí)比較久育韩,后面讀取數(shù)據(jù)幾乎不耗時(shí)。就是因?yàn)镾P文件沒(méi)有加載完成闺鲸,導(dǎo)致線程阻塞引起的筋讨,后續(xù)讀取因?yàn)槎际侵苯訌膬?nèi)存中(mMap)中讀取,所以幾乎不會(huì)耗時(shí)摸恍。

2.寫(xiě)入數(shù)據(jù)

在向SP寫(xiě)入數(shù)據(jù)的時(shí)候悉罕,我們首先獲取了一個(gè)Editor對(duì)象,這個(gè)Editor對(duì)象的作用是什么呢立镶?來(lái)看下獲取Editor對(duì)象的源碼壁袄。

    public Editor edit() {
        synchronized (mLock) {
            awaitLoadedLocked();
        }

        return new EditorImpl();
    }

這里和讀取數(shù)據(jù)一樣,首先也是調(diào)用awaitLoadedLocked方法來(lái)等待SP文件加載完成谜慌。然后就是調(diào)用EditorImpl來(lái)創(chuàng)建editor對(duì)象然想。看一下EditorImpl類的定義欣范。

public final class EditorImpl implements Editor {
        private final Object mEditorLock = new Object();
        
        private final Map<String, Object> mModified = new HashMap<>();

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

       @Override
        public void commit() {
          ...
        }
}

這個(gè)類很簡(jiǎn)單变泄,主要就是創(chuàng)建來(lái)一個(gè)Map(mModified),并定義來(lái)一些put方法恼琼,還有就是定義來(lái)一個(gè)apply方法和一個(gè)commit方法妨蛹。
在向SharedPreference寫(xiě)入數(shù)據(jù)的時(shí)候,我們是調(diào)用editor的put方法來(lái)寫(xiě)入數(shù)據(jù)的晴竞,以putString方法為例蛙卤。

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

這里可以看到,寫(xiě)入數(shù)據(jù)時(shí)噩死,并沒(méi)有把數(shù)據(jù)直接寫(xiě)如文件颤难,而是把數(shù)據(jù)放在了mModified這個(gè)表里邊,這個(gè)表是在內(nèi)存里的已维。
執(zhí)行寫(xiě)入數(shù)據(jù)的最后一步是調(diào)用editor的supply/commit方法來(lái)提交變更行嗤,那么這兩個(gè)方法有什么區(qū)別呢?首先來(lái)看一下commit方法垛耳。

        public boolean commit() {
            ...
            MemoryCommitResult mcr = commitToMemory();
            SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null /* sync write on this thread okay */);
            ...
        }

commit方法首先是調(diào)用commitToMemory構(gòu)造存儲(chǔ)對(duì)象栅屏,然后調(diào)用enqueueDiskWrite將進(jìn)行持久化存儲(chǔ)。首先來(lái)看一下commitToMemory方法

        private MemoryCommitResult commitToMemory() {
            long memoryStateGeneration;
            List<String> keysModified = null;
            Set<OnSharedPreferenceChangeListener> listeners = null;
            Map<String, Object> mapToWriteToDisk;
               mapToWriteToDisk = mMap; 
                synchronized (mEditorLock) {
                    boolean changesMade = false;
                    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 {
                            if (mapToWriteToDisk.containsKey(k)) {
                                Object existingValue = mapToWriteToDisk.get(k);
                                if (existingValue != null && existingValue.equals(v)) {
                                    continue;
                                }
                            }
                            mapToWriteToDisk.put(k, v);
                        }
                 }
        }

我們最終向文件寫(xiě)入的內(nèi)容是mapToWriteToDisk堂鲜,這個(gè)map包含兩部分栈雳,第一部分是創(chuàng)建SP對(duì)象時(shí)從文件加載到內(nèi)存的map,第二部分是創(chuàng)建editor對(duì)象的時(shí)創(chuàng)建的mModified缔莲,editor的所有put操作都是放在了這個(gè)map里邊哥纫,把兩個(gè)map合并之后就得到了最終要向文件寫(xiě)入的map,所以SP每次提交數(shù)據(jù)修改并不是增量寫(xiě)入數(shù)據(jù)痴奏,而是將新增數(shù)據(jù)和原有數(shù)據(jù)合并之后全量寫(xiě)入蛀骇。
后面接著看enqueueDiskWrite方法奖慌。

    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) {
                        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;
            }
        }

        QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
    }

這里首先創(chuàng)建了一個(gè)Runnable對(duì)象writeToDiskRunnable,在這個(gè)對(duì)象的run方法里邊執(zhí)行文件寫(xiě)入操作松靡。然后如果isFromSyncCommit為true且當(dāng)前只有一個(gè)寫(xiě)入操作,就直接在當(dāng)前線程執(zhí)行writeToDiskRunnable的run方法建椰,也就是說(shuō)在當(dāng)前線程執(zhí)行寫(xiě)入文件操作雕欺。否則就傳入QueuedWork進(jìn)行異步寫(xiě)入。那么isFromSyncCommit什么時(shí)候?yàn)閠rue呢棉姐,就是在postWriteRunnable=null的時(shí)候屠列,這時(shí)再回頭看commit方法,這個(gè)方法在調(diào)用enqueueDiskWrite方法時(shí)伞矩,postWriteRunnable參數(shù)傳入的是null笛洛,看到這里也就明白了,commit是同步IO操作乃坤,也就是在調(diào)用commit方法的線程直接執(zhí)行寫(xiě)入操作苛让。
再來(lái)看apply方法。

        public void apply() {
            final long startTime = System.currentTimeMillis();

            final MemoryCommitResult mcr = commitToMemory();
            final Runnable awaitCommit = new Runnable() {
                    @Override
                    public void run() {
                        try {
                            mcr.writtenToDiskLatch.await();
                        } catch (InterruptedException ignored) {
                        }
                    }
                };

            QueuedWork.addFinisher(awaitCommit);

            Runnable postWriteRunnable = new Runnable() {
                    @Override
                    public void run() {
                        awaitCommit.run();
                        QueuedWork.removeFinisher(awaitCommit);
                    }
                };

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

分析過(guò)commit方法之后湿诊,apply方法就很簡(jiǎn)單了狱杰,首先apply方法也是構(gòu)建了一個(gè)mcr對(duì)象,然后定義了一個(gè)postWriteRunnable對(duì)象并調(diào)用了enqueueDiskWrite方法厅须,根據(jù)上面對(duì)enqueueDiskWrite方法的分析仿畸,postWriteRunnable!=null會(huì)使isFromSyncCommit為false,進(jìn)而在異步線程執(zhí)行文件寫(xiě)入操作朗和。
所以在使用SharedPreference存儲(chǔ)數(shù)據(jù)的時(shí)候错沽,最好使用apply方法提交修改,而不是commit眶拉,因?yàn)閏ommit是在當(dāng)前線程執(zhí)行IO操作千埃,有可能會(huì)導(dǎo)致線程卡頓甚至出現(xiàn)ANR。而apply是異步寫(xiě)入的镀层,不會(huì)阻塞當(dāng)前線程執(zhí)行镰禾。

使用SharedPreference的建議
  • 不要使用SP存儲(chǔ)大文件及存儲(chǔ)大量的key和value,因?yàn)樽罱KSharedPreference是會(huì)把所有數(shù)據(jù)加載到內(nèi)存的唱逢,存儲(chǔ)大數(shù)據(jù)或者大量數(shù)據(jù)會(huì)造成界面卡頓或者ANR吴侦,SP是輕量級(jí)存儲(chǔ)框架,如果要存儲(chǔ)較大數(shù)據(jù)坞古,請(qǐng)考慮數(shù)據(jù)庫(kù)或者文件存儲(chǔ)方式备韧。
  • apply進(jìn)行存儲(chǔ),而不是commit方法痪枫,因?yàn)閍pply是異步寫(xiě)入磁盤(pán)的织堂,所以效率上會(huì)比commit好叠艳,但是如果需要即存即用的話還是盡量使用commit。
  • 如果修改數(shù)據(jù)易阳,盡量批量寫(xiě)入后再調(diào)用apply或者commit附较,從源碼分析可以看到,無(wú)論是apply或者是commit潦俺,都是將修改的數(shù)據(jù)和原有數(shù)據(jù)合并拒课,并執(zhí)行全量寫(xiě)入操作贸典。多次調(diào)用apply或者commit不僅會(huì)發(fā)起多次IO操作继效,還會(huì)導(dǎo)致不必要的數(shù)據(jù)寫(xiě)入。
  • 不要把所有數(shù)據(jù)都存儲(chǔ)在一個(gè)SP文件里邊掠归,SP文件越大肖爵,讀寫(xiě)速度越慢卢鹦。因此,不同功能模塊的數(shù)據(jù)最好用不同的文件存儲(chǔ)劝堪,這樣可以提高SP的加載和寫(xiě)入速度冀自。。
  • 盡量不要存儲(chǔ)json或者h(yuǎn)tml數(shù)據(jù)秒啦,因?yàn)閖son或者h(yuǎn)tml在存儲(chǔ)時(shí)會(huì)引來(lái)額外的字符轉(zhuǎn)義開(kāi)銷凡纳,如果數(shù)據(jù)比較大,會(huì)大大降低sp的讀取速度帝蒿。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荐糜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子葛超,更是在濱河造成了極大的恐慌暴氏,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绣张,死亡現(xiàn)場(chǎng)離奇詭異答渔,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)侥涵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門沼撕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人芜飘,你說(shuō)我怎么就攤上這事务豺。” “怎么了嗦明?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵笼沥,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)奔浅,這世上最難降的妖魔是什么馆纳? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮汹桦,結(jié)果婚禮上鲁驶,老公的妹妹穿的比我還像新娘。我一直安慰自己舞骆,他們只是感情好灵嫌,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著葛作,像睡著了一般。 火紅的嫁衣襯著肌膚如雪猖凛。 梳的紋絲不亂的頭發(fā)上赂蠢,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音辨泳,去河邊找鬼虱岂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛菠红,可吹牛的內(nèi)容都是我干的第岖。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼试溯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蔑滓!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起遇绞,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤键袱,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后摹闽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蹄咖,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年付鹿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了澜汤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡舵匾,死狀恐怖俊抵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坐梯,我是刑警寧澤务蝠,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響馏段,放射性物質(zhì)發(fā)生泄漏轩拨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一院喜、第九天 我趴在偏房一處隱蔽的房頂上張望亡蓉。 院中可真熱鬧,春花似錦喷舀、人聲如沸砍濒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)爸邢。三九已至,卻和暖如春拿愧,著一層夾襖步出監(jiān)牢的瞬間杠河,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工浇辜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留券敌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓柳洋,卻偏偏與公主長(zhǎng)得像待诅,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子熊镣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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