1 Star 0 Fork 0

nadyboy / ZSp.SharedPreferences+MMKV

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README(技术文档).md 10.56 KB
一键复制 编辑 原始数据 按行查看 历史

Kotlin Sp框架

摘要

研发目的

由于功能框架与业务代码的生命周期不同且功能框架与业务代码耦合紧密,导致无法更换过时的功能框架。 为了解决类似的问题我决定做一个搬运工的操作,将所有常用的框架与业务代码完全分离, 并提供一个比较友好的开发环境。已封装的框架有

  • SP缓存
  • 网络框架
  • 数据库框架

本章主要介绍SP缓存框架 gitee的链接地址:https://gitee.com/GZona/zsp

框架优点:

1、保持本地与缓存的数据同步
2、崩溃后可以对缓存数据进行还原
3、操作简单,以接口的形式存储数据,增加数据的可读性
4、可对缓存数据进行第一次加工
5、业务相关的代码与功能框架完全隔离,当需要更新框架直接修改配置就可以

目录

一 实现原理,与技术讲解

1、写一个最基本的SP缓存,以Android自带的SharedPreferences为例
 /**
     * 从SP中获取数据
     * @param context 上下文
     * @param spName SP文件名
     * @param key 取出数据的Key值
     */
    fun getValue(context: Context, spName: String, key: String) {
        val sharedPreferences: SharedPreferences =
            context.getSharedPreferences(spName, Context.MODE_PRIVATE)
        sharedPreferences.getString(key, "")
    }

    /**
     * 将数据存入SP中
     * @param context 上下文
     * @param spName SP文件名
     * @param key 存入数据的Key值
     * @param value 存入的数据
     */
    fun setValue(context: Context, spName: String, key: String, value: String) {
        val sharedPreferences: SharedPreferences =
            context.getSharedPreferences(spName, Context.MODE_PRIVATE)
        val editor = sharedPreferences.edit()
        editor.putString(key, value)
        editor.apply()
    }

该套流程是我们最常见的一种使用方式,但是这样写有许多的问题,其中最重要的两个问题是

  • 本地缓存和内存中的数据可能不同,如果保持数据的同步性,
  • 一旦内存被释放掉也不会自动将本地缓存赋予进内存
2、将kotlin的代理模式接入进来

通过代理模式产生的作用, 1、监听setValue方法,写入数据到内存的时候会同步保存到本地, 2、监听getValue方法,当读取数据时如果内存数据为空时获取默认数据(从本地缓存拉取数据),如果内存不为空则直接返回内存数据

1、写代理类,监听getValue和setValue


/**
 * Create by LiJie at 2019-05-29
 * 通过代理从[SharedPreferences]获取或保存变量,,ReadWriteProperty读写代理,当调用get、和get时可以触发相应事件
 * var account by SharedPref(default = "")
 */
class SharedPref<T>(
    private val default: T,
    private val keyName: String = "",
    spName: String = "shared_pref",
    context: Context = CommonLibApp.appContext
) : ReadWriteProperty<Any, T> {

    private val sharedPreferences: SharedPreferences = context.getSharedPreferences(spName, Context.MODE_PRIVATE)

    override fun getValue(thisRef: Any, property: KProperty<*>): T {
        val name = if (keyName.isEmpty()) property.name else keyName
        return with(sharedPreferences) {
            @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
            when (default) {
                is String -> getString(name, default) as T
                is Int -> getInt(name, default) as T
                is Float -> getFloat(name, default) as T
                is Boolean -> getBoolean(name, default) as T
                is Long -> getLong(name, default) as T
                else -> throw IllegalArgumentException("not support type")
            }
        }
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
        val name = if (keyName.isEmpty()) property.name else keyName
        val editor = sharedPreferences.edit()
        with(editor) {
            @Suppress("IMPLICIT_CAST_TO_ANY")
            when (value) {
                is String -> putString(name, value)
                is Int -> putInt(name, value)
                is Float -> putFloat(name, value)
                is Boolean -> putBoolean(name, value)
                is Long -> putLong(name, value)
                else -> throw IllegalArgumentException("not support type")
            }
        }
        editor.apply()
    }
}

2、引用代理,实现内存与本地缓存同步


    private var uuid_: String? by SharedPref(
            default = "",
            keyName = "uuid",
            spName = SP_NAME
    )

    //Delegates.observable的作用是监听数据的指针,如果指针发生了变化则触发相应事件,以下列数据为例,
    //当uuid为null时回去拉取uuid_的数据(即获取数据SP缓存),当uuid发生改变的时候,会触发下列回调事件(即数据写入SP缓存)
    var uuid by Delegates.observable(
            uuid_
    ) { _, _, newValue ->
        uuid_ = newValue
    }

常见的SP缓存问题解决了,但是又有新的问题出现了

(1)为了一个数据的缓存编写的代码过多
(2)当需要更换其他SP缓存框架的时候比较费劲
(3)支持的数据类型有限
(4)功能框架与业务逻辑分离的还不够彻底
3、引入编译时注解

将重复的代码通过编译时自动生成,提供开发效率,彻底隔绝功能框架与业务逻辑,通过接口统一管理同模块数据,方便阅读和理解


