Android开发: 分享如何利用好Kotlin的特点(一)---- 提高开发效率

5,699 阅读6分钟

Kotlin-first but not kotlin-must

谷歌在 I/O 大会上宣布,Kotlin 编程语言现在是 Android 应用程序开发人员的首选语言后,有更多的安卓程序投入Kotlin的怀抱。 Kotlin的语法糖更加提高了开发的效率,加快了开发速度,使开发工作变得有趣,也让我们有更多时间写注释了(笑)。但是其实对于Kotlin和Java在Android开发上的选择,个人觉得这个除了开发人员对语言的喜好的,同时也会应该到各自语言的魅力和特点,甚至项目的需求以及后续维护等等各个因素,没有绝对的选择的。我们要做到的是放大不同语言优点并加以拓展,不是一味只选择某个语言,语言不是问题,用的那个人怎么用才是关键。

利用Lazy帮助实现初始化

lazy() 是一个函数, 接受一个 Lambda 表达式作为参数, 返回一个 Lazy<T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。 先贴上代码:

fun startInit(component: Components.()->Unit){
    component.invoke(Components.get())
}

class Components {

    companion object{

        private val entry = ArrayMap<String,Any?>()

        private val instant by lazy { Components() }

        fun get() = instant

        fun getEntry() = entry
    }


    inline fun <reified T>single(single: ()->T){
        val name = T::class.java.name
        getEntry()[name] = single()
    }


}

inline fun <reified T> get(name: String = T::class.java.name) : T{
   return Components.getEntry()[name] as T
}

inline fun <reified T> inject(name: String = T::class.java.name) : Lazy<T> {
    return lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { Components.getEntry()[name]  as T }
}


// 使用例子
startInit {
            single {  RoomApi.getDao() }
            single {  RetroHttp.createApi(Main::class.java) }
        }

 
 private val  main : Main by inject()
 private val dao : MainDao by inject()

总结:简单的代码优化,提高开发效率

借助协程实现倒计时和超时等待任务

一、倒计时

这个一直都是安卓开发的常见需求,普遍可采用如下方案
1、RxJava
2、CountDownTimer
3、Timer+TimerTask
4、线程
这些都是属于常见的可行方案,相关对应使用时候的坑这里就不展开,各有各的。 那我们借助协程这个工具,我们可以自己实现其中一种方案,优雅地编写代码,一样贴代码

fun counter(dispatcher: CoroutineContext,start:Int, end:Int, delay:Long,
onProgress:((value:Int)->Unit),onFinish: (()->Unit)?= null){
    val out = flow<Int> {
        for (i in start..end) {
            emit(i)
            kotlinx.coroutines.delay(delay) 
        }
    }
    GlobalScope.launch {
        withContext(dispatcher) {
            out.collect {
                onProgress.invoke(it)
            }
            onFinish?.invoke()
        }
    }
}

利用flow实现这种异步输出数字同时,达到每个输出结果之间延迟,这样就能更好地计算出结果,其中的GlobalScope.launch这个方法我十分建议替换成lifecycleScope.launchWhenStarted在安卓上生命周期状态十分重要,也是近年Google推出这个框架的原因:

androidx.lifecycle:lifecycle-***-***

其中的lifecycleScope.launchWhenStarted 来自这里,有兴趣的童鞋可以深入研究,其实这块知识和源码不难理解的,

androidx.lifecycle:lifecycle-runtime-ktx

二、超时等待任务

在平时的开发中,或多或少遇到一种需求就是,我需要执行一个超时任务,然后这个任务有个最大超时时间,超过了这个时间,任务作废或执行另一个任务,超时之内的我要提前执行其他逻辑等等。例如不少的启动页拿广告的,无论是广告来自你家的还是第三方的,涉及到网络获取,总有遇到延时问题,我要拿到广告才能进入主页面,我也不能等太长时间,影响正常的功能使用,而我的确需要这个广告的相关收益。那只好执行类似的超时任务,我个人的解决方案是如下:
还是借助协程来进行,同样GlobalScope.launch这个方法我十分建议替换成lifecycleScope.launchWhenStarted

fun waitUtil(dispatcher: CoroutineContext,outTimeMills:Long,onTime:(result:Boolean)->Unit, doWork: suspend () ->Unit){
    GlobalScope.launch(dispatcher) {
        val result = withTimeoutOrNull(outTimeMills){
            doWork.invoke()
        }
        onTime.invoke(result != null)
    }
}

利用协程里的超时方法来帮助判断任务的执行是否超时了。
总结:其实协程是一个对线程池封装的好框架,也不需要把它想象得是什么十分高大上很难理解的东西,慢慢分析源码很重要。

浮点变量转dp,px,sp等等

这个也是我们平时开发经常用到,px转dp,px转sp,等等。先贴代码:

val Float.dp
   get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this,Resources.getSystem().displayMetrics)

val Float.px
  get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, this,Resources.getSystem().displayMetrics)

val Float.sp
    get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this,Resources.getSystem().displayMetrics)

