本文主要是介绍SharedPreferences垃圾吗?对比MMKV和DataStore经验之谈,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
SharedPreferences 很垃圾吗? 嗯,他会阻塞主线程。他可能会崩溃,他可能无法提供大内容的存储,性能比较差,ANR等等。
但是是它的错吗?他的设计本意是提供极少的一些变量存储。结果臃肿的代码和封装写法,过度使用导致了很多问题。
如果不想看全篇,看粗体内容看看你是否有共鸣和不了解的地方,查漏补缺。
目前流行的存储有如下几个我这边自行给出个人使用感受:
-
MMKV
通过内存映射,写入文件就是写入内存,读取和存储都极快。稳定性和速度都能经得起考验。它是腾讯出品用于日志存储的,由于是奔溃瞬间也能记录,那么也就只有mmap的特性,进程死了也能存储。但是会有较低概率,彻底损坏,而且是整个文件损坏。所以存储无关紧要的东西最好。如果真实发生损坏的话,丢失了也没什么关系。但是,很多公司和代码都习惯使用mmkv来作为缓存第一方案。比较概率还是很低很低的。
我有做过上架app的程序,也有在系统平台定制rom开发的程序,mmkv丢失没有遇到过的,但是可能并没有及时同步到文件中(比如改变一个配置,立刻关机,下次开机该配置是老的,修改办法是每次有修改都delay10秒去mmkv.async()一下)。因为它要的就是快,如果每次你写入,立刻保存它就退化成了SharedPref了。那么,极度依赖数据不丢失的程序,建议开启自行编写一个策略去备份,比如10天半个月备份一次,引自官方文档:
String backupRootDir = getFilesDir().getAbsolutePath() + "/mmkv_backup_3"; // backup one instance boolean ret = MMKV.backupOneToDirectory(mmapID, backupRootDir, null); // backup all instances long count = MMKV.backupAllToDirectory(backupRootDir);// restore one instance ret = MMKV.restoreOneMMKVFromDirectory(mmapID, backupRootDir, otherDir); // restore all instances count = MMKV.restoreAllFromDirectory(backupRootDir);
那么, 大部分情况,都是比较推荐使用MMKV的。如果你的产品并不在意大部分丢失。比如我们的一个app本身日活可能是每周2次,哪怕是登录信息丢失了。也没什么,用户也会认为过期等再次登录即可。当然是需要你们产品综合考虑的。更何况在一般手机上并不会突然断电,断电也轮不到你的app在工作。
-
SharedPreferences
android有的时候就有它了,基本上代码15年机制没有变化。他的缺点前面已经提到。引用别人的帖子讲的很全:1、不要存放大的key和value在SharedPreferences中,否则会一直存诸在内存中得不到释放,内存使用过高会频发引发GC,导致界面丢帧甚至ANR。
2、不相关的配置选项最好不要放在一起,单个文件越大读取速速度则越慢。
3、读取频繁的key和不频繁的key尽量不要放在一起(如果整个个文件本身就较小则忽略,为了这点性能添加维护得不偿失)。
4、不要每次都edit,因为每次都会创建一个新的Editorlmpl对象象,最好是批量处理统一提交。
否则edit().commit每次创建一个Editorlmpl对象并且进行一次IO操作,严重影响性能。
5、commit发生在Ul线程中,apply发生在工作线程中,对于数据的提交最好是批量操作统一提交。虽然apply发生在工作线程(不会因为IO阻塞UI线程)但是如果添加任
务较多也有可能带来其他严重后果(参照ActivityThread源码中handleStopActivity方法实现)。
6、尽量不要存放大json,大html,这种可以直接文件缓存。 7、不要指望这货能够跨进程通信Context.PROCESS。
8、最好提前初始化SharedPreferences, 避免SharedPreferences第一次创建时读取文件线程未结束而出现等待情况。那么我再补充一点,很多人封装了这种static函数:
public static void putXXX(Context ctx, String key, boolean value) {// name存储文件名称ctx.getSharedPreferences(NAME, Context.MODE_PRIVATE).edit().putXXX(key, value).apply(); } public static boolean getXXX(Context ctx, String key, boolean defValue) {// name存储文件名称return ctx.getSharedPreferences(SPXMLNAME, Context.MODE_PRIVATE).getXXX(key, defValue); }
对吧,但是看了源码你就知道,如果你频繁需要用到这个函数的时候,你每次去创建内部有大量的同步锁,文件读取,创建等待。性能备受挑战。综合这些SP的问题,其实大部分是使用者给他强加了太多的压力了,他本来就是一个小巧的东西。
经验总结:
1. 一定要存的条目少,value要短;条目多了拆文件。
2. 大量读取少量写入的case,一定要结合static变量,避免每次都新建sp(后面我会贴出参考代码)。
3. Application的attachBaseContext里面如果有读取需要,建议考虑使用SP。MMKV可能初始化没好,DataStore又是异步。 -
DataStore
文件存储跟SP一样,会自行备份,安全不丢失。
也解决了SP的各种卡顿,隐形奔溃。
缺点是,项目没有kotlin不行。读取必须是suspend,异步。如果非要同步可以使用runBlocking{}包裹。
稍微复杂点,我已经封装好类。
同样的,如果你比如在application或者首屏有加载需要,考虑拆分文件。
SharedPrefUtils静态类: 前面提到过,少读少取就用这种封装类:
AppDataStore单例类:kotlin项目DataStore的单例帮助类:
MemorySharedPreference类:多读少取就使用这种封装类:
class MyPefUtil {private static String perf; //通过这种方式来提速读取效率。因为这个perf在代码中需要到处读取public static void putPerf(Context ctx, String key, String value) {this.perf = value;// name存储文件名称ctx.getSharedPreferences(NAME, Context.MODE_PRIVATE).edit().putString(key, value).apply();}public static String getXXX(Context ctx, String key) {if (perf != null) {return perf;}// name存储文件名称perf = ctx.getSharedPreferences(NAME, Context.MODE_PRIVATE).getString(key, defValue);return perf;}
}
MemorySharedPreference:
class MemorySharedPreference(private val spName:String, private val applicationContext:Context) {private val sp by lazy { applicationContext.getSharedPreferences(spName, Context.MODE_PRIVATE) }//暂时内存结果。避免过度readprivate val map = ConcurrentHashMap<String?, Any?>(4)fun getString(key: String, defValue: String?): String? {if (map.containsKey(key)) {return map[key].asOrNull()}return sp.getString(key, defValue).also { map[key] = it }}fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String>? {if (map.containsKey(key)) {return map[key].asOrNull()}return sp.getStringSet(key, defValues).also { map[key] = it }}fun getInt(key: String, defValue: Int): Int? {if (map.containsKey(key)) {return map[key].asOrNull()}return sp.getInt(key, defValue).also { map[key] = it }}fun getLong(key: String, defValue: Long): Long {if (map.containsKey(key)) {return map[key].asOrNull() ?: 0}return sp.getLong(key, defValue).also { map[key] = it }}fun getFloat(key: String, defValue: Float): Float {if (map.containsKey(key)) {return map[key].asOrNull() ?: 0f}return sp.getFloat(key, defValue).also { map[key] = it }}fun getBoolean(key: String, defValue: Boolean): Boolean {if (map.containsKey(key)) {return map[key].asOrNull() ?: false}return sp.getBoolean(key, defValue).also { map[key] = it }}fun contains(key: String): Boolean {if (map.containsKey(key)) {return true}return sp.contains(key)}fun putString(key: String, value: String?) {map[key] = valuesp.edit().putString(key, value).apply()}fun putStringSet(key: String, values: MutableSet<String>?) {map[key] = valuessp.edit().putStringSet(key, values).apply()}fun putInt(key: String, value: Int) {map[key] = valuesp.edit().putInt(key, value).apply()}fun putLong(key: String, value: Long) {map[key] = valuesp.edit().putLong(key, value).apply()}fun putFloat(key: String, value: Float) {map[key] = valuesp.edit().putFloat(key, value).apply()}fun putBoolean(key: String, value: Boolean) {map[key] = valuesp.edit().putBoolean(key, value).apply()}fun remove(key: String) {map.remove(key)sp.edit().remove(key).apply()}fun clear() {map.clear()sp.edit().clear().apply()}
}
public class SharedPrefUtil {private static String SPXMLNAME = "sp_config";public static void removeKey(Context ctx, String key) {ctx.getSharedPreferences(SPXMLNAME, Context.MODE_PRIVATE).edit().remove(key).commit();}// 1,存储boolean变量方法public static void putBoolean(Context ctx, String key, boolean value) {// name存储文件名称ctx.getSharedPreferences(SPXMLNAME, Context.MODE_PRIVATE).edit().putBoolean(key, value).apply();}// 2,读取boolean变量方法public static boolean getBoolean(Context ctx, String key, boolean defValue) {// name存储文件名称return ctx.getSharedPreferences(SPXMLNAME, Context.MODE_PRIVATE).getBoolean(key, defValue);}public static void putString(Context ctx, String key, String value) {// name存储文件名称ctx.getSharedPreferences(SPXMLNAME, Context.MODE_PRIVATE).edit().putString(key, value).apply();}public static String getString(Context ctx, String key, String defValue) {// name存储文件名称return ctx.getSharedPreferences(SPXMLNAME, Context.MODE_PRIVATE).getString(key, defValue);}//public static void putInt(Context ctx, String key, int value) {// name存储文件名称ctx.getSharedPreferences(SPXMLNAME, Context.MODE_PRIVATE).edit().putInt(key, value).apply();}public static int getInt(Context ctx, String key, int defValue) {// name存储文件名称return ctx.getSharedPreferences(SPXMLNAME, Context.MODE_PRIVATE).getInt(key, defValue);}
}
object AppDataStore {private const val DATA_STORE_NAME = "dataStore" //对应最终件:/data/data/xxxx/files/datastore/dataStore.preferences_pbprivate later var app:Applicationval Context.dataStore by preferencesDataStore(name = DATA_STORE_NAME,//指定名称
// produceMigrations = {context -> //指定要恢复的sp文件,无需恢复可不写
// listOf(SharedPreferencesMigration(context, SP_PREFERENCES_NAME))
// })suspend inline fun <reified T> containsKey(key:String) : Boolean{val prefKey = when (T::class.java) {Int::class.java -> intPreferencesKey(key)Long::class.java -> longPreferencesKey(key)Double::class.java -> doublePreferencesKey(key)Float::class.java -> floatPreferencesKey(key)Boolean::class.java -> booleanPreferencesKey(key)String::class.java -> stringPreferencesKey(key)Set::class.java -> stringSetPreferencesKey(key)else -> {throw IllegalArgumentException("This type can be removed from DataStore")}}val t = app.dataStore.data.map {it.asMap().forEach { (t, u) ->ALog.t("allData: $t -> $u")}it.contains(prefKey)}.first()return t}fun clear() {runBlocking {app.dataStore.edit {ALog.t("clear!")it.clear()} }}fun save(key:String, value: Any) {runBlocking {ALog.t("save $key <to> $value")saveSuspend(key, value)}}fun save(vararg pair:Pair<String, Any>) {runBlocking {pair.forEach {saveSuspend(it.first, it.second)}}}inline fun <reified T> remove(key:String) {runBlocking {removeSuspend<T>(key)}}suspend inline fun <reified T> removeSuspend(key:String) : T?{var ret : T? = nullapp.dataStore.edit { setting ->ret = when (T::class.java) {Int::class.java -> setting.remove(intPreferencesKey(key)).asOrNull()Long::class.java -> setting.remove(longPreferencesKey(key)).asOrNull()Double::class.java -> setting.remove(doublePreferencesKey(key)).asOrNull()Float::class.java -> setting.remove(floatPreferencesKey(key)).asOrNull()Boolean::class.java -> setting.remove(booleanPreferencesKey(key)).asOrNull()String::class.java -> setting.remove(stringPreferencesKey(key)).asOrNull()Set::class.java -> setting.remove(stringSetPreferencesKey(key)).asOrNull() //later: 这里不做二次检查了。默认就认为是stringSetelse -> {throw IllegalArgumentException("This type can be removed from DataStore")}}}return ret}/*** 因为我们用于保存,不应该使用lifeCycleScope来发起。有可能无法保存成功。应该使用全局scope。*/@Deprecated("不建议直接使用,因为可能协程被取消,除非你明白你的scope一定保存成功")suspend fun saveSuspend(key:String, value:Any) {app.dataStore.edit { setting ->when (value) {is Int -> setting[intPreferencesKey(key)] = valueis Long -> setting[longPreferencesKey(key)] = valueis Double -> setting[doublePreferencesKey(key)] = valueis Float -> setting[floatPreferencesKey(key)] = valueis Boolean -> setting[booleanPreferencesKey(key)] = valueis String -> setting[stringPreferencesKey(key)] = valueis Set<*> -> {val componentType = value::class.java.componentType!!@Suppress("UNCHECKED_CAST") // Checked by reflection.when {String::class.java.isAssignableFrom(componentType) -> {app.dataStore.edit { preferences ->preferences[stringSetPreferencesKey(key)] = value as Set<String>}}}}else -> {throw IllegalArgumentException("This type can be saved into DataStore")}}}}/*** 获取数据* */suspend inline fun < reified T : Any> read(key: String, defaultValue:T): T {return when (T::class) {Int::class -> {app.dataStore.data.map { setting ->setting[intPreferencesKey(key)] ?: defaultValue}.first() as T}Long::class -> {app.dataStore.data.map { setting ->setting[longPreferencesKey(key)] ?: defaultValue}.first() as T}Double::class -> {app.dataStore.data.map { setting ->setting[doublePreferencesKey(key)] ?:defaultValue}.first() as T}Float::class -> {app.dataStore.data.map { setting ->setting[floatPreferencesKey(key)] ?:defaultValue}.first() as T}Boolean::class -> {app.dataStore.data.map { setting ->setting[booleanPreferencesKey(key)]?:defaultValue}.first() as T}String::class -> {app.dataStore.data.map { setting ->setting[stringPreferencesKey(key)] ?: defaultValue}.first() as T}else -> {throw IllegalArgumentException("This type can be get into DataStore")}}}
}
这篇关于SharedPreferences垃圾吗?对比MMKV和DataStore经验之谈的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!