val TestPref: ITest by lazy { SharePrefHolder.getSpClass(ITest::class.java) }

@AnSharedPref()
interface ITest {
    var test1: String

}

其数据最终以接口的形式存在,并且以全局静态变量的方式调用,其优势 1、代码简单 2、通过单独一个接口来集合数据,可以做到更好的归纳,方便梳理数据 3、由于SP缓存框架已经被封装,所以我们可以当有性能更好的框架时可以做到无感切换 4、增加了更多的数据存储类型,支持接口的存储、数据中含有接口的存储、数据中的抽象集合中的抽象接口数据存储

二 初始化配置

  1. 在模块的build.gradle文件中添加Sp框架

//1、将它添加到存储库末尾的根 build.gradle 中
    allprojects {
		repositories {
			...
			maven { url 'https://jitpack.io' }
		}
	}
	
...................

//2、在模块对于的build.gradle 中

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    //1、支持kapt,这一步非常重要
    id "kotlin-kapt"
}


...................

//3、Java语音版本支持
 compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    
...................  
//4、导入库
    
    dependencies{
        //3、导入开发jar包
        //SP工具框架
        //用MMKV本地缓存
        implementation 'com.gitee.GZona.zsp:lib_mmkv:v1.1.04'
        //用系统自带的本地缓存SP存储数据(与MMKV只需要实现一个就可以)
        //implementation 'com.gitee.GZona.zsp:lib_sp:v1.1.04'
    
        //SP的注解框架
        implementation 'com.gitee.GZona.zsp:lib_sp_annotation:v1.1.04'
        //SP的注解解析框架
        kapt 'com.gitee.GZona.zsp:lib_sp_annotation:v1.1.04'
    }
    
    
...................
  1. 在代码中初始化SP框架


class App : Application() {
    override fun onCreate() {
        super.onCreate()
        //由于SP需要使用到context,所以在使用前需要提前需要初始化
          SharePrefHolder.init { this }
    }
}

三 注解相关

  1. AnSharedPref:作用于接口上,代表当前接口与SP关联,
  • spName:SP文件名,如果不填SP文件名默认使用接口的接口名
  1. AnSpField:作用在全局变量上
  • name:SP的键值,不填默认用全局变量的参数名
  • defaultValue:SP的全局变量的默认值
  1. 应用示例


package com.zona.yhsp

import android.text.TextPaint
import android.widget.TextView
import com.zona.lib_sp.SharePrefHolder
import com.zona.lib_sp_annotation.AnSharedPref
import com.zona.lib_sp_annotation.AnSpField


val TestPref: ITest by lazy { SharePrefHolder.getSpClass(ITest::class.java) }

/**
 * @ClassName Test
 * @Description
 * @Author zona
 * @Date 2021/3/31 16:44
 * @Version 1.0
 */
@AnSharedPref()
interface ITest {
    var test1: String

    @AnSpField( defaultValue = "添加的默认数据")
    var test8: String
    var test2: Int
    var test3: Float
    var test4: Double
    var test5: Boolean
    var test6: List<TextView>?
    var test7: List<Map<String, HashMap<Int, TextPaint>>>?
    var test8: Test?

    fun d(i: Int): String {
        return "测试数据:${i}"
    }

    fun text(): String {
        return test1
    }

}

~~~~~~

class Test(val ddd:String) {
    var texs1: String? = null
    var texs2: String = ""
    var aaaa: Int = 0
}

~~~~~~

四 框架的使用

应用示例


setContentView(R.layout.layout_test)
        val content1 = findViewById<TextView>(R.id.content1)
        val content2 = findViewById<TextView>(R.id.content2)
        val btn1_1 = findViewById<TextView>(R.id.btn1_1)
        val btn1_2 = findViewById<TextView>(R.id.btn1_2)
        val btn2_1 = findViewById<TextView>(R.id.btn2_1)
        val btn2_2 = findViewById<TextView>(R.id.btn2_2)
        content1.text = TestPref.test1
        content2.text = TestPref.test8
        btn1_1.setOnClickListener {
            content1.text = btn1_1.text
            TestPref.test1 = btn1_1.text.toString()
        }
        btn1_2.setOnClickListener {
            content1.text = btn1_2.text
            TestPref.test1 = btn1_2.text.toString()
            Toast.makeText(this, TestPref.text(), Toast.LENGTH_SHORT).show()
        }
        
        TestPref:test8 = Test("测试")
        btn2_2.setOnClickListener {
            //如果需要修改对象中的数据,可以使用该方法同步
            TestPref:test8.saveSpData {
                DomeConf.test?.aaaa = (DomeConf.test?.aaaa ?: 0) + 1
            }
        }

五 框架迭代历史

  • v1.1.00:第一个正式版本
  • v1.1.03:解决了存储接口数据,为空的情况下无法获取数据的问题
  • v1.1.04:解决了存储接口数据,接口对应的实现类使用SerializedName别名时无法生成数据的BUG
Kotlin
1
https://gitee.com/GZona/zsp.git
git@gitee.com:GZona/zsp.git
GZona
zsp
ZSp.SharedPreferences+MMKV
master

搜索帮助