从Android开发的角度去认识Kotlin

作者: cooperise | 来源:发表于2017-09-14 22:52 被阅读281次

    在2017的Google I/O大会上,Google宣布,这门诞生于俄罗斯的年轻语言,即日起成为最新的一级安卓编程语言,并在Android Studio 3.0 已加入对其的支持。Kotlin是JetBrains设计并开源(最新开源版本为1.1.4)的一门静态编程语言,由于设计者是IDE著名开发商JetBrains公司,Kotlin从一开始就自带IDE 支持。在Intellij IDEA 15和Android Studio3.0之前的版本需要安装Kotlin插件,之后的版本自带Kotlin插件。

    Kotlin想给我们展现什么?

    1. 互操作与互兼容

    谈起Kotlin与Java,很多人估计会联想起Swift与OC,Swift是苹果于2014年WWDC(苹果开发者大会)发布的新开发语言,可与Objective-C共同运行于Mac OS和iOS平台。根据了解,现在国内一些大公司依然使用OC或者Swift与OC混编的方式开发iOS。这其中涉及很多原因:

    1. 早期版本的Swift编译速度和运行速度慢,导致用户觉得应用卡;
    2. 用Swift开发打包后的安装包比用OC的大;
    3. 再比如Swift的版本更新太快,不太稳定,开发者不得不花时间去适配到最新的Swift。
    4. Swift与OC并不能完全互操作,存在兼容性问题,除此以外虽然Swift调用OC比较简单,但OC里用Swift比较麻烦。(源自简书作者LingoGuo

    但是Kotlin,却与Java有着100%的互操作和互兼容性,并在编译速度和运行速度上,与Java相比并未有劣势可言:

    • 兼容性(Compatibility)—— Kotlin能兼容JDK 6,确保Kotlin的应用程序在老版本的Android设备上运行
    • 运行速度(Performance)—— Kotlin 应用程序的运行速度与 Java 差不多,但是随着Kotlin对内联函数的支持,使用 lambda 表达式的代码通常比用 Java 写的代码运行得更快
    • 互操作性(Interoperability)—— 用Java写的类库和代码可以继续在Kotlin的代码中继续沿用,并支持Kotlin和Java两种语言的混合编程
    • 占用空间(Footprint)—— Kotlin拥有一个紧密的runtime library,在ProGuard的作用下减小了更多内存的占用,在实际应用程序中,Kotlin 开发的apk比Java开发的apk增加不到 100K 的大小。
    • 编译时间(Compilation Time)—— Kotlin 支持高效的增量编译,所以对于清理构建会有额外的开销,增量构建通常与 Java 一样快或者更快(增量编译只重新编译代码中更改的部分)
    2. 易表现(简洁)
    • 常量与变量

    在Kotlin中,变量用var声明,常量用val声明,val声明的对象意味着它在实例化之后就不能再去改变它的状态了。如果你需要一个这个对象修改之后的版本,那就会再创建一个新的对象。这个让编程更加具有健壮性和预估性:

    val s = "Example" // A String
    val actionBar = supportActionBar // An ActionBar in an Activity context
    var i = 23 // An Int
    var m = 23.4 // An Double
    

    而在Java中,声明一个对象不可变需要加final属性,间接性一目明了:

    private final String s = "Example"
    
    • 基本类型

    在Kotlin中,基本类型自带转化方法:

    val i:Int = 7
    val d:Double = i.toDouble()
    val c:Char = 'c'
    val i:Int = c.toInt()
    
    • 函数

    Kotlin的函数可以给参数指定一个默认值使得它们变得可选,如下,第二个参数( length) 指定了一个默认值,意味着调用的时候可以传入第二个值或者不传,这样可以避免你需要的重载函数:

    fun toast(message: String, length: Int = Toast.LENGTH_SHORT) {
        Toast.makeText(this, message, length).show()
    }
    
    toast("Hello")
    toast("Hello", Toast.LENGTH_LONG)
    

    这个与下面的Java代码是一样的,明显Kotlin更加易于表现:

    void toast(String message){
        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
    } 
    
    void toast(String message, int length){
        Toast.makeText(this, message, length).show();
    }
    

    在Java中,如果我们要典型的数据类,我们需要去编写(至少生成) 这些代码:

    public class Artist {
        private long id;
        private String name;
        private String url;
        private String mbid;
    
        public long getId() {
            return id;
        } 
    
        public void setId(long id) {
            this.id = id;
        } 
    
        public String getName() {
            return name;
        }
      
        public void setName(String name) {
            this.name = name;
        } 
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        } 
    
        @Override 
        public String toString() {
            return "Artist{" + "id=" + id + ", name='" + name + '\'' + ", url='" + url + '\'' + '}';
        }
    }
    

    使用Kotlin,我们只需要通过数据类,这个数据类,它会自动生成所有属性和它们的访问器,以及一些有用的方法,比如toString():

    data class Artist(
        var id: Long,
        var name: String,
        var url: String,
        var mbid: String)
    

    如果我们使用不可修改的对象,假如我们需要修改这个对象状态,必须要创建一个新的一个或者多个属性被修改的实例。这个任务在java里是非常重复且不简洁的,然后Kotlin可以这样实现:

    val f1 = Forecast(Date(), 27.5f, "Shiny day")
    val f2 = f1.copy(temperature = 30f)
    

    而且Kotlin还支持映射对象的每一个属性到一个变量中:

    val f1 = Forecast(Date(), 27.5f, "Shiny day")
    val (date, temperature, details) = f1
    

    在Java中我们需要这样去实现:

    Forecast f1 = new Forecast(Date(), 27.5f, "Shiny day");
    Date date = f1.getDate();
    float temperature = f1.getTemperature();
    String details = f1.getDetails();
    
    • 操作符重载

    在Kotlin中,每个操作符都有对应的操作方法,如:

    操作符 对应方法
    a + b a.plus(b)
    a - b a.minus(b)
    a * b a.times(b)
    a / b a.div(b)

    正如这两个表达式是一样的意思:

    var num:Int = 1
    num = num.plus(1)      // 与num = num + 1效果一样
    

    这也就提供了重载操作符(扩展操作符)的方法:

    fun main(args: Array<String>){
        var num1 = Number(1, 1)
        var num2 = Number(1, 1)
        println((num1 + num2).toString())
    }
    
    operator fun Number.plus(other: Number):Number{
        this.one = this.one + other.one
        this.two = this.two + other.two
        return this
    }
    
    data class Number(
            var one: Int,
            var two: Int
    )
    

    输出结果为

    Number(one=2, two=2)

    • 强大的list
    val list = listOf(1, 2, 3, 4, 5, 6)
    list.any { it % 2 == 0 }         // 如果至少有一个元素符合给出的判断条件,则返回true
    list.all { it % 2 == 0 }         // 如果全部的元素符合给出的判断条件,则返回true
    list.count { it % 2 == 0 }       // 返回符合给出判断条件的元素总数
    list.forEach { println(it) }     // 遍历所有元素,并执行给定的操作
    list.forEachIndexed { index, value -> println("position $index contains a $value") }
                                     // 与 forEach ,但是我们同时可以得到元素的index
    list.max()                       // 返回最大的一项,如果没有则返回null
    list.maxBy { -it }               // 根据给定的函数返回最大的一项,如果没有则返回null
    list.min()                       // 返回最小的一项,如果没有则返回null
    list.minBy { -it }               // 根据给定的函数返回最小的一项,如果没有则返回null
    list.sumBy { it % 2 }            // 返回所有每一项通过函数转换之后的数据的总和
    
    list.drop(4)                     // 返回包含去掉前n个元素的所有元素的列表
    list.filter { it % 2 == 0 }      // 过滤所有符合给定函数条件的元素
    
    list.contains(2)                 // 如果指定元素可以在集合中找到,则返回true
    list.elementAt(1)                // 返回给定index对应的元素
    list.first { it % 2 == 0 }       // 返回符合给定函数条件的第一个元素
    list.indexOf(4)                  // 返回指定元素的第一个index,如果不存在,则返回 -1 
    list.indexOfFirst { it % 2 == 0 }             
                                     // 返回第一个符合给定函数条件的元素的index,如果没有符合则返回 -1 
    list.indexOfLast{ it % 2 == 0 }  // 返回最后一个符合给定函数条件的元素的index,如果没有符合则返回 -1
    
    list.reverse()                   // 返回一个与指定list相反顺序的list
    list.sort()                      // 返回一个自然排序后的list
    list..sortBy { it % 3 }          // 返回一个根据指定函数排序后的list
    
    ......
    
    • 流程控制

    if表达式可实现赋值操作:

    val z = if (condition) x else y
    

    when表达式代替switch/case

    when (x){
        1 -> print("x == 1")
        2 -> print("x == 2")
        else -> {
            print("I'm a block")
            print("x is neither 1 nor 2")
        }
    }
    
    val result = when (x) {
        0, 1 -> "binary"
        else -> "error"
    }
    
    when(view) {
        is TextView -> view.setText("I'm a TextView")
        is EditText -> toast("EditText value: ${view.getText()}")
        is ViewGroup -> toast("Number of children: ${view.getChildCount()} ")
        else -> view.visibility = View.GONE
    }
    
    val cost = when(x) {
        in 1..10 -> "cheap"
        in 10..100 -> "regular"
        in 100..1000 -> "expensive"
        in specialValues -> "special value!"
        else -> "not rated"
    }
    
    val res = when{
        x in 1..10 -> "cheap"
        s.contains("hello") -> "it's a welcome!"
        v is ViewGroup -> "child count: ${v.getChildCount()}"
        else -> ""
    }
    

    Range 表达式使用一个 .. 操作符,它是被定义实现了一个 RangTo 方法。Ranges 帮助我们使用很多富有创造性的方式去简化我们的代码。比如我们可以把它:

    if(i >= 0 && i <= 10) println(i)
    

    转化成:

    if (i in 0..10) println(i)
    
    3. 空安全

    我们有时候的确需要去定义一个变量包不包含一个值。在Java中尽管注解和IDE在这方面帮了我们很多,但是我们仍然可以这么做:

    Forecast forecast = null;
    forecast.toString();
    

    这个代码可以被完美地编译( 你可能会从IDE上得到一个警告) ,然后正常地执行,但是显然它会抛一个NullPointerException 。这个相当不安全的。当然,在Kotlin中,也可以有一个可null的对象(用?标记):

    val a: Int? = null
    

    然而一个可null类型,你在没有进行检查之前你是不能直接使用它,这个代码不能被编译:

    val a: Int? = null
    a.toString()        // 编译失败
    a?.toString()       // 编译成功
    if(a!=null){        // 编译成功
        a.toString()
    }
    
    4. 可扩展方法

    Kotlin允许我们给任何类添加函数。它比那些我们项目中典型的工具类更加具有可读性。举个例子,我们可以给fragment增加一个显示toast的函数:

    fun Fragment.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
        Toast.makeText(getActivity(), message, duration).show()
    }
    

    我们现在可以这么做:

    fragment.toast("Hello world!")
    

    注:扩展函数并不是真正地修改了原来的类,它是以静态导入的方式来实现的。扩展函数可以被声明在任何文件中,因此有个通用的实践是把一系列有关的函数放在一个新建的文件里。

    5. 函数式支持(lambda)

    一个lambda表达式通过参数的形式被定义在箭头的左边( 被圆括号包围) ,然后在箭头的右边返回结果值。在这个例子中,我们接收一个View,然后返回一个Unit( 没有东西) 。所以根据这种思想,我们可以把前面的代码简化成这样:

    view.setOnClickListener({ view -> toast("Click")})
    

    这是非常棒的简化!当我们定义了一个方法,我们必须使用大括号包围,然后在箭头的左边指定参数,在箭头的右边返回函数执行的结果。如果左边的参数没有使用到,我们甚至可以省略左边的参数:

    view.setOnClickListener({ toast("Click") })
    

    如果这个函数的最后一个参数是一个函数,我们可以把这个函数移动到圆括号外面:

    view.setOnClickListener() { toast("Click") }
    

    并且,最后,如果这个函数只有一个参数,我们可以省略这个圆括号:

    view.setOnClickListener { toast("Click") }
    

    比原始的Java代码简短了5倍多,并且更加容易理解它所做的事情,非常让人影响深刻。

    Kotlin Android Extensions有什么用?

    Kotlin Android Extensions是Kotlin团队研发的可以让开发更简单的插件,Kotlin Android Extensions自动创建了属性让开发者直接访问XML中的view,而不需要在开始使用之前明确地从布局中去找到这些views。

    注:属性的名字就是来自对应view的id,所以取id的时候要十分小心,因为它们将会是我们类中非常重要的一部分。这些属性的类型也是来自XML中的,所以不需要去进行额外的类型转换。

    在使用的时候需要我们需要使用import 语句,以 kotlin.android.synthetic 开头,然后加上需要绑定到Activity的布局XML的名字:

    import kotlinx.android.synthetic.activity_main.*
    

    然后就可以直接使用View对象了:

    class MainActivity : Activity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            textView.setText("Hello, world!")
            // Instead of findViewById(R.id.textView) as TextView
        }
    }
    

    那它背后是怎么工作的?其实正如Kotlin支持扩展方法一样,Kotlin也支持扩展属性,通过获取布局文件的控件id,以其为名添加相应控件作为该Activity的扩展属性,并与布局中的控件通过findViewById等方式获取控件实例(映射)。

    该插件会代替任何属性调用函数,比如获取到view,并具有缓存功能,以免每次属性被调用都会去重新获取这个view。需要注意的是这个缓存装置只会在 Activity 或者 Fragment 中才有效。如果它是在一个扩展函数中增加的,这个缓存就会被跳过,因为它可以被用在 Activity 中但是插件不能被修改,所以不需要再去增加一个缓存功能。

    Anko!强大?方便?

    Anko是JetBrains开发的一个强大的库。它主要的目的是用来替代以前XML的方式来使用代码生成UI布局。如:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
    
        linearLayout {
            button("Login") {
                textSize = dip(16)
                onclick{
                    clickButton()
                }
            }.lparams(width = wrapContent) {
                 horizontalMargin = dip(5)
                 topMargin = dip(10)
            }
        }
    }
    

    以上代码相当于:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Login"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            android:layout_marginTop="10dp"/>
    
    </LinearLayout>
    

    这种通过代码生成UI布局的方式虽然方便,但是,这种由代码生成布局的方式,不利于控制器(Controller)与视图(View)的分离,处理不当可能会造成Activity代码臃肿;而且这种方式不支持视图的预览,必须运行之后才能看清楚效果。从个人的UI绘制经验出发,使用XML会更容易一些。

    但是,Anko还是有其优势的,最重要的一点就是上方提及的扩展函数(方法),扩展函数数是指在一个类上增加一种新的行为,甚至我们没有这个类代码的访问权限。另外,Anko也支持扩展属性:

    public var TextView.text: CharSequence
        get() = getText()
        set(v) = setText(v)
    

    所以,当你在Fragment中看见activity的引用时不要惊讶,那其实就是使用getActivity()的扩展属性,再如当给ListView设置适配器时:

    listview.adapter = mAdapter;          // .adapter等同于set/getAdapter
    

    还有,当一个Listener有多个方法时,Anko就显得很方便了, 看下面的代码(没有使用Anko):

    seekBar.setOnSeekBarChangeListener(object: OnSeekBarChangeListener {
        override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
            // Something
        }
        override fun onStartTrackingTouch(seekBar: SeekBar?) {
            // Just an empty method
        }
        override fun onStopTrackingTouch(seekBar: SeekBar) {
            // Another empty method
        }
    })
    

    使用了Anko之后:

    seekBar {
        onSeekBarChangeListener {
            onProgressChanged { seekBar, progress, fromUser ->
                // Something
            }
        }
    }
    

    我相信,当越来越多的Android开发者认识到Kotlin的魅力后,Kotlin会真正成为Android开发的主流语言,以上的分享也只是简单的认识,如果读者有发现Kotlin更加诱人的魅力,希望能够多多分享,小牧在此先谢过啦(真诚脸)。

    相关文章

      网友评论

        本文标题:从Android开发的角度去认识Kotlin

        本文链接:https://www.haomeiwen.com/subject/qdlodxtx.html