非常好用的Android开发key-value数据缓存工具-kvcache,和SharedPreference代码说byebye

kvcache

欢迎Star🌟🌟🌟Github:kvcache:在Android开发中优雅的存取key/value数据,从此不用再写SharedPreference代码

kvcache 简介

该库可帮助你在Andrtoid开发中以更好的方式处理key-value数据。从现在开始,将您的sharedpreference代码和其他键值代码可以更改为kvcache,并编写更简介更漂亮的代码。

如何使用

步骤1/2/3是基本的依赖和初始化,很简单
当你需要缓存一个键值对的时候,只需按照步骤4写一个方法即可
然后就可以像步骤5那样使用了

Step 1. 在项目的根 build.gradle 里添加maven库(如果已有则不需要了):

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Step 2. 在app的build.gradle中添加依赖:

dependencies {
        implementation 'com.github.mazouri:kvcache:1.1'
}

Step 3. 可以在Application中初始化KVCache,并得到全局的对象kv:

private fun initKV() {
    val kvCache = KVCache.Builder(this)
        // 使用addServiceConfig()设置您自己的缓存方法
        // 如果未设置,则默认使用SharedPreference
        // Use addServiceConfig() to set your own storage method
        // If you not set, shared preference will be used by default
        .addServiceConfig(SimpleIKVConfig())
        // 如果使用默认的SharedPreference方式来缓存,则文件名默认为kv_cache_shared_prefs.xml
        // 可以使用addDefaultPrefsFileName()来修改保存的文件名
        // If the default shared preference storage is used, the file name defaults to kv_cache_shared_prefs.xml
        // You can use addDefaultPrefsFileName() to modify the saved file name
        .addDefaultPrefsFileName("kv_cache_main_sp")
        .build()

    kv = kvCache.create(KV::class.java)
}

Step 4. 创建一个接口KV用于配置你的key-value信息:

  • @KEY:对的,就是key的值
  • @DEFAULT:默认value,都是字符串(放心使用吧)
  • Call<Type>:Type按照你的需要定义基本数据类型
    interface KV {

    @KEY("save_String")
    @DEFAULT("")
    fun testKVCacheString(): Call<String>

    @KEY("save_int")
    @DEFAULT("0")
    fun testKVCacheInt(): Call<Int>

    @KEY("save_boolean")
    @DEFAULT("false")
    fun testKVCacheBoolean(): Call<Boolean>

    @KEY("save_float")
    @DEFAULT("0")
    fun testKVCacheFloat(): Call<Float>

    @KEY("save_long")
    @DEFAULT("0")
    fun testKVCacheLong(): Call<Long>
}

Step 5.然后,你就可以在项目的任何地方简单方便的使用了:

    // save you key value to cache, default is using shared preference
    KVApp.kv.testKVCacheString().put("hello KVCache")
    KVApp.kv.testKVCacheInt().put(2020)
    KVApp.kv.testKVCacheFloat().put(3.14f)
    KVApp.kv.testKVCacheBoolean().put(true)
    KVApp.kv.testKVCacheLong().put(111100001111L)

    // get value by key
    val resultString = KVApp.kv.testKVCacheString().get()
    val resultInt = KVApp.kv.testKVCacheInt().get()
    val resultFloat = KVApp.kv.testKVCacheFloat().get()
    val resultBoolean = KVApp.kv.testKVCacheBoolean().get()
    val resultLong = KVApp.kv.testKVCacheLong().get()

kvcache 的实现原理

如果你使用过Retrofit,并且理解Retrofit的实现原理,那么你看下面的实现原理将非常顺滑

KVCache

KVCache使用设计模式中的门面模式,客户端仅需使用该类

KVCache的创建方式如下:

private fun initKV() {
        val kvCache = KVCache.Builder(this)
            .addServiceConfig(SimpleIKVConfig())
            .addDefaultPrefsFileName("kv_cache_main_sp")
            .build()

        kv = kvCache.create(KV::class.java)
    }

使用了设计模式中的Builder模式

Builder代码如下:

class KVCache private constructor(private val builder: Builder){
    
    ...
    
    class Builder constructor(val context: Context) {
        internal var serviceConfig: IKVConfig? = null
        internal var filename: String = ""

        fun addServiceConfig(serviceConfig: IKVConfig): Builder {
            this.serviceConfig = serviceConfig
            return this
        }

        fun addDefaultPrefsFileName(filename: String): Builder {
            this.filename = filename
            return this
        }

        fun build(): KVCache {
            return KVCache(this)
        }
    }
}

通过该类,可以配置自定义的ServiceConfig和默认缓存方式shared_preference使用的文件名。

下面看KVCache的create函数实现:

    @Suppress("UNCHECKED_CAST")
    fun <T> create(service: Class<T>): T {
        context = builder.context.applicationContext
        val proxy = Proxy.newProxyInstance(
            service.classLoader,
            arrayOf<Class<*>>(service),
            object : InvocationHandler {
                override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
                    if (method!!.declaringClass == Any::class.java) {
                        return method.invoke(this, args)
                    }
                    return KVCall<T>(loadServiceMethod(method))
                }

            }
        )

        return proxy as T
    }

使用了设计模式中的动态代理模式

通过动态代理,将客户端写的KV接口的类,转换成KV对象调用请求

