• Kotlin高阶函数&DSL布局劝退指南


    目录

    1. 一、背景
    2. 二、相关介绍
    3. 三、实际运用
    4. 四、总结

    一、背景:

    运行时app打开某个页面,必须做的事情有:

    1.先把xml文件加载到内存

    2.解析xml标签,读取布局

    3.渲染绘制各层级View到屏幕

    而使用代码直接动态绘制页面布局的话,就不需要这1、2两个耗时步骤。

    实际测试对比,简单单层布局的页面就是20ms->2ms左右的巨大提升。如果是复杂或层级更深的页面,提升更大。

    有利就有弊,不足的是:

    1.代码动态布局,代码可读性较差

    2.页面布局有变动,维护难度较大

    3.代码量较大,同一类里编写,代码行数容易突破750行

    4.界面无法预览,为了提升运行效率,而牺牲了开发效率

    二、相关介绍

    2.1 高阶函数

    高阶函数是一种特殊的函数:它的参数或者返回值是另一个函数。比如平时开发用的传参回调,可以省去回调接口、实现的代码编写:

        private fun exampleFun(emit: (bean: ImEventBean) -> Unit) {
            ...
            ...
            val result = GsonUtils.fromJson(json, ImEventBean::class.java)
            emit(result)
        }
    exampleFun { 
        EventBusUtils.post(it)
    }

    或者如:常用的let函数

    public inline fun  T.let(block: (T) -> R): R {
        contract {
            callsInPlace(block, InvocationKind.EXACTLY_ONCE)
        }
        return block(this)
    }

    2.2、内联函数

    简单理解,就是编译时,把函数体复制粘贴到函数调用处,减少一次方法栈的开辟(局部变量表、操作栈等附带开支)

    主要运用场景,用于配合高阶函数/Lambda的使用。其中与Lambda的配合,主要是为了使其允许局部返回,如:

    return@let

    2.3 Kotlin中的Lambda

    Kotlin 中的 Lambda函数,可以访问接收者的非私有成员。如:

    info.apply{
        updateName(info.nickName)//获取非私有成员
        info.age = mInput.text.toString()//设置非私有成员
    }

    2.4 DSL改善动态代码布局的可读性

    DSL(domain specific language),全称“特定领域语言”,DSL专注特定领域的操作/表达,具有结构性。例如HTML、SQL也是一种具有结构性DSL语言

    基于Kotlin 自带接收者的lambda(&高阶函数)的特性,结合DSL可读化的结构性质,可以对动态布局代码进行可读性的优化,实践举例如下:

    ConstraintLayout {
            layout_width = match_parent
            layout_height = match_parent
    
            ImageView {
                layout_id = "ivBack"
                layout_width = 28
                layout_height = 28
                margin_start = 16
                margin_top = 18
                src = R.drawable.ic_back
                start_toStartOf = parent_id
                top_toTopOf = parent_id
                onClick = { onBackClick() }
            }
    
            TextView {
                layout_width = wrap_content
                layout_height = wrap_content
                text = "commit"
                textSize = 16f
                textStyle = bold
                align_vertical_to = "ivBack"
                center_horizontal = true
            }
    }

    三、实际运用

    此处仅举例,具体以demo&lib_dsl为准,DemoGit

    3.1 高阶函数扩展View的构建

    inline fun ViewGroup.TextView(
        style: Int? = null,
        autoAdd: Boolean = true,
        init: AppCompatTextView.() -> Unit
    ): TextView {
        val textView =
        if (style != null) AppCompatTextView(ContextThemeWrapper(context, style))
        else AppCompatTextView(context)
        return textView.apply(init).also { if (autoAdd) addView(it) }
    }

    然后就可以这么用

    3.2 扩展xml属性

    inline var View.layout_width: Number
        get() {
            return 0
        }
        set(value) {
            val w = if (value.dp > 0) value.dp else value.toInt()
            val h = layoutParams?.height ?: 0
            updateLayoutParams {
                width = w
                height = h
            }
        }

    3.3 定义顶层属性值

    val wrap_content = ViewGroup.LayoutParams.WRAP_CONTENT
    val match_parent = ViewGroup.LayoutParams.MATCH_PARENT

    然后就可以在kt代码中这么用

    3.4 获取控件

    因为DSL的结构化构建布局,我们可以直接声明成员变量,构建view时直接赋值,也可以后续需要时,再通过父view.findViewById()获取

        lateinit var btnMenu: View
    
        ...
        mRootView = context.ConstraintLayout {
            ...
            // 直接赋值
            btnMenu = TextView {
                            //声明id 后续再findViewById()
                            layout_id = "tvMenu"
                            layout_width = wrap_content
                            layout_height = wrap_content
                            textSize = 16F
                            text = "菜单"
                        }
            ...
        }
    
    

    3.5 使用

    将动态布局代码独立为一个类,并在其中使用DSL编写布局。然后再在需要的地方进行初始化、使用

    /** 布局dsl 等价于 viewBinding文件 */
    private val contentViewDsl by lazy { SecondActivityDslLayout().inflate(this) }
            val beforeMills = System.currentTimeMillis()
            // 懒加载,setView
            setContentView(contentViewDsl.getRoot())
            val totalMills = System.currentTimeMillis() - beforeMills
    
            Log.d(this.javaClass.simpleName, "================= 页面构建耗时:${totalMills}ms")
            // 类似ViewBinding使用控件
            contentViewDsl.tvTitle.text = "耗时:${totalMills}ms"

    3.6 效果与分析

    最终运行效果与xml一致,可以运行代码查看。

    与xml相比首次渲染布局dsl耗时更久。

    我们通过Android Studio工具,查看DSL布局.kt文件 编译后的情况,发现仅Demo简单的页面,编译后的Java代码已经是几千行。其中一部分原因是扩展函数使用了inline关键字,另一部分原因是kotlin自身的特性。

    以上Demo是不涉及 点击或者变化监听 的纯UI布局代码,如果是正常业务的页面,那么生成的Java类文件将会是非常庞大的,后期依然是要被优化的。

    四、总结

    1.使用DSL代码动态布局,解决了普通代码可读性差的问题,可读性与xml 基本持平

    2.页面布局有变动,维护难度降低,利于扩展

    3.编写代码量较小,界面绘制效率大大优于xml

    4.界面无法预览是硬伤,为了提升运行效率,而牺牲了开发效率,建议后期不需兼顾低版本用户时,切换到Jetpack Compose

    5.前期DSL扩展库编写需要耗费较多时间,实际使用的话推广难度较大

    6.DSL布局.kt 代码转化为Java代码,代码量随着页面复杂度增加而增加

    总而言之,等允许放弃5.0以下的用户的时候,直接上Jetpack Compose吧。

    五、代码

    KotlinDSL: DSL lib 动态编写布局

  • 相关阅读:
    1.shell命令入门
    python标准模块----logging
    家校协同小程序实战教程
    js基础笔记学习318练习1
    小白学习-ElasticSearch教程(3) -文档查询之term精确查询
    关于网球的要点
    深度解析:Web 3.0和元宇宙
    cwe_checker初识别
    synchronized已经不在臃肿了,放下对他的成见之初识轻量级锁
    【Rust日报】2022-08-14 Actix Web 的可扩展速率限制中间件
  • 原文地址:https://blog.csdn.net/Cupster/article/details/125889841