SharedPreferences使用性能優化

前言:SharedPreferences是開發中很常見的一個類,它的主要作用是持久化存儲本地的一些輕量級數據,便於我們做一些簡單的數據存儲和邏輯判斷,因爲它簡單和無結構化的特點,對於一般簡單的業務場景來說,比數據庫更加實用,本文主要說明一下在使用過程中的性能優化注意事項。

1、避免存儲大量數據

SharedPreferences設計之初就是爲了提供一個輕量級的數據存儲方案,所以它不能和數據庫相比,如果一個SharedPreferences存放的數據過多,在我們調用get*()的時候就會進行長時間的加載,會影響到主線程,所以儘量保證SharedPreferences中存放的數據不要太多,對於一些結構化的數據我們應該考慮是否使用數據庫進行存放更爲合適。

每次調用apply()或者commit()都是建立一個空文件,然後將所有的數據一次性寫入,而不是增量寫入。可以看看源碼(SharedPreferences是個接口,具體實現由SharedPreferencesImpl實現類完成)

@Override
public boolean commit() {
    long startTime = 0;

    if (DEBUG) {
        startTime = System.currentTimeMillis();
    }

    MemoryCommitResult mcr = commitToMemory();

    SharedPreferencesImpl.this.enqueueDiskWrite(
        mcr, null /* sync write on this thread okay */);
    try {
        mcr.writtenToDiskLatch.await();
    } catch (InterruptedException e) {
        return false;
    } finally {
        if (DEBUG) {
            Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                    + " committed after " + (System.currentTimeMillis() - startTime)
                    + " ms");
        }
    }
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}

所以數據越大耗時就越長,就越有可能造成ANR。

幾乎所有的App都會在Application中使用SharedPreferences做一些邏輯判斷,所以推薦在Application中和MainActivity中使用的SharedPreferences是一個特有的,這個特有的SharedPreferences僅僅存放啓動時必要的少量數據,因爲初始化用到的數據不會太多,最開始SharedPreferences的緩存也是空的,更小的SharedPreferences可以讓取值過程加快,也降低了App首次啓動的內存消耗,提高啓動速度和性能。

2、儘可能提前初始化

SharedPreferences在沒有緩存的時候初始化是比較耗時的,一般寫代碼是這樣的:

sp = context.getSharedPreferences("sp_dwf_user", Context.MODE_PRIVATE);
editor = sp.edit();
editor.putString(key, (String) obj).commit();

這種初始化後立馬操作對象的方式是正常寫法,但是也比較低效。爲了儘可能在edit()之前就讓SharedPreferences加載完成,可以將當前頁面需要的SharedPreferences對象放在activity的onCreate()或者fragment的onCreate()中創建,這樣它就會加載到內存中,建立好緩存,在我們需要用到的時候立馬開始工作。

3、避免key過長

先來看看SharedPreferences中用到的數據結構:
1.ContextImpl中sSharedPrefsCache是一個ArrayMap:

class ContextImpl extends Context {
    ...
    /**
     * Map from package name, to preference name, to cached preferences.
     */
    @GuardedBy("ContextImpl.class")
    private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
	...
}

2.緩存SharedPreferencesImpl的cache是一個ArrayMap:

@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
    SharedPreferencesImpl sp;
    synchronized (ContextImpl.class) {
        final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
        sp = cache.get(file);
        ...
    }
}

3.SharedPreferencesImpl內部的mMap是HashMap:

if (thrown == null) {
    if (map != null) {
        mMap = map;
        mStatTimestamp = stat.st_mtim;
        mStatSize = stat.st_size;
    } else {
        mMap = new HashMap<>();
    }
}

爲什麼一會兒用ArrayMap,一會兒用HashMap呢?

從Android角度來說,當數據有下列特徵時可以選擇ArrayMap:
(1)數據量小(<1000)時,因爲二分查找,所以速度更快。
(2)數據量小,無需考慮擴容的情況。
(3)運行期間不執行或較少執行remove操作。
(4)數據的插入操作十分低頻。
(5)本身包含子map對象,即map中存map的結構。

一般App開發中用到的SharedPreferences個數很有限,一般人不會用超過20個,所以源碼中用到了ArrayMap。而SharedPreferences內部存值用到了HashMap,因爲HashMap的插入和查找效率都很高,並且系統也不知道開發者會存多少數據到SharedPreferences中,所以用HashMap能更好的擴容。

HashMap的數據結構本身就是比較耗內存的,甚至在沒有數據的時候,初始化的時候已經分配了空間,所以會有基礎的內存開銷,而ArrayMap=0。

最後,如果SharedPreferences中存放的key過長,那麼計算hashcode的時間就會加長,這肯定會影響效率,所以一般key不要太長,除非是爲了某些情況取名衝突。

4、多次操作,批量提交

每次代碼提交都是有性能代價的,官方推薦多次修改完成後,進行一次性提交。

一般情況下,SharedPreferences的效率並不會成爲App性能的障礙,舉個典型的例子,用戶退出App時會保存用戶的當前狀態,我們可以將這種大量的相關性數據變成一個javabean對象,方便一次性存取。

5、緩存Editor對象

在SharedPreferencesImpl中,Editor對象的源碼如下:

@Override
public Editor edit() {
    // TODO: remove the need to call awaitLoadedLocked() when
    // requesting an editor.  will require some work on the
    // Editor, but then we should be able to do:
    //
    //      context.getSharedPreferences(..).edit().putString(..).apply()
    //
    // ... all without blocking.
    synchronized (mLock) {
        awaitLoadedLocked();
    }

    return new EditorImpl();
}

也就是說每次調用edit()方法都會new出一個editor對象,這沒有必要,如果我們當前類中有多處使用editor的地方,可以將editor變成類的成員變量,這樣會減少內存波動。

6、不存放HTML和JSON

對於HTML和JSON數據,很多開發者都是直接toString()或者Json.toJsonString()後就放到SharedPreferences中,當然,SharedPreferences是可以存放string類型的數據,但是這樣的數據存放在XML中會出現很多轉義符號,在解析取出值的時候,如果系統碰到特殊符號就會進行特殊的處理,從而引發額外的字符串拼接以及函數調用。這類文件往往都很大,取值時SharedPreferences會將它直接緩存到內存中,進行靜態持有直到App退出時纔會被釋放。

SharedPreferences雖然好用,但是需要思考這些對象是否可以直接存放在磁盤中,她們是否真的需要key-value關係。

7、拆分高頻和低頻操作

儘量避免寫完之後立刻讀值的操作,這種高頻讀寫的操作放在一起就會出現因爲鎖而引起的開銷。對於高頻寫操作的值與高頻讀操作的值,可以適當的拆分到不同的SharedPreferences中,可以減少一個文件中同步鎖的競爭。當然這種情況也不常見,如果項目沒有遇到性能瓶頸,可以忽略。

發佈了88 篇原創文章 · 獲贊 165 · 訪問量 93萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章