美文网首页Android
[ANR] 优化SharedPreference apply引起

[ANR] 优化SharedPreference apply引起

作者: 尹学姐 | 来源:发表于2023-02-16 20:12 被阅读0次

    前一篇文章[ANR]为什么SharedPreference会引起ANR,我们知道, SharedPreferenceapply方法虽然是异步写入文件的,但是会在ActivityService生命周期方法调用的时候,等待所有的写入完成,可能引起卡顿和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
            }
        }
    }
    

    使用的时候,通过在自定义的ApplicationBaseActivity中,修改getSharedPreferences方法,将我们的类作为系统SharedPreference的包装类返回。

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

    总结

    优点:

    • 可以快速修复ActivityService生命周期onStartonPause方法,等待SharedPreference写入文件的ANR问题

    缺点:

    • onSharedPreferenceChangeListener的回调会变慢,本来只需要等到写入内存缓存即可回调,现在需要等待真正写入文件才能回调

    待解决问题:

    • 如果用户调用commitapply多次,还是会写入多次,不会合并请求,且SharedPreference的写入都是完整文件的写入
    • 没有解决调用getBoolean等方法获取value时,等待SharedPreference读到缓存,引起的ANR问题
    • 没有解决getPreferencesDir引起的ANR问题

    相关文章

      网友评论

        本文标题:[ANR] 优化SharedPreference apply引起

        本文链接:https://www.haomeiwen.com/subject/xznukdtx.html