职能企业保护职业创业
投稿投诉
创业安检
掌握呵护
案例安身
升初专业
职业小学
形象知识
工作提高
科技职级
保护职务
法务经验
安保方法
中考作文
技巧安全
初中魅力
常识安心
保全安护
企业技能
生活安务
法律纠纷
技艺百科
职能能力
技术安监
培训提升
辩护水平

KotlinDSL

2月26日 溷元楼投稿
  什么是DSL?
  DSL全称是DomainSpecificLanguage,即领域特定语言。顾名思义DSL是用来专门解决某一特定问题的语言,比如我们常见的SQL或者正则表达式等,DSL没有通用编程语言(Java、Kotlin等)那么万能,但是在特定问题的解决上更高效。
  设想以下有这样一种场景,如果我们希望其他人跟随我们既定的规则,排除定义接口、类、方法这种传统方式,我们能怎么实现呢?
  自己去开发一门语言,难度可想而知。其实可以转换一下思路,我们可以基于已有的通用编程语言打造自己的DSL,比如日常开发中我们将常见到gradle脚本,其本质就是来自Groovy的一套DSL:android{compileSdkVersion30defaultConfig{applicationIdx。xx。xxxminSdkVersion24targetSdkVersion30versionCode1versionName1。0testInstrumentationRunnerandroid。support。test。runner。AndroidJUnitRunner}buildTypes{release{minifyEnabledfalseproguardFilesgetDefaultProguardFile(proguardandroidoptimize。txt),proguardrules。pro}}}
  使用大括号表现层级结构,使用键值对的形式设置参数,没有多余的程序符号,非常直观。还原成标准的Groovy语法则变成如下:Android(30,DefaultConfig(com。my。app,24,30,1,1。0,android。support。test。runner。AndroidJUnitRunner)),BuildTypes(Release(false,getDefaultProguardFile(proguardandroidoptimize。txt),proguardrules。pro))
  我们可以对比一下上下两段代码,很明显上面一段的代码可读性更高!
  目前Gradle已经开始推荐使用kts替代gradle,其实就是利用了Kotlin优秀的DSL特性。KotlinDSL
  Kotlin早几年已经被Google作为Android应用软件开发的主要编程语言,在很多场景下,我们已经看到了DSL在Android开发中发挥的优势,并且可以有效地提升开发效率。例如JetpackCompose的UI代码就是一个很好的示范,它借助DSL让Kotlin代码具有了不输于XML的表现力,同时还兼顾了类型安全,提升了UI开发效率。
  XML布局
  xmlView
  ComposeDSL布局
  composeView
  通过对比可以看到KotinDSL有诸多好处:有着近似XML的结构化表现力;较少的字符串,更多的强类型,更安全;linearLayoutParams这样的对象可以多次复用,更方便组件化和维护;可以在定义布局的同时实现onClick等事件;还可以嵌入if,for这样的控制语句,用于根据状态动态控制和显示V
  在没有DSL之前,如果我们想要实现这样的效果,代码可能会写成这样,如下:LinearLayout(context)。apply{addView(ImageView(context)。apply{imagecontext。getDrawable(R。drawable。avatar)},LinearLayout。LayoutParams(context,null)。apply{。。。})addView(LinearLayout(context)。apply{。。。},LinearLayout。LayoutParams(context,null)。apply{。。。})addView(Button(context)。apply{setOnClickListener{。。。}},LinearLayout。LayoutParams(0,0)。apply{。。。})}
  虽然代码已经借助apply等作用域函数进行了优化,但写起来仍然很繁琐,同时阅读起来也比较困难。
  小结:
  通过上面的代码实例,我们已经能感受到KotlinDSL带来的冲击,并且伴随着JetpackCompose的快速迭代,所带来的性能提升、即时预览等等,在不远的未来传统的xml布局开发方式,也会被取代(Android平台Java基本已经被Kotlin取代)。Kotlin是怎么实现DSL?
  常见的DSL都会用大括号来表现层级。Kotlin的高阶函数允许指定一个lambda类型的参数,且当lambda位于参数列表的最后位置时可以脱离圆括号,满足DSL中的大括号语法要求。
  那么我们不妨先尝试去改造一下下面这段代码:LinearLayout(context)。apply{orientationLinearLayout。HORIZONTALaddView(ImageView(context))}
  为LinearLayout定义一个高阶函数HorizontalLayout,用于表示水平布局,代码如下:函数定义funHorizontalLayout(context:Context,init:(LinearLayout)Unit):LinearLayout{returnLinearLayout(context)。apply{orientationLinearLayout。HORIZONTALinit(this)}}函数调用HorizontalLayout(context){。。。it。addView(ImageView(context))。。。}
  通过上面函数调用,可以看出来虽然省略了apply,但是离我们想要的结果还是有很远差距。大括号内部,还是需要使用it来进行相关函数的调用,addView方法也带有浓重的传统写法味道。
  我们再进一步对函数定义进行优化函数定义funHorizontalLayout(context:Context,init:LinearLayout。()Unit):LinearLayout{returnLinearLayout(context)。apply{orientationLinearLayout。HORIZONTALinit()}}隐藏addView方法到ImageView内部,同时ViewGroup添加拓展函数由于不用把ImageView实例返回给父View,直接返回UnitfunViewGroup。ImageView(init:ImageView。()Unit){addView(ImageView(context)。apply(init))}优化之后的,函数调用HorizontalLayout{。。。ImageView{。。。}。。。}
  再看一下DSL改造view的setOnClickListener的代码,如下:函数定义funView。onClick(listener:(v:View)Unit){setOnClickListener(listener)}函数调用Button。onClick{。。。。。。}
  当然这个例子中由于setOnClickListener是一个SAM接口,优势并不明显。下面我们用EdttText的addTextChangedListener方法,来进一步展示DSL的优势,对于EditText代码通常如下:EditText{addTextChangedListener(object:TextWatcher{overridefunbeforeTextChanged(。。。){。。。}overridefunonTextChanged(。。。){。。。。}overridefunafterTextChanged(。。。){。。。。}}}
  使用DSL进行改造funEditText。textChangeListener(init:TextWatcher。()Unit){vallistenerTextWatcher()listener。init()addTextChangedListener(listener)}classTextWatcher:android。text。TextWatcher{privatevaronTextChanged:((Charsequence?,Int,Int,Int)Unit)?nulloverridefunonTextChanged(s:CharSequence?,start:Int,before:Int,count:int){onTextChanged?。invoke(s,start,before,count)}funonTextChanged(listener:(Charsequence?,Int,Int,Int)Unit){onTextChangedlistener}beforeTextChanged和afterTextChanged相关代码省略}
  函数调用代码如下,同时我们在调用的过程中可以按需调用内部的三个方法,而不用每次都必须复写三个方法。EditText{textChangedListener{beforeTextChanged{charSequence,i,i2,i3。。。}onTextChanged{charSequence,i,i2,i3。。。}afterTextChanged{。。。}}}KotlinDSL更进一步
  经过前面的一步一步的优化,我们的DSL基本达到了预期效果,接下来通过更多Kotlin的特性让这套DSL更加好用,并且语义更加清晰。infix增强可读性
  Kotlin的中缀函数可以让函数省略圆点以及圆括号等程序符号,让语句更自然,进一步提升可读性。
  比如所有的View都有setTag方法,正常使用如下:HorizontalLayout{setTag(1,tag1)setTag(2,tag2)}
  我们使用中缀函数来优化setTag的调用如下:classTag(valview:View){infixfunBInt。to(that:B)View。setTag(this,that)}funView。tag(block:Tag。()Unit){Tag(this)。apply(block)}
  DSL中调用代码如下:HorizontalLayout{tag{1totag12totag2}}DslMarker限制作用域HorizontalLayout{this:LinearLayout。。。TextView{this:TextView此处仍然可以调用HorizontalLayoutHorizontalLayout{。。。}}}
  上面这段代码,我们发现在TextView{。。。}内部可以调用HorizontalLayout{。。。},这明显是不符合逻辑的。由于TextView的作用域同时处于父HorizontalLayout的作用域内,所以上面代码编译器任务其内容的HorizontalLayout{。。。}是在调用thisLinearLayout不会报错。如果编译器不报错,那么将提升代码的bug,同时也不利于我们日常开发功能。
  Kotlin为DSL的使用场景提供了DslMarker注解,可以对方法的作用域进行限制。添加注解的lambda中在省略this的隐式调用时只能访问到最近的类型,当调用更外层的的方法会报错。
  DslMarker是一个元注解,我们需要基于它定义自己的注解,如下:DslMarkerTarget(AnnotationTarget。TYPE)annotationclassViewDslMarker
  接着,在尾lambda的Receiver添加此注解,如下:funViewGroup。TextView(init:(ViewDslMarkerTextView)。()Unit){addView(TextView(context)。apply(init))}
  TextView{。。。}中如果不写this。则只能调用TextView的方法,如果想调用外层的方法,必须显示的使用thisxxx进行调用。ContextReceivers传递多个上下文
  先看一段代码,如下:funView。dp(value:Int):Int(valuecontext。resources。displayMetrics。density)。toInt()HorizontalLayout{TextView{layoutParamsLinearLayout。LayoutParams(context,null)。apply{widthdp(60)height0weight1。0}}}RelativeLayout{TextView{layoutParamsRelativeLayout。LayoutParams(context,null)。apply{widthdp(60)heightViewGroup。LayoutParams。WRAPCONTENT}}}
  上面的代码中有几点可以使用context帮助改善。
  首先,代码中使用带参数的dp(60)进行dip转换。我们可以通过前面介绍的context语法替换为60f。dp这样的写法,避免括号的出现,写起来更加舒适。
  此外,我们知道View的LayoutParams的类型由其父View类型决定,上面代码中,我们在创建LayoutParams时必须时刻留意类型是否正确,心理负担很大。
  这个问题也可以用context很好的解决,如下我们为TextView针对不同的context定义layoutParams扩展函数:context(RelativeLayout)funTextView。layoutParams(block:RelativeLayout。LayoutParams。()Unit){layoutParamsRelativeLayout。LayoutParams(context,null)。apply(block)}context(LinearLayout)funTextView。layoutParams(block:LinearLayout。LayoutParams。()Unit){layoutParamsLinearLayout。LayoutParams(context,null)。apply(block)}
  在DSL中使用效果如下:
  TextView的layoutParams{。。。}会根据父容器类型自动返回不同的this类型,便于后续配置。使用inline和PublishedApi提高性能
  DSL的实现使用了大量高阶函数,过多的lambda会产生过的匿名类,同时也会增加运行时对象创建的开销,不少DSL选择使用inline操作符,减少匿名类的产生,提高运行时性能。
  比如为ImageView的定义添加inline:inlinefunViewGroup。ImageView(init:ImageView。()Unit){addView(ImageView(context)。apply(init))}
  inline函数内部调用的函数必须是public的,这会造成一些不必要的代码暴露,此时可以借助PublishedApi化解。resInt指定图片inlinefunViewGroup。ImageView(resId:Int,init:ImageView。()Unit){ImageView(init)。apply{setImageResource(resId)}}drawable指定图片inlinefunViewGroup。ImageView(drawable:Drawable,init:ImageView。()Unit){ImageView(init)。apply{setImageDrawable(drawable)}}PublishedApiinternalinlinefunViewGroup。ImageView(init:ImageView。()Unit)ImageView(context)。apply{thisImageView。addView(this)init()}
  如上,为了方便DSL中使用,我们定义了两个ImageView方法,分别用于resId和drawable的图片设置。由于大部分代码可以复用,我们抽出了一个ImageView方法。但是由于要在inline方法中使用,所以编译器要求ImageView必须是public类型。ImageView只需在库的内部服务,所以可以添加为internal的同时加PublishdApi注解,它允许一个模块内部方法在inline中使用,且编译器不会报错。总结
  经过上面的步骤,我们已经基本能实现02中ComposeDSLview代码,但是kotlinDSL的运用场景远不止UI这一种,但是基本思路都是相通的,我们再回顾一下基本步骤:使用带有尾lambda的高阶函数实现大括号的层级调用;为lambda添加Receiver,通过this传递上下文;通过扩展函数优化代码风格,DSL中避免出现命令式的语义;使用infix减少点号圆括号等符号的出现,提高可读性;使用DslMarker限制DSL作用域,避免出错;使用ContextReceivers传递多个上下文,DSL更聪明(非正式语法,未来有变动的可能);使用inline提升性能,同时使用PublishedApi避免不必要的代码暴露;
投诉 评论 转载

上年纪的人左手摇扇可防脑溢血推荐:老年人与子女相处的几大禁忌摇扇是一种单侧肢体运动,不仅可使肢体的关节、肌肉得到锻炼,还可锻炼大脑血管的收缩和舒张功能。由于……日下造句用日下造句大全121)2006年3月2日下午2时许,孙团结伙同袁继学、刘佩春等十余人窜至金乡县影视中心广场,光天化日之下行凶作恶。122)2005年5月24日下午3时,福建水口水电站1……儿童用药知识家长请勿越过安全线小编曾碰到过一个比较极端的例子,孩子感冒发烧了,家长让他同时吃了抗生素、化痰药、止咳糖浆、还有扑尔敏等5种药,结果吃到第五天,孩子就出现了血尿。小编提醒父母应熟知儿童用药知识:……分娩采取这些姿势可缓和产痛改变姿势有时能够意外有效的缓解分娩阵痛的难过,从以下介绍的姿势中产妇不妨积极的尝试,找寻自己感觉最舒服的姿势。来回踱步当阵痛尚不强烈的时候,活动一下身体会比一直躺在……叫好不叫座的典范,沃尔沃S60为何降价也卖不动?沃尔沃S60叫好不叫座的原因有三点作为外国品牌的汽车不论以合资或进口方式销售,一旦销量低迷则会被定义为叫好不叫座;这种人设实际是拉屁股盖脸,品牌叫好的车辆是必然会热销,反……超火日系女生短发发型打卡女生最爱短发快来围观ins超火日系女生短发发型日系风短发很火很流行,很受女生们的欢迎,大部分剪短发的女生去理发店都是参照日系女生的短发发型。今天小编特意给大家收集了一些日系风的女生短发发型,……头发干燥没光泽怎么办?头发干燥没光泽怎么办?头发干燥没光泽可以通过饮食调理和改变生活习惯等进行护理。头发干枯毛躁是由于头发缺少脂质和水分导致头发干燥枯槁而失去光泽的现象,可以通过以下方式……采茶春姑娘迈着轻柔的步子来到了茶山,茶树脱下暗绿色的棉袄,换上了嫩绿的新装。和往年一样,妈妈又带着我上山采茶叶。爬上茶山,一股清香幽幽地飘进了我的鼻子。一望无际的茶山上,茶叶……最令日军胆寒的战役民兵身绑炸药誓死保卫家国抗日战争期间,日军对中国很多城市发起了大规模的进攻,尤其是在七七事变爆发后,全国军民进入了长达八年的全面抗战时期,面对国家和民族的危难,很多地方部队都为保家卫国做出了自己的重大……KotlinDSL什么是DSL?DSL全称是DomainSpecificLanguage,即领域特定语言。顾名思义DSL是用来专门解决某一特定问题的语言,比如我们常见的SQL或者正则表达式……甄嬛传电视剧中皇帝对从寺里回来的甄嬛是怎么样的一种心理态度?谢邀!要想分析出皇帝对回来后的甄嬛是一个怎样的心理态度,首先我们要先知道甄嬛出宫前皇帝对她的态度。雍正其实在殿选第一次看见甄嬛时,心里就隐约把她当成纯元的替身,才有……匈奴志出现于战国中后期匈奴人以前在哪里匈奴是一个历史悠久的北方游牧民族,我们熟悉的汉朝就深受其扰,随后与匈奴展开过许多次战争。这个强大的北方游牧民族历史记载出现于战国中后期,随后匈奴就一直不安分,总是想着南下……
女儿读初一,期末考勉强进A班,但她自认这次考试运气大于她能力内蒙古鄂尔多斯都有哪些古镇?1难以置信!荷兰两大猛将一战身价暴增,巴萨有望筹集到过亿欧元Roguelike卡牌游戏高考之路上架Steam发售日期待定新生儿月子里可以穿纸尿裤吗?重庆除了城市辖区面积第一外,还有什么是世界第一?天天穿衣服,你知道怎么把身上的衣服拍好看吗?拍衣服有什么摄影许国利灭妻碎尸始末作案后面对媒体假装深情,日用2吨水露马脚将生态高颜值转化为经济高价值靖康之难北宋王朝突如其来的灭顶之灾,汉民族难以想象的屈辱每日一习话脚踏实地,埋头苦干,积跬步以至千里

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找常识日常社交礼仪安全防范适应宝库新闻军事国内国际财经股票基金外汇科技手机众测体育娱乐时尚女性育儿