由于功能框架与业务代码的生命周期不同且功能框架与业务代码耦合紧密,导致无法更换过时的功能框架。
为了解决类似的问题我决定做一个搬运工的操作,将所有常用的框架与业务代码完全分离,
并提供一个比较友好的开发环境。已封装的框架有
- SP缓存
- 网络框架
- 数据库框架
本章主要介绍SP缓存框架
gitee的链接地址:https://gitee.com/GZona/zsp
框架优点:
1、保持本地与缓存的数据同步 2、崩溃后可以对缓存数据进行还原 3、操作简单,以接口的形式存储数据,增加数据的可读性 4、可对缓存数据进行第一次加工 5、业务相关的代码与功能框架完全隔离,当需要更新框架直接修改配置就可以目录
- 一 实现原理,与技术讲解
- 二 初始化配置
- 三 注解相关
- 四 框架的使用
fun getValue(context: Context, spName: String, key: String) {
val sharedPreferences: SharedPreferences =
context.getSharedPreferences(spName, Context.MODE_PRIVATE)
sharedPreferences.getString(key, "")
}
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()
}
该套流程是我们最常见的一种使用方式,但是这样写有许多的问题,其中最重要的两个问题是
- 本地缓存和内存中的数据可能不同,如果保持数据的同步性,
- 一旦内存被释放掉也不会自动将本地缓存赋予进内存
通过代理模式产生的作用,
1、监听setValue方法,写入数据到内存的时候会同步保存到本地,
2、监听getValue方法,当读取数据时如果内存数据为空时获取默认数据(从本地缓存拉取数据),如果内存不为空则直接返回内存数据
1、写代理类,监听getValue和setValue
class SharedPref( private val default: T, private val keyName: String = "", spName: String = "shared_pref", context: Context = CommonLibApp.appContext ) : ReadWriteProperty { 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、增加了更多的数据存储类型,支持接口的存储、数据中含有接口的存储、数据中的抽象集合中的抽象接口数据存储
- 在模块的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.00'
//用系统自带的本地缓存SP存储数据(与MMKV只需要实现一个就可以)
//implementation 'com.gitee.GZona.zsp:lib_sp:v1.1.00'
//SP的注解框架
implementation 'com.gitee.GZona.zsp:lib_sp_annotation:v1.1.00'
//SP的注解解析框架
kapt 'com.gitee.GZona.zsp:lib_sp_annotation:v1.1.00'
}
...................
- 在代码中初始化SP框架
class App : Application() {
override fun onCreate() {
super.onCreate()
//由于SP需要使用到context,所以在使用前需要提前需要初始化
SharePrefHolder.init { this }
}
}
三 注解相关
- AnSharedPref:作用于接口上,代表当前接口与SP关联,
- spName:SP文件名,如果不填SP文件名默认使用接口的接口名
- AnSpField:作用在全局变量上
- name:SP的键值,不填默认用全局变量的参数名
- defaultValue:SP的全局变量的默认值
- 应用示例
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) }
@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?
var test7: List
四 框架的使用
应用示例
setContentView(R.layout.layout_test)
val content1 = findViewById(R.id.content1)
val content2 = findViewById(R.id.content2)
val btn1_1 = findViewById(R.id.btn1_1)
val btn1_2 = findViewById(R.id.btn1_2)
val btn2_1 = findViewById(R.id.btn2_1)
val btn2_2 = findViewById(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
}
}



