我是如何使用Kotlin优化项目代码(1)
这篇代码中全都是干货,需要读者具有一定的Kotlin基础,了解Kotlin中扩展函数、高阶函数、委托、DSL等特性,当然如果看的不是太明白,也可以拿来直接使用哈!~~
推荐下好文会更有利于对于本文的理解:
一.快捷单例的实现
-
object
声明单例object Demo1{ }
可以看到声明一个单例一行代码就可以搞定,反编译成Java代码看一看:
public final class Demo1 { @NotNull public static final Demo1 INSTANCE; private Demo1() { } static { Demo1 var0 = new Demo1(); INSTANCE = var0; } }
这就是我们熟悉的创建单例的方式了:
利用类的静态代码块只在类加载的时候被执行一次的特性实现了单例,特点
线程安全
、饿汉式
顺便再提以下,在
object
声明的类中创建方法其实并不是真的静态方法,可以反编译看一下:在kotlin创建的方法: fun getA() = "a" //反编译成java代码 @NotNull public final String getA() { return "a"; }
可以看到这是一个普通的成员函数,所以在Java环境下调用这个方法的时候就得通过
Demo1.INSTANCE.getA()
实现,而kotlin利用其语法糖就可以通过Demo1.getA()
实现如果我们想要在Java环境下像Kotlin那样调用
getA()
方法,就得在这个方法上加个注解@JvmStatic
即可实现://加了注解的方法 @JvmStatic fun getA() = "a" //反编译成java代码看看 @JvmStatic @NotNull public static final String getA() { return "a"; }
可以看到,加了注解之后其实就是将这个
getA()
方法声明成了一个静态方法,所以Java和Kotlin环境下都可以通过Demo1.getA()
实现调用属性的声明方式和方法比较类似,笔者就不再这里提及了
-
利用
by lazy
实现单例class Demo1 { companion object { val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { Demo1() } } }
- 首先lazy中第一个参数传入了一个
LazyThreadSafetyMode.SYNCHRONIZED
,这个就保证了线程安全,这也是其默认的参数,大家写的时候直接去掉这个参数就行 - 其次
by lazy
实现了懒加载,只有当我们调用了instance才会执行创建Demo1对象的代码,下次在调用会直接返回上一次的结果,也就是说,这个by lazy
里面的代码只会被执行一次,接下来后续在调用只会返回第一次执行时返回的结果
- 首先lazy中第一个参数传入了一个
二.内部DSL优化接口的实现
平常大家写代码的时候会经常遇到这样的问题:
为了实现某些功能的监听回调,就通过某个方法传入一个接口实现类,在实现类中我们需要重写这个接口中所有的方法,但是很多时候我们根本用不到那么多方法,以至于写的时候显得很冗余、不美观!!!
所以接下来笔者会利用Kotlin内部DSL的特性来实现只重写这个接口中我们需要方法,而不用去声明其他方法的重写
-
以
Application.registerActivityLifecycleCallbacks
()为例这个方法是监听所有Activity生命周期的回调函数,平常大家最多使用到的方法也就是
onActivityCreated()
、onActivityDestroyed
等,其他的用到的很少,所以为了避免也要很麻烦的重写其他的方法,我们开始Kotlin DSL的改造fun Application.addActivityLifecycleCallback(block: ActivityLifecycleCallback.() -> Unit) { registerActivityLifecycleCallbacks(ActivityLifecycleCallback().apply(block)) } class ActivityLifecycleCallback: Application.ActivityLifecycleCallbacks { private var onActivityCreated: ((Activity?, Bundle?) -> Unit)? = null private var onActivityStarted: ((Activity?) -> Unit)? = null private var onActivityResumed: ((Activity?) -> Unit)? = null private var onActivityPaused: ((Activity?) -> Unit)? = null private var onActivityStopped: ((Activity?) -> Unit)? = null private var onActivitySaveInstanceState: ((Activity?, Bundle?) -> Unit)? = null private var onActivityDestroyed: ((Activity?) -> Unit)? = null fun setOnActivityCreated(block: (Activity?, Bundle?) -> Unit) { onActivityCreated = block } fun setOnActivityStarted(block: (Activity?) -> Unit) { onActivityStarted = block } fun setOnActivityResumed(block: (Activity?) -> Unit) { onActivityResumed = block } fun setOnActivityPaused(block: (Activity?) -> Unit) { onActivityPaused = block } fun setOnActivityStopped(block: (Activity?) -> Unit) { onActivityStopped = block } fun setOnActivitySaveInstanceState(block: (Activity?, Bundle?) -> Unit) { onActivitySaveInstanceState = block } fun setOnActivityDestroyed(block: (Activity?) -> Unit) { onActivityDestroyed = block } override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) { onActivityCreated?.invoke(activity, savedInstanceState) } override fun onActivityStarted(activity: Activity?) { onActivityStarted?.invoke(activity) } override fun onActivityResumed(activity: Activity?) { onActivityResumed?.invoke(activity) } override fun onActivityPaused(activity: Activity?) { onActivityPaused?.invoke(activity) } override fun onActivityStopped(activity: Activity?) { onActivityStopped?.invoke(activity) } override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) { onActivitySaveInstanceState?.invoke(activity, outState) } override fun onActivityDestroyed(activity: Activity?) { onActivityDestroyed?.invoke(activity) } }
然后我们就可以在Application进行优雅的调用了:
class AppInstance : Application() { override fun onCreate() { super.onCreate() //实现Activity声明周期的监听 addActivityLifecycleCallback { setOnActivityCreated { activity, bundle -> } setOnActivityDestroyed { } } } }
-
以动画
Animation.addUpdateListener
()为例
fun Animator.addAnimCallback(block: AnimatorCallback.() -> Unit) {
addListener(AnimatorCallback().apply(block))
}
class AnimatorCallback: Animator.AnimatorListener {
private var animStart: ((Animator?) -> Unit)? = null
private var animEnd: ((Animator?) -> Unit)? = null
private var animCancel: ((Animator?) -> Unit)? = null
private var animRepeat: ((Animator?) -> Unit)? = null
fun setAnimStart(block: (Animator?) -> Unit) {
animStart = block
}
fun setAnimEnd(block: (Animator?) -> Unit) {
animEnd = block
}
fun setAnimCancel(block: (Animator?) -> Unit) {
animCancel = block
}
fun setAnimRepeat(block: (Animator?) -> Unit) {
animRepeat = block
}
override fun onAnimationStart(animation: Animator?) {
animStart?.invoke(animation)
}
override fun onAnimationEnd(animation: Animator?) {
animEnd?.invoke(animation)
}
override fun onAnimationCancel(animation: Animator?) {
animCancel?.invoke(animation)
}
override fun onAnimationRepeat(animation: Animator?) {
animRepeat?.invoke(animation)
}
}
然后我们就可以在动画的状态监听中优雅的这样使用了:
ValueAnimator().addAnimCallback {
//动画开始
setAnimStart {
}
//动画结束
setAnimEnd {
}
}
希望读者可以通过上面两个例子来了解Kotlin DSL如何进行接口实现的优化
PS:其实在Java中也可以进行这些接口的实现的优化,比如我们定义一个抽象类实现某个接口,在这个抽象类实现这个接口的所有方法,方法体中什么都不写入就行,然后当我们需要传入一个接口的实现类的时候,只需要继承这个抽象类,然后选择性的重写里面想要的方法即可
三.委托与SharedPreference的结合
利用Kotlin委托的特性,我们可以实现很优雅读写SP里面内容的代码
-
利用属性委托实现与SP的结合
实现属性委托有两种方式,这里我只给大家介绍其中最方便的一种方法:实现
ReadWriteProperty
接口/** * 其中第一个传入的是需要读写的key,第二个传入是当读取出来的数据不存在的时候默认返回的值 */ class Preference<T>(private val label: String, private val default: T): ReadWriteProperty<Any?, T> { private val sp: SharedPreferences by lazy { //主要是获取SP对象,这个根据每个项目视情况获取 AppInstance.getInstance().getSharedPreferences("data", Context.MODE_PRIVATE) } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { putSharePreferences(label, value) } override fun getValue(thisRef: Any?, property: KProperty<*>): T { return getSharePreferences(label, default) } private fun putSharePreferences(name: String, value: T) = with(sp.edit()) { when (value) { is Long -> putLong(name, value) is String -> putString(name, value) is Int -> putInt(name, value) is Boolean -> putBoolean(name, value) is Float -> putFloat(name, value) else -> throw IllegalArgumentException("This type of data cannot be saved!") }.apply() } @Suppress("UNCHECKED_CAST") private fun getSharePreferences(name: String, default: T): T = with(sp) { val res: Any = when (default) { is Long -> getLong(name, default) is String -> getString(name, default) is Int -> getInt(name, default) is Boolean -> getBoolean(name, default) is Float -> getFloat(name, default) else -> throw IllegalArgumentException("This type of data cannot be saved!") } return res as T } }
然后我们就可以很优雅读写SP了:
class AppInstance : Application() { private var value: String by Preference("key", "") override fun onCreate() { super.onCreate() //read sp value println(value) //write sp value value = "b" } }
这种情况下如果有很多个需要读写的对象的时候,会为每个对象都会创建一个委托类,感觉性能上不是很好那么一点点,所以接下来介绍下类委托
-
利用类委托实现与SP的结合
我们可以把这个类委托理解为一种代理模式,这样有助于大家理解封装的代码
fun getSharedPreferenceInstance(name: String) = lazy { SharedPreferenceUtil( AppInstance.getInstance().getSharedPreferences( name, Context.MODE_PRIVATE) ) } class SharedPreferenceUtil(private val sp: SharedPreferences): SharedPreferences by sp { operator fun <T> SharedPreferences.Editor.set(name: String, value: T): SharedPreferences.Editor { when (value) { is Long -> putLong(name, value) is String -> putString(name, value) is Int -> putInt(name, value) is Boolean -> putBoolean(name, value) is Float -> putFloat(name, value) else -> throw IllegalArgumentException("This type of data cannot be saved!") } return this } @Suppress("UNCHECKED_CAST") operator fun <T> get(name: String, default: T): T = with(sp) { val res: Any = when (default) { is Long -> getLong(name, default) is String -> getString(name, default) is Int -> getInt(name, default) is Boolean -> getBoolean(name, default) is Float -> getFloat(name, default) else -> throw IllegalArgumentException("This type of data cannot be saved!") } return res as T } /** * 单个对象的存储 */ operator fun <T> set(key: String, value: T) { with(edit()) { this[key] = value this.apply() } } /** * 多个对象的存储: */ fun storeData(vararg values: Pair<String, Any>) { with(edit()) { values.forEach { this[it.first] = it.second } this.apply() } } }
来看一下代码中的优雅运用:
class ExpandTouchActivity1 : AppCompatActivity() { private val mPrefs by getSharedPreferenceInstance("data") private val TAG = "ExpandTouchActivity1" private lateinit var mClickMeButton: Button private lateinit var mContentFrameLayout: FrameLayout override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_expand_touch1) //单个读写 mPrefs["var1"] = "var1" Log.i(TAG, "onCreate: ${mPrefs["var1", ""]}") //多个写入读取 mPrefs.storeData("var2" to "var2", "var3" to 5) Log.i(TAG, "onCreate: var2 = ${mPrefs["var2", ""]}, var3 = ${mPrefs["var3", -1]}, var4 = ${mPrefs["var4", ""]}") } }
针对其中出现的细节讲一下:
我在代理类SharedPreferenceUtil中提供了两个写入的方法,为什么要这么做呢?
因为如果我们只需要多个数据的话,虽然也可以通过
mPrefs["var1"] = "var1", mPrefs["var2"] = "var2"
写入,但是这恰恰会降低SharedPreference读写的性能,因为会每次写入都会创建一个Editor对象我们在写入SharedPreference应遵循一个原则,多次写入应该一次edit(),多次putXXX(),一次apply(),所以我给出了一个一次性写入多组数据的方法
storeData()
这两种封装方式大家可以根据项目实际情况进行选择,如果SP中发生读写的对象很多的时候,建议大家使用第二种:类委托与SP的结合;其他的第一种:属性委托的方式即可搞定
四.总结
Kotlin是门很优秀的语言,利用好其中的特性可以很大程度简化我们代码的操作,希望大家一起砥砺前行,提升技术,寻找合适的属于自己的空间
五.历史文章
关于BaseRecycleViewAdapterHelper与ViewBinding结合的封装: