前一篇文章[ANR]为什么SharedPreference会引起ANR,我们知道, SharedPreference
的apply
方法虽然是异步写入文件的,但是会在Activity
和Service
生命周期方法调用的时候,等待所有的写入完成,可能引起卡顿和ANR。
解决方法
为了快速解决这个问题,我们可以通过代理系统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()
// 再用异步线程,调用系统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
}
}
}
使用的时候,通过在自定义的Application
和BaseActivity
中,修改getSharedPreferences
方法,将我们的类作为系统SharedPreference
的包装类返回。
public SharedPreferences getSharedPreferences(String name, int mode) {
return NoMainThreadWriteSharedPreferences.getInstance(super.getSharedPreferences(name, mode), name);
}
总结
优点:
- 可以快速修复
Activity
和Service
生命周期onStart
和onPause
方法,等待SharedPreference
写入文件的ANR问题
缺点:
-
onSharedPreferenceChangeListener
的回调会变慢,本来只需要等到写入内存缓存即可回调,现在需要等待真正写入文件才能回调
待解决问题:
- 如果用户调用
commit
和apply
多次,还是会写入多次,不会合并请求,且SharedPreference
的写入都是完整文件的写入 - 没有解决调用
getBoolean
等方法获取value
时,等待SharedPreference
读到缓存,引起的ANR问题 - 没有解决
getPreferencesDir
引起的ANR问题