其实这种拓展方法在日常的Kotlin开发经常用到, 详细解疑可见大佬扔物线详细讲解视频:www.bilibili.com/video/BV16K…

在适应位置跳出Kotlin的ForEach

这里说的ForEach是这种偷懒写法list.forEach{},这种情况下用break跳出是有问题的,可以自己敲敲代码看看。 我个人优化方案如下:

inline fun <T> Iterable<T>.forEachBreak(action: (T) -> Boolean ){
    kotlin.run breaking@{
        for (element in this)
            if(!action(element)){
                return@breaking
            }
    }
}

利用最后的返回值为false的时候跳出去循环,当然其实你如果使用for (element in this)是可以正常使用break,countine的,我举个例子就知道了:


val list = ArrayList<Int>()
    for(i in 0..10){
        list.add(i)
    }

    list.forEach {
        if(it>5){
            break;//编译器会报错 'break' and 'continue' are only allowed inside a loop
        }
        print("Test 1 Loop in $it")
    }

    for(i in list){
        if(i>5){
            break
        }
        print("Test 2 Loop in $i")
    }
    
       list.forEachBreak {
        if(it>5){
           return@forEachBreak false
        }
        println("Test 3 Loop in $it")
        true
    }
    

除了报错的那个下面两个输出的结果是一样的

Test 2 Loop in 0
Test 2 Loop in 1
Test 2 Loop in 2
Test 2 Loop in 3
Test 2 Loop in 4
Test 2 Loop in 5
Test 3 Loop in 0
Test 3 Loop in 1
Test 3 Loop in 2
Test 3 Loop in 3
Test 3 Loop in 4
Test 3 Loop in 5

总结:不要懒了,写多几行代码吧,有时候懒得语法糖不一定好吃

EditText的addTextChangedListener和TabLayout的addOnTabSelectedListener优化使用

下面代码一大坨,有时候真不想写代码了(狗头),先贴一下:

一、EditText的addTextChangedListener:

fun EditText.textWatcher(textWatch: SimpleTextWatcher.() -> Unit) {
    val simpleTextWatcher = SimpleTextWatcher(this)
    textWatch.invoke(simpleTextWatcher)
}

class SimpleTextWatcher(var view: EditText) {

    private var afterText: (Editable?.() -> Unit)? = null
    fun afterTextChanged(afterText: (Editable?.() -> Unit)) {
        this.afterText = afterText
    }

    private var beforeText: ((s: CharSequence?, start: Int, count: Int, after: Int) -> Unit)? = null
    fun beforeTextChanged(beforeText: ((s: CharSequence?, start: Int, count: Int, after: Int) -> Unit)) {
        this.beforeText = beforeText
    }

    private var onTextChanged: ((s: CharSequence?, start: Int, before: Int, count: Int) -> Unit)? =
        null

    fun onTextChanged(onTextChanged: ((s: CharSequence?, start: Int, before: Int, count: Int) -> Unit)) {
        this.onTextChanged = onTextChanged
    }

    init {
        view.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {
                afterText?.invoke(s)
            }

            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
                beforeText?.invoke(s, start, count, after)
            }

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                onTextChanged?.invoke(s, start, before, count)
            }
        })
    }
}

二、TabLayout的addOnTabSelectedListener

有了上面的基础,下面的就简单了:

fun TabLayout.onTabSelected(tabSelect: TabSelect.() -> Unit) {
    tabSelect.invoke(TabSelect(this))
}

class TabSelect(tab: TabLayout) {
    private var tabReselected: ((tab: TabLayout.Tab) -> Unit)? = null
    private var tabUnselected: ((tab: TabLayout.Tab) -> Unit)? = null
    private var tabSelected: ((tab: TabLayout.Tab) -> Unit)? = null

    fun onTabReselected(tabReselected: (TabLayout.Tab.() -> Unit)) {
        this.tabReselected = tabReselected
    }

    fun onTabUnselected(tabUnselected: (TabLayout.Tab.() -> Unit)) {
        this.tabUnselected = tabUnselected
    }

    fun onTabSelected(tabSelected: (TabLayout.Tab.() -> Unit)) {
        this.tabSelected = tabSelected
    }

    init {
        tab.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
            override fun onTabReselected(tab: TabLayout.Tab?) {
                tab?.apply { tabReselected?.invoke(tab) }
            }
            override fun onTabUnselected(tab: TabLayout.Tab?) {
                tab?.apply { tabUnselected?.invoke(tab) }
            }
            override fun onTabSelected(tab: TabLayout.Tab?) {
                tab?.apply { tabSelected?.invoke(tab) }
            }

        })
    }
}
  //使用
  tab.onTabSelected {
            onTabSelected {
                pos = position
            }
        }

总结:其实上面这两个都是很典型的DSL语法,利用Kotlin的DSL,充分的发挥Kotlin的优雅,适当的方法命名,恰当的设计,能让开发者或者维护人员更好地理解你的代码,毕竟如果是一大段代码放在那里,维护起来确实是有点累人的。

总结

总体而言,分享的内容并不多,都是一些我们日常经常用到的,平时多敲多写会有很多不同发现。