[ANR] 優(yōu)化SharedPreference apply引起的ANR問題

前一篇文章[ANR]為什么SharedPreference會引起ANR,我們知道, SharedPreferenceapply方法雖然是異步寫入文件的,但是會在ActivityService生命周期方法調用的時候,等待所有的寫入完成,可能引起卡頓和ANR。

解決方法

為了快速解決這個問題,我們可以通過代理系統(tǒng)SharedPreference的所有apply方法,改為直接在子線程調用commit,即可。下面看代碼。ps:以下代碼可以直接復制使用。

class NoMainThreadWriteSharedPreferences private constructor(private val sysPrefs: SharedPreferences, val name: String) :
    SharedPreferences {

    private val preferencesCache: MutableMap<String, Any?> = HashMap()

    companion object {
        private val executor: ExecutorService = Executors.newSingleThreadExecutor()
        private val INSTANCES: MutableMap<String, NoMainThreadWriteSharedPreferences> = HashMap()

        @JvmStatic
        fun getInstance(sharedPreferences: SharedPreferences, name: String): SharedPreferences {
            return INSTANCES.getOrPut(name, { NoMainThreadWriteSharedPreferences(sharedPreferences, name) })
        }

        @VisibleForTesting
        @JvmStatic
        fun reset() {
            INSTANCES.clear()
        }
    }

    init {
        // 初始化的時候,緩存所有的鍵值對,也可以等到調用getBoolean等獲取key方法的時候,再做
        preferencesCache.putAll(sysPrefs.all)
    }

    override fun contains(key: String?) = preferencesCache[key] != null

    override fun getAll() = HashMap(preferencesCache)

    override fun getBoolean(key: String, defValue: Boolean): Boolean {
        return preferencesCache[key] as Boolean? ?: defValue
    }

    override fun getInt(key: String, defValue: Int): Int {
        return preferencesCache[key] as Int? ?: defValue
    }

    override fun getLong(key: String, defValue: Long): Long {
        return preferencesCache[key] as Long? ?: defValue
    }

    override fun getFloat(key: String, defValue: Float): Float {
        return preferencesCache[key] as Float? ?: defValue
    }

    override fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String>? {
        @Suppress("UNCHECKED_CAST")
        return preferencesCache[key] as MutableSet<String>? ?: defValues
    }

    override fun getString(key: String, defValue: String?): String? {
        return preferencesCache[key] as String? ?: defValue
    }

    override fun edit(): SharedPreferences.Editor {
        return Editor(sysPrefs.edit())
    }

    override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
        sysPrefs.registerOnSharedPreferenceChangeListener(listener)
    }

    override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
        sysPrefs.unregisterOnSharedPreferenceChangeListener(listener)
    }

    inner class Editor(private val sysEdit: SharedPreferences.Editor) : SharedPreferences.Editor {

        private val modifiedData: MutableMap<String, Any?> = HashMap()
        private var keysToRemove: MutableSet<String> = HashSet()
        private var clear = false

        override fun commit(): Boolean {
            submit()
            return true
        }

        // apply和commit都調用submit方法
        override fun apply() {
            submit()
        }

        private fun submit() {
            synchronized(preferencesCache) {
                // 先更新到本地的內存緩存
                storeMemCache()
                // 再用異步線程,調用系統(tǒng)commit方法去更新
                queuePersistentStore()
            }
        }

        private fun storeMemCache() {
            if (clear) {
                preferencesCache.clear()
                clear = false
            } else {
                preferencesCache.keys.removeAll(keysToRemove)
            }
            keysToRemove.clear()
            preferencesCache.putAll(modifiedData)
            modifiedData.clear()
        }

        private fun queuePersistentStore() {
            try {
                executor.submit {
                    sysEdit.commit()
                }
            } catch (ex: Exception) {
            }
        }

        override fun remove(key: String): SharedPreferences.Editor {
            keysToRemove.add(key)
            modifiedData.remove(key)
            sysEdit.remove(key)
            return this
        }

        override fun clear(): SharedPreferences.Editor {
            clear = true
            sysEdit.clear()
            return this
        }

        override fun putLong(key: String, value: Long): SharedPreferences.Editor {
            modifiedData[key] = value
            sysEdit.putLong(key, value)
            return this
        }

        override fun putInt(key: String, value: Int): SharedPreferences.Editor {
            modifiedData[key] = value
            sysEdit.putInt(key, value)
            return this
        }

        override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor {
            modifiedData[key] = value
            sysEdit.putBoolean(key, value)
            return this
        }

        override fun putStringSet(key: String, values: MutableSet<String>?): SharedPreferences.Editor {
            modifiedData[key] = values
            sysEdit.putStringSet(key, values)
            return this
        }

        override fun putFloat(key: String, value: Float): SharedPreferences.Editor {
            modifiedData[key] = value
            sysEdit.putFloat(key, value)
            return this
        }

        override fun putString(key: String, value: String?): SharedPreferences.Editor {
            modifiedData[key] = value
            sysEdit.putString(key, value)
            return this
        }
    }
}

使用的時候,通過在自定義的ApplicationBaseActivity中,修改getSharedPreferences方法,將我們的類作為系統(tǒng)SharedPreference的包裝類返回。

public SharedPreferences getSharedPreferences(String name, int mode) {
  return NoMainThreadWriteSharedPreferences.getInstance(super.getSharedPreferences(name, mode), name);
}

總結

優(yōu)點:

  • 可以快速修復ActivityService生命周期onStartonPause方法,等待SharedPreference寫入文件的ANR問題

缺點:

  • onSharedPreferenceChangeListener的回調會變慢,本來只需要等到寫入內存緩存即可回調,現(xiàn)在需要等待真正寫入文件才能回調

待解決問題:

  • 如果用戶調用commitapply多次,還是會寫入多次,不會合并請求,且SharedPreference的寫入都是完整文件的寫入
  • 沒有解決調用getBoolean等方法獲取value時,等待SharedPreference讀到緩存,引起的ANR問題
  • 沒有解決getPreferencesDir引起的ANR問題
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容