這篇文章主要是對MMKV進行封裝,由此瞭解一些Kotlin特性,建議對着示例代碼閱讀文章,示例代碼以下:java
MMKVDemoandroid
其實在MMKV的Wiki中已經有很詳細的介紹了,地址以下:git
MMKV for Android官方Wikigithub
MMKV是基於mmap內存映射的key-value組件,底層序列化/反序列化使用protobuf實現,性能高,穩定性強,並且Android這邊還支持多進程。編程
寫入性能安全
MMKV遠超於SharedPreferences和SQLite。閉包
讀取性能併發
MMKV與SharedPreferences相近,好於SQLite。app
寫入性能框架
MMKV遠超於MultiProcessSharedPreferences和SQLite。
讀取性能
MMKV遠超於MultiProcessSharedPreferences和SQLite。
mmap是一種內存映射的方法,它能夠將對象或者文件映射到地址空間,實現文件磁盤地址和進程虛擬地址空間中的一段虛擬地址的一一對映關係,實現了這種映射關係後,進程能夠採用指針的方式讀寫操做一段內存,而系統會自動回寫髒頁面到對應的文件磁盤上,這樣就完成了對文件的操做,而不須要再去調用write、read等系統調用函數,同時內核空間對這段區域的修改也直接反映用戶空間,從而能夠實現不一樣進程間的文件共享。
Preferences.kt,代碼以下:
package com.tanjiajun.mmkvdemo.utils
import android.os.Parcelable
import com.tencent.mmkv.MMKV
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
/** * Created by TanJiaJun on 2020-01-11. */
private inline fun <T> MMKV.delegate( key: String? = null, defaultValue: T, crossinline getter: MMKV.(String, T) -> T,
crossinline setter: MMKV.(String, T) -> Boolean
): ReadWriteProperty<Any, T> =
object : ReadWriteProperty<Any, T> {
override fun getValue(thisRef: Any, property: KProperty<*>): T =
getter(key ?: property.name, defaultValue)
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
setter(key ?: property.name, value)
}
}
fun MMKV.boolean( key: String? = null, defaultValue: Boolean = false ): ReadWriteProperty<Any, Boolean> =
delegate(key, defaultValue, MMKV::decodeBool, MMKV::encode)
fun MMKV.int(key: String? = null, defaultValue: Int = 0): ReadWriteProperty<Any, Int> =
delegate(key, defaultValue, MMKV::decodeInt, MMKV::encode)
fun MMKV.long(key: String? = null, defaultValue: Long = 0L): ReadWriteProperty<Any, Long> =
delegate(key, defaultValue, MMKV::decodeLong, MMKV::encode)
fun MMKV.float(key: String? = null, defaultValue: Float = 0.0F): ReadWriteProperty<Any, Float> =
delegate(key, defaultValue, MMKV::decodeFloat, MMKV::encode)
fun MMKV.double(key: String? = null, defaultValue: Double = 0.0): ReadWriteProperty<Any, Double> =
delegate(key, defaultValue, MMKV::decodeDouble, MMKV::encode)
private inline fun <T> MMKV.nullableDefaultValueDelegate( key: String? = null, defaultValue: T?, crossinline getter: MMKV.(String, T?) -> T,
crossinline setter: MMKV.(String, T) -> Boolean
): ReadWriteProperty<Any, T> =
object : ReadWriteProperty<Any, T> {
override fun getValue(thisRef: Any, property: KProperty<*>): T =
getter(key ?: property.name, defaultValue)
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
setter(key ?: property.name, value)
}
}
fun MMKV.byteArray( key: String? = null, defaultValue: ByteArray? = null ): ReadWriteProperty<Any, ByteArray> =
nullableDefaultValueDelegate(key, defaultValue, MMKV::decodeBytes, MMKV::encode)
fun MMKV.string(key: String? = null, defaultValue: String? = null): ReadWriteProperty<Any, String> =
nullableDefaultValueDelegate(key, defaultValue, MMKV::decodeString, MMKV::encode)
fun MMKV.stringSet( key: String? = null, defaultValue: Set<String>? = null ): ReadWriteProperty<Any, Set<String>> =
nullableDefaultValueDelegate(key, defaultValue, MMKV::decodeStringSet, MMKV::encode)
inline fun <reified T : Parcelable> MMKV.parcelable( key: String? = null, defaultValue: T? = null ): ReadWriteProperty<Any, T> =
object : ReadWriteProperty<Any, T> {
override fun getValue(thisRef: Any, property: KProperty<*>): T =
decodeParcelable(key ?: property.name, T::class.java, defaultValue)
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
encode(key ?: property.name, value)
}
}
複製代碼
用法以下:
package com.tanjiajun.mmkvdemo.ui.activity
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.tanjiajun.mmkvdemo.R
import com.tanjiajun.mmkvdemo.data.model.UserData
import com.tanjiajun.mmkvdemo.utils.*
import com.tencent.mmkv.MMKV
/** * Created by TanJiaJun on 2020-01-14. */
class MainActivity : AppCompatActivity() {
private val mmkv: MMKV by lazy { MMKV.defaultMMKV() }
private var boolean by mmkv.boolean(key = "boolean", defaultValue = false)
private var int by mmkv.int(key = "int", defaultValue = 0)
private var long by mmkv.long("long", 0L)
private var float by mmkv.float(key = "float", defaultValue = 0.0F)
private var double by mmkv.double(key = "double", defaultValue = 0.0)
private var byteArray by mmkv.byteArray(key = "byteArray")
private var string by mmkv.string(key = "string")
private var stringSet by mmkv.stringSet(key = "stringSet")
private var parcelable by mmkv.parcelable<UserData>("parcelable")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
boolean = true
int = 100
long = 100L
float = 100F
double = 100.0
byteArray = ByteArray(100).apply {
for (i in 0 until 100) {
set(i, i.toByte())
}
}
string = "譚嘉俊"
stringSet = HashSet<String>().apply {
for (i in 0 until 100) {
add("第($i)個")
}
}
parcelable = UserData(name = "譚嘉俊", gender = "男", age = 26)
Log.i(TAG, "boolean:$boolean")
Log.i(TAG, "int:$int")
Log.i(TAG, "long:$long")
Log.i(TAG, "float:$float")
Log.i(TAG, "double:$double")
Log.i(TAG, "byteArray:$byteArray")
Log.i(TAG, "string:$string")
Log.i(TAG, "stringSet:$stringSet")
Log.i(TAG, "parcelable:$parcelable")
}
private companion object {
const val TAG = "TanJiaJun"
}
}
複製代碼
挑幾個語法講解一下:
在示例代碼中我建立幾個內聯的代理函數,那什麼是內聯函數呢?爲何要用內聯函數?
內聯函數的原理是編譯器把實現內聯函數的字節碼動態插入到每次的調用處。
使用高階函數會帶來一些運行時的效率損失,由於在Kotlin中,每個函數都是一個對象,而且會捕獲一個閉包,即那些在函數體內會訪問到的變量。內存分配和虛擬調用都會增長開銷,在不少狀況下,使用內聯化Lambda表達式能夠消除這類開銷,舉個例子,有這樣一個函數:
fun add(list: MutableList<String>, block: () -> String): String {
list.add("譚嘉俊")
return block()
}
複製代碼
而後是這樣調用的:
add(mutableListOf("MutableList")) { "譚嘉俊" }
複製代碼
剛剛也說了,每個函數都是一個對象,因此後面這段Lambda表達式它也是一個對象,因此調用的時候,其實它會調用block方法,Kotlin是基於JVM的編程語言,因此調用一個方法,其實就是將這個方法入棧的操做,調用結束後就會將這個方法出棧,入棧和出棧都會有性能的開銷,因此咱們可使用內聯函數,代碼以下:
inline fun add(list: MutableList<String>, block: () -> String): String {
list.add("譚嘉俊")
return block()
}
複製代碼
用上內聯函數後,編譯器就會將block方法裏的代碼內聯到調用的地方,而不會再去調用block方法,從而減小了性能的開銷,就像以下代碼:
inline fun add(list: MutableList<String>, block: () -> String): String {
list.add("譚嘉俊")
return "譚嘉俊"
}
複製代碼
在示例代碼中,我用crossinline修飾了getter和setter這兩個參數,crossinline是修飾符關鍵字,它要在內聯函數中使用,能夠禁止傳遞內聯函數的Lambda表達式中的非局部返回。
那什麼是非局部返回呢?在Kotlin中,咱們只能對具名函數或者匿名函數使用非限定的return來退出,因此咱們在退出一個Lambda表達式就必須使用一個標籤,而且在Lambda表達式內部禁止使用裸return,由於Lambda表達式不能使包含它的函數return,代碼以下:
fun function(block: () -> Unit) =
print("譚嘉俊")
fun add(list: MutableList<String>) {
list.add("譚嘉俊")
function {
// 不能使add函數在此處return
return
}
}
複製代碼
可是若是Lambda表達式傳給的函數是內聯的,return也能夠是內聯的,代碼以下:
inline fun function(block: () -> Unit) =
print("譚嘉俊")
fun add(list: MutableList<String>) {
list.add("譚嘉俊")
function {
// 可使add函數在此處return
return
}
}
複製代碼
這種位於Lambda表達式中,但退出的是包含它的函數叫作非局部返回,就像咱們常常用到的forEach就是個內聯函數,代碼以下:
fun function(list: List<String>): Boolean {
list.forEach {
if (it == "譚嘉俊") return true // function函數return
}
return false
}
複製代碼
若是隻是想局部返回到forEach的話,能夠像以下那樣寫:
fun function(list: List<String>): Boolean {
list.forEach {
if (it == "譚嘉俊") return@forEach // 使用forEach隱式標籤,局部返回到forEach
}
return false
}
複製代碼
一些內聯函數可能調用的參數不是直接來自函數體,而是來自另外一個執行上下文的Lambda表達式,例如:來自局部對象或者嵌套函數,在這種狀況下,這個Lambda表達式中也不容許非局部返回,爲了標識這種狀況,這個Lambda表達式須要用crossinline修飾符標記,在上面的Preferences.kt文件中,getter參數和setter參數就用到crossinline修飾符,由於是局部對象ReadWriteProperty的getValue方法和setValue方法調用了getter參數和setter參數,代碼就再也不貼出來了。
在示例代碼中,我用到了Kotlin的reified修飾符,在說這個以前,咱們大概瞭解下Java的泛型:
咱們知道Java的 泛型是**」僞泛型「,它會在編譯階段進行類型擦除**。
泛型的類型擦除的原則有如下幾點:
當類或者方法定義中的類型參數沒有限制時,例如:或者<?>都被替換成Object,示例代碼以下:
類型擦除前:
public class Generic<T> {
private T value;
private List<?> list;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
public void setList(List<?> list) {
this.list = list;
}
}
複製代碼
類型擦除後:
public class Generic {
// T替換成Object
private Object value;
// List<?>替換成原生態類型List
private List list;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public void setList(List list) {
this.list = list;
}
}
複製代碼
當類或者方法定義中的類型參數存在上界的時候,都被替換成它的上界,例如:和<? extends Number>都會被替換成Number;當類或者方法定義中的類型參數存在下界的時候,都被替換成它的下界,例如:<? super Number>會被替換成Object。示例代碼以下:
類型擦除前:
public class Generic<T extends Number> {
private T value;
private List<? extends Number> extendsList;
private List<? super Number> superList;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
public void setExtendsList(List<? extends Number> extendsList) {
this.extendsList = extendsList;
}
public void setSuperList(List<? super Number> superList) {
this.superList = superList;
}
}
複製代碼
類型擦除後:
public class Generic {
// <T extends Number>替換成Number
private Number value;
// <? extends Number>替換成Number
private List<Number> extendsList;
// <? super Number>替換成Object
private List<Object> superList;
public Number getValue() {
return value;
}
public void setValue(Number value) {
this.value = value;
}
public void setExtendsList(List<Number> extendsList) {
this.extendsList = extendsList;
}
public void setSuperList(List<Object> superList) {
this.superList = superList;
}
}
複製代碼
以上就是Java泛型類型擦除的大概內容,如今說下Kotlin的reified修飾符:
reified修飾符能夠保證泛型的類型參數在運行時獲得保留,要注意的是這個函數必須是內聯函數,原理就是基於內聯函數的工做機制,上面有說起到,每次調用帶有reified的函數,編譯器都知道此次調用中的泛型的類型參數類型,而後就會生成對應的不一樣類型的類型實參的字節碼,而且動態插入到調用處,因爲生成的字節碼類型實參引用了具體類型,而不是類型參數,因此不會被編譯器擦除。示例代碼以下:
內聯函數startActivity:
inline fun <reified T : AppCompatActivity> Activity.startActivity() =
startActivity(Intent(this, T::class.java))
複製代碼
調用處:
startActivity<MainActivity>()
複製代碼
反編譯後的部分代碼:
startActivity:
public static final void startActivity(@NotNull Activity $this$startActivity) {
Intrinsics.checkParameterIsNotNull($this$startActivity, "$this$startActivity");
Context var10003 = (Context)$this$startActivity;
Intrinsics.reifiedOperationMarker(4, "T");
$this$startActivity.startActivity(new Intent(var10003, AppCompatActivity.class));
}
複製代碼
調用處:
// 被編譯器替換成以下代碼
this.startActivity(new Intent((Context)this, MainActivity.class));
複製代碼
要注意的是,Java代碼不能夠調用具體化的類型參數的內聯函數,可是能夠調用失去內聯特性的普通的內聯函數,由於具體化類型參數得益於內聯特性,上面也提到,這裏再也不贅述了。
在示例代碼中,繼承了ReadWriteProperty,而且實現了getValue方法和setValue方法,這裏用到了Kotlin的委託屬性。
語法:val/var <屬性名>: <類型> by <表達式>
屬性的委託沒必要實現任何的接口,若是是var屬性須要提供getValue方法和setValue方法,若是是val屬性須要提供getValue方法,by後面的表達式就是該委託,屬性對應的get()方法被委託給它的getValue方法,屬性對應的set()的方法被委託給它的setValue方法。示例代碼以下:
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String =
"$thisRef, thank you for delegating '${property.name}' to me!"
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) =
println("$value has been assigned to '${property.name}' in $thisRef.")
}
複製代碼
這裏用到了operator修飾符,能夠重載操做符,咱們也能夠實現ReadWriteProperty接口,它是用於實現讀寫屬性委託的基本接口,這個只是爲了方便咱們實現委託屬性,若是你有相同簽名的方法,就沒必要實現這個接口,代碼以下:
class Delegate : ReadWriteProperty<Any, String> {
override fun getValue(thisRef: Any, property: KProperty<*>): String =
"$thisRef, thank you for delegating '${property.name}' to me!"
override fun setValue(thisRef: Any, property: KProperty<*>, value: String) =
println("$value has been assigned to '${property.name}' in $thisRef.")
}
複製代碼
除了ReadWriteProperty外,還有另一個接口:ReadOnlyProperty,這個是爲了委託只讀屬性,只須要重寫它的getValue方法就能夠了。
Kotlin標準庫爲幾種委託提供了工廠方法,例如如下說的延遲屬性Lazy就是其中一種:
調用延遲屬性有這樣的特徵,第一次拿到屬性的值(調用get()方法)會執行已傳遞給函數的Lambda表達式而且記錄結果,後續調用get()方法只是返回記錄的結果。
咱們能夠看下源碼,提供了三個函數。
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
複製代碼
這個函數接受一個Lambda表達式,而且返回Lazy,而且調用SynchronizedLazyImpl函數,並且咱們能夠得知多個線程去調用這個lazy函數是安全的,代碼以下:
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
private fun writeReplace(): Any = InitializedLazyImpl(value)
}
複製代碼
咱們能夠看到用的是**雙重檢查鎖(Double Checked Locking)**來保證線程安全。
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
複製代碼
這個函數接受兩個參數,一個是LazyThreadSafetyMode,另一個是Lambda表達式,而且返回Lazy,LazyThreadSafetyMode是個枚舉類,代碼以下:
public enum class LazyThreadSafetyMode {
SYNCHRONIZED,
PUBLICATION,
NONE,
}
複製代碼
使用SYNCHRONIZED能夠保證只有一個線程初始化實例,實現細節在上面也說過了;使用PUBLICATION容許多個線程併發初始化值,可是只有第一個返回值用做實例的值;使用NONE不會有任何線程安全的保證以及的相關的開銷,因此你若是你確認初始化老是發生在同一個線程的話能夠用此模式,減小一些性能上的開銷。
public actual fun <T> lazy(lock: Any?, initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer, lock)
複製代碼
這個函數接受兩個參數,一個是你使用指定的對象(lock),目的是進行同步,另一個是Lambda表達式,返回的是Lazy,調用的是SynchronizedLazyImpl函數,上面也說過,這裏再也不贅述了。
個人GitHub:TanJiaJunBeyond
Android通用框架:Android通用框架(Kotlin-MVVM)
個人掘金:譚嘉俊
個人簡書:譚嘉俊