这里看下loadServiceMethod(method)的实现:

    private fun loadServiceMethod(method: Method): KVMethod<*> {
        var result = serviceMethodCache[method]
        if (result != null) return result

        // 2.解析method
        synchronized(serviceMethodCache) {
            result = serviceMethodCache[method]

            if (result == null) {
                // 3.将method传到KVMethod中解析
                result = KVMethod.Builder<Any?>(method).build()
                serviceMethodCache[method] = result as KVMethod<*>
            }
        }

        return result!!
    }

这里加了一个缓存,如果之前已经解析过的方法,就不用再解析了,提高效率。

然后将method传到KVMethod中去解析

KVMethod

该类用于解析客户端定义的注解类

从上面的代码可以知道KVMethod也是通过Builder构建的

这里使用了设计模式的Builder模式

重点我们看下,method传进来后,是怎么解析的:

class Builder<T> constructor(private val method: Method) {

        private val methodAnnotations = method.annotations
        var typeClass: Class<T>
        lateinit var key: String
        lateinit var default: String
        var isSync = false

        init {
            val returnType = method.genericReturnType as ParameterizedType
            typeClass = returnType.actualTypeArguments[0] as Class<T>
        }

        fun build(): KVMethod<T> {
            for (annotation in methodAnnotations) {
                parseMethodAnnotation(annotation)
            }

            return KVMethod<T>(this)
        }

        private fun parseMethodAnnotation(annotation: Annotation) {
            when(annotation) {
                is KEY -> key = annotation.value
                is DEFAULT -> default = annotation.value
                is SYNC -> isSync = true
            }
        }
    }

拿到method的注解信息和返回值类型。

比如下面的代码:

        @KEY("save_String")
        @DEFAULT("")
        fun testKVCacheString(): Call<String>

解析注解时,

  • 如果发现注解是KEY这个类型,就把该注解的value值给到key
  • 如果发现注解是DEFAULT这个类型,就把该注解的value值给到default

KVCall

在存取数据的时候会这么使用:

        // save you key value to cache, default is using shared preference
        KVApp.kv.testKVCacheString().put("hello KVCache")
       
        // get value by key
        val resultString = KVApp.kv.testKVCacheString().get()

那么put()和get()是怎么实现的呢?

看下KVCall这个类中的put方法:

internal class KVCall<T> constructor(private val serviceMethod: KVMethod<*>): Call<T> {

    ...
    
    override fun put(value: T) {
            val key = serviceMethod.key + key
            val cls = serviceMethod.typeClass
            val isSync = serviceMethod.isSync
    
            try {
                when (cls) {
                    java.lang.Integer::class.java -> {
                        setIntValue(key, value as Int, isSync)
                    }
                    java.lang.Float::class.java -> {
                        setFloatValue(key, value as Float, isSync)
                    }
                    java.lang.Boolean::class.java -> {
                        setBooleanValue(key, value as Boolean, isSync)
                    }
                    java.lang.Long::class.java -> {
                        setLongValue(key, value as Long, isSync)
                    }
                    java.lang.String::class.java -> {
                        setStringValue(key, value as String, isSync)
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        
        ...
        
        private fun setStringValue(key: String, value: String, isSync: Boolean) {
            KVConfigManager.instance.setStringValue(key, value, isSync)
       }
}

在这里,根据KVMethod的返回值类型,调用缓存管理类KVConfigManager设置对应的数据

IKVConfig

缓存key-value数据的接口类,客户端通过实现该接口实现自己的缓存方式

interface IKVConfig {
    fun hasKey(key: String?): Boolean

    fun getLongValue(key: String?, defValue: Long): Long

    fun getBooleanValue(key: String?, defValue: Boolean): Boolean

    fun getIntValue(key: String?, defValue: Int): Int

    fun getFloatValue(key: String?, defValue: Float): Float

    fun getStringValue(key: String?, defValue: String?): String?

    fun setBooleanValue(
        key: String?,
        value: Boolean,
        isSync: Boolean
    )

    fun setLongValue(
        key: String?,
        value: Long,
        isSync: Boolean
    )

    fun setIntValue(
        key: String?,
        value: Int,
        isSync: Boolean)

    fun setFloatValue(
        key: String?,
        value: Float,
        isSync: Boolean
    )

    fun setStringValue(
        key: String?,
        value: String?,
        isSync: Boolean
    )
}

KVConfigManager

缓存方式管理类,默认使用KVPrefs

internal class KVConfigManager private constructor(): IKVConfig {

    private var serviceConfig: IKVConfig = KVPrefs()

    companion object {
        val instance: KVConfigManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            KVConfigManager()
        }
    }

    internal fun setServiceConfig(serviceConfig: IKVConfig?) {
        Log.d("", "setServiceConfig called")
        if (serviceConfig == null) return
        this.serviceConfig = serviceConfig
    }
    
    override fun setStringValue(key: String?, value: String?, isSync: Boolean) = serviceConfig.setStringValue(key, value, isSync)
    ...
} 

这里使用了设计模式中的单例模式

如果客户端没有使用自定义的缓存方式,则默认使用KVPrefs缓存

KVPrefs

默认的存储方式,即使用SharedPreferences

比较简单,这里就不赘述了,可以直接看源码KVPrefs

欢迎Star🌟🌟🌟Github:kvcache:在Android开发中优雅的存取key/value数据,从此不用再写SharedPreference代码

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342