preface
This paper mainly includes the following contents: 1. What are the defects of MMKV? 2. Practical tips for optimizing MMKV calls using Kotlin delegate
If you find this article helpful, please give it a thumbs up. Thank you
MMKV
What are the defects
As we have mentioned before, MMKV has many advantages compared with SharedPreferences. For example, mmap prevents data loss and improves read and write efficiency. 2. Reduce data size by minimizing the amount of data to represent the most information. 3. Incremental update to avoid full write of a large amount of data. MMKV integration and principle SharedPreferences
So what are the disadvantages of MMKV? The main disadvantage of MMKV is that it does not support getAll
publicMap<String, ? > getAll() {throw new UnsupportedOperationException("use allKeys() instead, getAll() not implement because type-erasure inside mmkv");
}
Copy the code
MMKV is stored in bytes, the actual writing of files erases the type, which is why MMKV does not support getAll
Although getAll is not used much, MMKV does not have the ability to export and migrate. For example, when a better storage framework is developed, for example, DataStore is officially released, there is no way to directly migrate from MMKV to the new framework in batches, unless the code has written a number of key migrations, which is very troublesome, so when we introduce MMKV, we should take into account the possible data migration in the future
How to makeMMKV
supportgetAll
?
Since the reason MMKV doesn’t support getAll is because the type is erased, the easiest way to think about it is to add a type suffix to the key
A more elegant approach would be to add a proxy layer to which all read and write operations are delegated and where the key is typed
class SpProxy(private val mmkv: MMKV?) : SharedPreferences, SharedPreferences.Editor {
override fun getAll(a): MutableMap<String, *> {
valkeys = mmkv? .allKeys()valmap = mutableMapOf<String, Any>() keys? .forEach {if (it.contains("@")) {
val typeList = it.split("@")
when (typeList[typeList.size - 1]) {
String::class.simpleName -> map[it] = getString(it, "") ?: ""
Int: :class.simpleName -> map[it] = getInt(it, 0)
Long: :class.simpleName -> map[it] = getLong(it, 0L)
Float: :class.simpleName -> map[it] = getFloat(it, 0f)
Boolean: :class.simpleName -> map[it] = getBoolean(it, false)}}}return map
}
override fun getString(key: String? , defValue:String?).: String? {
val typeKey = getTypeKey<String>(key)
returnmmkv? .getString(typeKey, defValue) }override fun getBoolean(key: String? , defValue:Boolean): Boolean {
val typeKey = getTypeKey<Boolean>(key)
returnmmkv? .getBoolean(typeKey, defValue) ? : defValue } ...override fun contains(key: String?).: Boolean {
val realKey = getRealKey(key)
return realKey.isNotEmpty()
}
override fun edit(a): SharedPreferences.Editor? {
returnmmkv? .edit() }override fun putString(key: String? , value:String?).: SharedPreferences.Editor? {
val typeKey = getTypeKey<String>(key)
returnmmkv? .putString(typeKey, value) }override fun putBoolean(key: String? , value:Boolean): SharedPreferences.Editor? {
val typeKey = getTypeKey<Boolean>(key)
returnmmkv? .putBoolean(typeKey, value) }override fun remove(key: String?).: SharedPreferences.Editor? {
val realKey = getRealKey(key)
if (realKey.isNotEmpty()){
returnmmkv? .remove(realKey) }return null
}
override fun clear(a): SharedPreferences.Editor? {
returnmmkv? .clear() }inline fun <reified T> getTypeKey(key: String?).: String {
val type = "@" + T::class.simpleName
return if(key? .contains(type) ==true) {
type
} else {
key + type
}
}
private fun getRealKey(key: String?).:String{
val typeKys = listOf(getTypeKey<String>(key),getTypeKey<Long>(key),getTypeKey<Float>(key),getTypeKey<Int>(key),getTypeKey<Boolean>(key))
typeKys.forEach {
if(mmkv? .containsKey(it)==true) {return it
}
}
return ""}}Copy the code
All read and write operations are performed based on SpProxy. 2. The getTypeKey method is used to implement the getTypeKey method 3. Support getAll method, convenient for subsequent migration 4. MMKV operations are all encapsulated in SpProxy class, if you want to migrate to other classes, you can modify SpProxy, external can not modify at all
All problems in computer software can be solved by adding an intermediate layer, encapsulating an agent layer for SharedPreferences, which can effectively extend the extensibility of our project
How do I migrate old data
MMKV importFromSharedPreferences method was provided for us to migrated from SharedPreferences MMKV but if invoke this method directly transfer, then the data type is missing, here to provide a new method of migration
fun migrate(migrateSp:SpProxy,preferences: SharedPreferences){
val kvs = preferences.all
if(kvs ! =null && kvs.isNotEmpty()) {
val iterator = kvs.entries.iterator()
while (iterator.hasNext()) {
val entry = iterator.next()
val key = entry.key
val value = entry.value
if(key ! =null&& value ! =null) {
migrateSp.run {
when (value) {
is Boolean -> this.putBoolean(key, value)
is Int -> this.putInt(key,value)
is Long -> this.putLong(key,value)
is Float -> this.putFloat(key, value)
is String -> this.putString(key,value)
else -> {}
}
}
}
}
kvs.size
}
}
Copy the code
As shown above: Support for migrating data from SharedPreferences to MMKV and retaining types
Optimize access operations with delegates
We typically use SharedPreferences to access data like this
private val KEY_DEMO_STR = "key_demo_STR"
fun setDemoStr(str: String) {
edit.putString(KEY_DEMO_STR, str).apply()
}
fun getDemoStr(a): String? {
return preferences.getString(KEY_DEMO_STR, "")}Copy the code
First we need to define a key, then we need to define the set and get methods. Such a simple operation takes eight lines of code, but we can do it in one line using the delegate mechanism
Data access in one line
The optimized code implements data access in one line
object TestSP : PreferenceHolder() {
var value: Long by bindToPreferenceField(0L)}/ / read sp
val value = TestSP.value
println(value) // 0 or 100
/ / in sp
TestSP.value = 100
Copy the code
As shown above, data can be accessed by reading and assigning the value of the variable. The principle behind this is to generate the key from the variable name, Then, the data access operation is entrusted to ReadWriteProperty, and the MMKV or SharedPreferences methods are called in getValue and setValue to realize the real storage and reading
The advantages of this approach are 1. Avoid defining a large number of string keys and duplicate keys 2. Concise delegate mode eliminates the need to write get(..) set(..) 3. If you need to modify the code later, you can modify the delegate class, and other codes in the project do not need to be changed, which enhances the scalability
Preferences Delegate optimization
conclusion
This paper mainly describes the defects of MMKV: that is, the subsequent migration difficulties caused by not supporting getAll and the solutions, and introduces the experience of optimizing data access API calls with delegation
Show Me The Code
This article code can be seen: SpProxy
The resources
SharedPreferences Kotlin should write this to talk about the shortcomings of MMKV and offline debugging tools