【Bugly 技術乾貨】Android開發必備知識:爲何說Kotlin值得一試

一、Hello, Kotlin

Bugly 技術乾貨系列內容主要涉及移動開發方向,是由 Bugly 邀請騰訊內部各位技術大咖,經過平常工做經驗的總結以及感悟撰寫而成,內容均屬原創,轉載請標明出處。html

1.1 Kotlin的身世

  • 寫了許久Java,有沒有發現其實你寫了太多冗餘的代碼?java

  • 後來你體驗了一下Python,有沒有以爲不寫分號的感受真是超級爽?android

  • 你雖然勤勤懇懇,可到頭來卻被NullPointerException折磨的死去活來,難道就沒有受夠這種日子麼?git

  • 直到有一天你發現本身已經寫了好幾十萬行代碼,發現竟然全是getter和setter!程序員

哈哈,實際上你徹底能夠不用這麼痛苦,用Kotlin替代Java開發你的程序,不管是Android仍是Server,你都能像以前寫Java同樣思考,同時又能享受到新一代編程語言的特性,說到這裏你是否是開始心動了呢?下面我就經過這篇文章來給你們介紹一下Kotlin到底是何方神聖。github

話說,Kotlin是JetBrain公司搞出來的,運行在JVM上的一門靜態類型語言,它是用波羅的海的一個小島的名字命名的。從外觀上,乍一看還覺得是Scala,我曾經琢磨着把Scala做爲個人下一門語言,不過想一想用Scala來幹嗎呢,我又不作大數據,而它又太複雜了o(╯□╰)o編程

用Kotlin建立一個數據類api

data class Mondai(var index: Int = 0,
                  var title: String = "",
                  val ans: ArrayList<String> = ArrayList(),
                  var correct: Int = 0,
                  var comment: String = "",
                  var color: String = "",
                  private var lives: Int = 50)

最初是在intelliJ的源碼中看到Kotlin的,那時候Kotlin的版本還不太穩定,因此源碼老是編譯不過,真是要抓狂啊,還罵『什麼破玩意兒!爲何又出來新語言了?Groovy還沒怎麼學會,又來個Kotlin!』話說,Kotlin,難道是『靠它靈』的意思??安全

其實通過一年多的發展,Kotlin 1.0已經release,feature基本完善,api也趨於穩定,這時候嘗試也不會有那種被坑的感受了。過年期間也算悠閒,因而用Kotlin作了個app,簡單來講,就是幾個感受:bash

  • 思路與寫Java時同樣,不過更簡潔清爽

  • 少了冗餘代碼的煩惱,更容易專一於功能的開發,整個過程輕鬆愉快

  • 擴展功能使得代碼寫起來更有趣

  • 空安全和不可變類型使得開發中對變量的定義和初始化傾注了更多關注

  • 啊啊,我不再用寫那個findViewById了,真的爽爆有木有!

1.2 第一個Kotlin程序

Kotlin開發固然使用JetBrain系列的IDE,實際上intelliJ idea 15發佈時就已經內置了Kotlin插件,更早的版本則須要到插件倉庫中下載安裝Kotlin插件——在安裝時你還會看到有個Kotlin Extensions for Android,不要管他,已通過時了。安裝好之後,咱們就可使用Kotlin進行開發了。

接下來咱們用Android Studio建立一個Android工程,好比叫作HelloKotlin,在app目錄下面的build.gradle文件中添加下面的配置:

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
ext.anko_version = '0.8.2'
ext.kotlin_version = '1.0.0'
……

dependencies{
……
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    compile "org.jetbrains.anko:anko-sdk15:$anko_version"
    compile "org.jetbrains.anko:anko-support-v4:$anko_version"
    compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
……
}

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}
……

這裏添加了kotlin對android的擴展,同時也添加了kotlin的gradle插件。

接下來就能夠編寫kotlin代碼了——等等,Android Studio會幫咱們生成一個MainActivity,你能夠直接在菜單

Code -> Convert Java file to Kotlin file

將這個java代碼轉換爲kotlin代碼。截止到如今,你什麼都不用作,程序就已經能夠跑起來了。

二、完美爲Java開發者打造

2.1 通用的集合框架

咱們都知道Jvm上面的語言,像什麼Java、Groovy、Jython啥的,都是要編成虛擬機的字節碼的,一旦編成字節碼,在必定程度上你們就都平等了。

英雄不問出身啊

有人作過一個很是形象的比喻:Java虛擬機語言就是打羣架。Kotlin正是充分利用了這一點,它本身的標準庫只是基於Java的語言框架作了許多擴展,你在Kotlin當中使用的集合框架仍然跟你在Java當中同樣。

舉個例子,若是你想要在Kotlin中使用ArrayList,很簡單,Java的ArrayList你能夠隨意使用,這個感受跟使用Java沒有任何區別,請看:

//實際上就是建立一個ArrayList
    val list = arrayListOf(1,2,3,4)
    list.add(5)
    list.remove(3)
    for(item in list){
        println(item)
    }

固然,Kotlin標準庫也對這些作了擴展,咱們在享用Java世界的一切資源的同時,還能比原生Java代碼更滋潤,真是爽爆有木有:

val list = arrayListOf(1, 2, 3, 4, 5)
    
    //doubleList = [2,4,6,8,10]
    val doubleList = list.map { 
        it * 2
    }
    
    //oddList = [1,3,5]
    val oddList = list.filter{
        it % 2 == 1
    }
    
    //將list挨個打印出來
    list.forEach { 
        println(it)
    }

2.2 與Java交互

Kotlin的標準庫更多的是對Java庫的擴展,基於這個設計思路,你絲絕不須要擔憂Kotlin對Java代碼的引用,你甚至能夠在Kotlin當中使用Java反射,反正只要是Java有的,Kotlin都有,因而有人作出這樣的評價:

Kotlin就是Java的一個擴展

這樣說Kotlin顯然是不公平的,但就像微信剛面世那會兒要爲QQ接收離線消息同樣,總得抱幾天大腿嘛。

有關從Kotlin中調用Java的官方文檔在此[Calling Java code from Kotlin
](https://kotlinlang.org/docs/r...,其中最多見的就是Getter/Setter方法對應到Kotlin屬性的調用,舉個例子:

準備一個Java類

public class JavaClass {
    private int anInt = 0;

    public int getAnInt() {
        return anInt;
    }

    public void setAnInt(int anInt) {
        this.anInt = anInt;
    }
}

下面是Kotlin代碼

val javaClass = JavaClass()
    javaClass.anInt = 5
    print(javaClass.anInt)

因此咱們在Android開發時,就能夠這樣:

view.background = ...
textView.text = ...

反過來在Java中調用Kotlin也毫無壓力,官方文檔Calling Kotlin from Java對於常見的狀況做了比較詳細的闡述,這裏就再也不贅述。

三、簡潔,可靠,有趣

3.1 數據類

最初學Java的時候,學到一個概念叫JavaBean,當時就要被這個概念給折磨死了。明明很簡單的一個東西,結果搞得很複雜的樣子,並且因爲當時對於這些數據類的設計概念不是很清晰,於是也並不懂得去覆寫諸如equals和hashcode這樣重要的方法,一旦用到HashMap這樣的集合框架,老是出了問題都不知道找誰。

Kotlin提供了一種很是簡單的方式來建立這樣的數據類,例如:

data class Coordinate(val x: Double, val y: Double)

僅僅一行代碼,Kotlin就會建立出一個完整的數據類,並自動生成相應的equals、hashcode、toString方法。是否是早就受夠了getter和setter?反正我是受夠了。

3.2 空安全與屬性代理

第一次見到空類型安全的設計是在Swift當中,那時候還以爲這個東西有點兒意思哈,一旦要求變量不能爲空之後,因它而致使的空指針異常的可能性就直接沒有了。想一想每次QA提的bug吧,說少了都得有三分之一是空指針吧。

Kotlin的空安全設計,主要是在類型後面加?表示可空,不然就不能爲null。

val anInt: Int = null // 錯誤
val anotherInt: Int? = null // 正確

使用時,則:

val nullable: Int? = 0
val nonNullable: Int = 2
nullable.toFloat() // 編譯錯誤
nullable?.toFloat() // 若是null,什麼都不作,不然調用toFloat
nullable!!.toFloat() // 強制轉換爲非空對象,並調用toFloat;若是nullable爲null,拋空指針異常
nonNullable.toFloat() // 正確

而對於Java代碼,好比咱們在覆寫Activity的onCreate方法時,有個參數savedInstanceState:

override fun onCreate(savedInstanceState: Bundle!)

這表示編譯器再也不強制savedInstanceState是否可null,開發者在覆寫時能夠本身決定是否可null。固然,對於本例,onCreate的參數是可能爲null的,所以覆寫之後的方法應爲:

override fun onCreate(savedInstanceState: Bundle?)

一般來說,教科書式的講法,到這裏就該結束了。然而直到我真正用Kotlin開始寫代碼時,發現,有些需求實現起來真的有些奇怪。

仍是舉個例子,我須要在Activity當中建立一個View的引用,一般咱們在Java代碼中這麼寫:

public class DemoActivity extends Activity{
    
    private TextView aTextView;
    
    public void onCreate(Bundle savedInstanceState){
        super.OnCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        aTextView = (TextView) findViewById(R.id.a_textview);
        aTextView.setText("Hello");
        aTextView.setTextSize(20);
        ...
    }
}

在Kotlin當中呢?

class DemoActivity : Activity(){
    private var aTextView: TextView? = null
    
    override fun onCreate(savedInstanceState: Bundle?){
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
        //固然有更好用的方式,暫且先這麼寫
        aTextView = findViewById(R.id.a_textview) as TextView
        aTextView!!.text = "Hello"
        aTextView!!.textSize = 20
        ...
    }
}

每次用aTextView都要加倆!,否則編譯器不能肯定它到底是不是null,因而不讓你使用。。這尼瑪。。。究竟是爲了方便仍是爲了麻煩??

因此後來我又決定這麼寫:

class DemoActivity : Activity(){
    private var aTextView: TextView // 編譯錯誤,必須初始化!!!
    
    ...
}

這可如何是好??

其實Kotlin確定是有辦法解決這個問題噠!好比上面的場景,咱們這麼寫就能夠咯:

class DemoActivity : Activity(){
    private val aTextView: TextView by lazy{
        findViewById(R.id.a_textview) as TextView
    }
    
    override fun onCreate(savedInstanceState: Bundle?){
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
        aTextView.text = "Hello"
        aTextView.textSize = 20
        ...
    }
}

lazy是Kotlin的屬性代理的一個實例,它提供了延遲加載的機制。換句話說,這裏的lazy提供了初始化aTextView的方法,不過真正初始化這個動做發生的時機倒是在aTextView第一次被使用時了。lazy默認是線程安全的,你固然也能夠關掉這個配置,只須要加個參數便可:

private val aTextView: TextView by lazy(LazyThreadSafetyMode.NONE){
     findViewById(R.id.a_textview) as TextView
}

好,這時候確定有人要扔西紅柿過來了(再扔點兒雞蛋唄),你這lazy只能初始化val啊,萬一我要定義一個var成語,又須要延遲初始化,關鍵還不爲null,怎麼辦??

class Demo {
    lateinit var anJsonObject: JsonObject
    
    fun initDemo(){
        anJsonObject = JsonObject("{...}")
    }
    
}

lateinit的使用仍是有不少限制的,好比只能在不可null的對象上使用,比須爲var,不能爲primitives(Int、Float之類)等等,不過這樣逼迫你必定要初始化這個變量的作法,確實能減小咱們在開發中的遺漏,從而提升開發效率。

至於lazy技術,其實是Delegate Properties的一個應用,也就是屬性代理了。在Kotlin當中,聲明成員屬性,除了直接賦值,還能夠用Delegate的方式來聲明,這個Delegate須要根據成員的類型(val或者var)來提供相應的getValue和setValue方法,好比一個可讀寫的Delegate,須要提供下面的方法:

public interface ReadWriteProperty<in R, T> {
    /**
     * Returns the value of the property for the given object.
     * @param thisRef the object for which the value is requested.
     * @param property the metadata for the property.
     * @return the property value.
     */
    public operator fun getValue(thisRef: R, property: KProperty<*>): T

    /**
     * Sets the value of the property for the given object.
     * @param thisRef the object for which the value is requested.
     * @param property the metadata for the property.
     * @param value the value to set.
     */
    public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

好嘴皮不如來個栗子,下面咱們就看一個自定義Delegate,用來訪問SharedPreference:

class Preference<T>(val context: Context, val name: String, val default: T) : ReadWriteProperty<Any?, T> {

    val prefs by lazy { context.getSharedPreferences("default", Context.MODE_PRIVATE) }

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return findPreference(name, default)
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        putPreference(name, value)
    }

    private fun <U> findPreference(name: String, default: U): U = with(prefs) {
        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 can be saved into Preferences")
        }

        res as U
    }

    private fun <U> putPreference(name: String, value: U) = with(prefs.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 can be saved into Preferences")
        }.apply()
    }
}

須要說明的是,這段代碼是我從《Kotlin for Android Developer》的示例中摘出來的。有了這個Delegate類,咱們就能夠徹底不須要關心SharedPreference了,下面給出使用的示例代碼:

class WhateverActivity : Activity(){
    var aInt: Int by Preference(this, "aInt", 0)
    
    fun whatever(){
        println(aInt)//會從SharedPreference取這個數據
        aInt = 9 //會將這個數據寫入SharedPreference
    }
}

因而咱們不再須要重複寫那些getSharedPreference,也不用edit、commit,再見那些edit以後忘了commit的日子。有沒有以爲很是贊!

3.3 擴展類

擴展類,就是在現有類的基礎上,添加一些屬性或者方法,固然擴展的這些成員須要導入當前擴展成員所在的包才能夠訪問到。下面給出一個例子:

data class Coordinate(val x: Double, val y: Double)

val Coordinate.theta: Double
    get() {
        return Math.atan(y/x)
    }

fun Coordinate.R():Double{
    return Math.hypot(x, y)
}

咱們已經介紹過data class,Coordinate有兩個成員分別是x和y,咱們知道一般表示一個二維平面,有這倆夠了;然而咱們在圖形學當中常常會須要求得其極座標,因此咱們擴展了Coordinate,增長了一個屬性theta表示角度(反正切的值域爲-π/2 ~ π/2,因此這個式子不適用於二三象限,不過這不是重點了),增長了一個R方法來得到點的半徑,因而咱們在main方法中就能夠這麼用:

fun main(args: Array<String>) {
    val coord = Coordinate(3.0,4.0)
    println(coord.theta)
    println(coord.R())
}

那麼這個擴展有什麼限制呢?

  • 在擴展成員當中,只能訪問被擴展類在當前做用域內可見的成員,本例中的x和y都是public的(Kotlin默認public,這個咱們後面會提到),因此能夠在擴展方法和屬性中直接訪問。

  • 擴展成員與被擴展類的內部成員名稱相同時,擴展成員將沒法被訪問到

好的,基本知識就是這些了,下面咱們再給出一個實際的例子。

一般咱們在Java中會自定義一些LogUtils類來打日誌,或者直接用android.util.log來輸出日誌,不知道你們是什麼感覺,我反正每次由於要輸入Log.d還要輸入個tag簡直煩的要死,並且有時候剛好這個類尚未tag這個成員,實踐中咱們一般會把當前類名做爲TAG,但每一個類都要作這麼個工做,是在是沒有什麼趣味可言(以前我是用LiveTemplates幫個人,即使如此也沒有那種流暢的感受)。

有了Kotlin的這個擴展功能,日子就會好過得多了,下面我建立的一個打日誌的方法:

package com.benny.utils

import android.util.Log

inline fun <reified T> T.debug(log: Any){
    Log.d(T::class.simpleName, log.toString())
}

有了這個方法,你能夠在任何類的方法體中直接寫:

debug(whatever)

而後就會輸出以這個類名爲TAG的日誌。

嗯,這裏須要簡單介紹Kotlin在泛型中的一個比較重要的加強,這個在Java中不管如何也是作不到的:inline、reified。咱們再來回頭看一下debug這個方法,咱們發現它能夠經過泛型參數T來獲取到T的具體類型,而且拿到它的類名——固然,若是你願意,你甚至能夠調用它的構造方法來構造一個對象出來——爲何Kotlin能夠作到呢?由於這段代碼是inline的,最終編譯時是要編譯到調用它的代碼塊中,這時候T的類型其實是肯定的,於是Kotlin經過reified這個關鍵字告訴編譯器,T這個參數可不僅是個擺設,我要把它當實際類型來用呢。

爲了讓你們印象深入,我下面給出相似功能的Java的代碼實現:

public static void debug(Class<?> clazz, Object log){
        Log.d(clazz.getSimpleName(), log.toString());
    }

而你若是說但願在Java中也但願像下面這樣拿到這個泛型參數的類型,是不能夠的:

public static <T> void debug(Object log){
    Log.d(T.getSimpleName(), log.toString());//錯誤,T是泛型參數,沒法直接使用
}

就算咱們在調用處會寫道 debug < Date >("blabla"),但這個Date在編譯以後仍是會被擦除。

3.4 函數式支持(Lambdas)

Java 8已經開始能夠支持Lambda表達式了,這種東西對於Java這樣一個『根紅苗正』的面向對象編程語言來講還真是顯得不天然,不過對於Kotlin來講,就沒那麼多顧忌了。

一般咱們須要執行一段異步的代碼,咱們會構造一個Runnable對象,而後交給executor,好比這段java代碼:

executor.submit(new Runnable(){
    @Override
    public void run(){
        //todo
    }
});

用Kotlin怎麼寫呢?

executor.submit({
    //todo
})

一會兒省了不少代碼。

那麼實際當中咱們可能更常見到下面的例子,這是一段很常見的Java代碼,在Android的UI初始化會見到:

textView.setOnClickListener(new OnClickListener(){
    @Override
    public void onClick(View view){
        //todo
    }
});

handle.post(new Runnable(){
    @Override
    public void run(){
        //todo
    }
});

那麼咱們用Kotlin怎麼寫呢?

textView.setOnClickListener{ /*todo*/ }
handler.post{ /*todo*/ }

在Anko這個Android庫的幫助下,咱們甚至能夠繼續簡化OnClickListener的設置方式:

textView.onClick{ /*todo*/ }

固然,好玩的不止這些,若是結合上一節咱們提到的擴展方法,咱們就很容易看到Kotlin的標準庫提供的相似with和apply這樣的方法是怎麼工做的了:

public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

咱們一般會在某個方法體內建立一個對象並返回它,可咱們除了調用它的構造方法以外還須要作一些其餘的操做,因而就要建立一個局部變量。。。有了apply這個擴展方法,咱們就能夠這麼寫:

fun getStringBuilder: StringBuilder{
    return StringBuilder().apply{
        append("whatever")
    }
}

這樣返回的StringBuilder對象其實是包含"whatever"這個字符串的。

至於說Kotlin對於RxJava的友好性,使得我忽然有點兒相信緣分這種東西了:

Observable.create<ArrayList<Dummy>> {
            it.onStart()
            try {
                it.onNext(dummyObjs)
            } catch(e: Exception) {
                it.onError(e)
            } finally {
                it.onCompleted()
            }
        }.subscribe(object : Subscriber<ArrayList<Dummy>>() {
            override fun onCompleted() {

            }

            override fun onNext(t: ArrayList<Dummy>?) {

            }

            override fun onError(e: Throwable?) {

            }

        })

3.5 Pattern Matching

記得以前在瀏覽Scala的特性時,看到:

object HelloScala{
  // do something
}

以爲很新鮮,這時候有個朋友不屑的說了句,Scala的模式匹配才真正犀利——Kotlin當中也有這樣的特性,咱們下面就來看個例子:

val x = 7
when (x) {
  in 1..10 -> print("x is in the range")
  in validNumbers -> print("x is valid")
  !in 10..20 -> print("x is outside the range")
  else -> print("none of the above")
}

咋一看感受when表達式就是一個加強版的switch——Java 7之前的switch實際上支持的類型很是有限,Java 7當中增長的對String的支持也是基於int類型的——咱們能夠看到when再也不像switch那樣只匹配一個數值,它的子式能夠是各類返回Boolean的表達式。

when表達式還有一種寫法更革命:

when {
  x.isOdd() -> print("x is odd")
  x.isEven() -> print("x is even")
  else -> print("x is funny")
}

只要是返回Boolean的表達式就能夠做爲when的子式,這樣when表達式的靈活性可見一斑。固然,與Scala相比,Kotlin仍是要保守一些的,下面給出一個Scala相似的例子,你們感覺一下,這實際上也能夠體現出Kotlin在增長Java的同時也儘可能保持簡單的設計哲學(你們都知道,畢竟Scala須要智商o(╯□╰)o)。

object Hello {
  def main(args: Array[String]) {
    easyMatch((1, 3))
    easyMatch(Array(1,3,4))
    easyMatch(Bean(3.0, 4.0))
  }

  def easyMatch(value : Any) = value match {
      case int :Int => {
        println("This is an Int.")
      }
      case (a, b) =>{
        println(s"a tuple with : $a , $b")
      }
      case Bean(x, y)  => {
        println(s"$x, $y")
    }
      case whatever => println(whatever)
    }
}

case class Bean(val x: Double, val y: Double)

運行結果以下:

a tuple with : 1 , 3
[I@2d554825
3.0, 4.0

3.6 若是你是一個SDK開發者

我曾經作過一段時間的SDK開發,SDK的內部有不少類實際上是須要互相有訪問權限的,但一旦類及其成員是public的,那麼調用方也就能夠看到它們了;而protected或者default這樣的可見性對於子包倒是不可見的。

用了這麼久Java,這簡直是我惟一強烈感到不滿的地方了,甚至於我忽然明白了C++的friend是多麼的有用。

Kotlin雖然沒有提供對於子包可見的修飾符,不過它提供了internal:即模塊內可見。換句話說,internal在模塊內至關於public,而對於模塊外就是private了——因而乎咱們若是開發SDK,那麼能夠減小api層的編寫,那些用戶不可見的部分直接用internal豈不更好。固然有人會說咱們應當有proguard作混淆,我想說的是,proguard天然是要用到的,不過那是SDK這個產品加工的下一個環節了,咱們爲何不能在代碼級別把這個事情作好呢?

關於Kotlin的默承認見性到底是哪一個還有人作出過討論,有興趣的能夠參考這裏:Kotlin’s default visibility should be internal

3.7 DSL

其實咱們對DSL確定不會陌生,gradle的腳本就是基於groovy的DSL,而Kotlin的函數特性顯然也是能夠支持DSL的。好比,咱們最終要生成下面的xml數據:

<project version="4">
  <component name="Encoding">
    <file url="PROJECT" charset="UTF-8" />
  </component>
</project>

咱們能夠構建下面的類:

class Project {
    var version: String? = null
        get() =
        if (field == null) ""
        else {
            " version=\"${field}\""
        }

    lateinit private var component: Component

    fun component(op: Component.() -> Unit) {
        component = Component().apply {
            op()
        }
    }

    override fun toString(): String {
        return "<project${version}>${component}<project>"
    }


}

fun project(op: Project.() -> Unit): Project {
    return Project().apply {
        op()
    }
}

class Component {
    var name: String? = null
        get() =
        if (field == null) ""
        else {
            " name=\"${field}\""
        }


    lateinit private var file: File

    fun file(op: File.() -> Unit) {
        file = File().apply {
            op()
        }
    }

    override fun toString(): String {
        return "<component${name}>${file}<component>"
    }
}

class File {
    var url: String? = null
        get() =
        if (field == null) ""
        else {
            " url=\"${field}\""
        }

    var charset: String? = null
        get() =
        if (field == null) ""
        else {
            " charset=\"${field}\""
        }

    override fun toString(): String {
        return "<file${url}${charset}/>"
    }
}

fun main(args: Array<String>) {
    val xml = project {
        version = "4"
        component {
            name = "Encoding"
            file {
                url = "PROJECT"
                charset = "UTF-8"
            }
        }
    }

    println(xml)
}

咱們看到在main方法當中,咱們用kotlin定義的dsl寫出了一個Project對象,它有這與xml描述的一致的結構和含義,若是你願意,能夠構造相應的方法來輸出這樣的xml,運行以後的結果:

<project version="4"><component name="Encoding"><file url="PROJECT" charset="UTF-8"/><component><project>

固然,這個例子作的足夠的簡陋,若是你有興趣也能夠抽象出"Element",併爲之添加"Attributes",實際上這也不是很難。

3.7 Kotlin與Android的另外一些有趣的東西

寫了不少代碼,卻發現它們幹不了多少事情,終究仍是會苦惱的。好比我一直比較痛苦的一件事兒就是:

Button button = (Button) findViewById(R.id.btn);

若是我須要不少個按鈕和圖片,那麼咱們要寫一大片這樣的findViewById。。媽呀。。。這活我幹不了啦。。

不過用Kotlin的Android擴展插件,咱們就能夠這樣:

先上佈局文件:

main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@drawable/open_bj"
                android:orientation="vertical">

        <TextView
            android:id="@+id/textView"        
            android:text="Hello"
            android:textSize="50sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <Button
            android:id="@+id/start"
            android:clickable="false"
            android:layout_gravity="center_horizontal"
            android:background="@drawable/start_selector"
            android:textSize="50sp"
            android:layout_marginTop="20dp"
            android:layout_marginBottom="200dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

</RelativeLayout>

在Activity中:

package com.benny

……
import kotlinx.android.synthetic.main.load_activity.*
import org.jetbrains.anko.onClick
import org.jetbrains.anko.startActivity
import org.jetbrains.anko.toast
……

class LoadActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)

        start.onClick {
            toast("開始")
            startActivity<AnotherActivity>()
        }
        textView.text = "你好"
              
    }
}

注意到:

import kotlinx.android.synthetic.main.load_activity.*

導入這一句以後,咱們就能夠直接在代碼中使用start、textView,他們分別對應於main.xml中的id爲start的按鈕和id爲textView的TextView。

因而你就發現你不再用findViewById了,多麼愉快的一件事!!!固然,你還會發現Toast的調用也變得簡單了,那其實就是一個擴展方法toast();而startActivity呢,其實就是一個inline加reified的應用——這咱們前面都提到過了。

還有一個噁心的東西就是UI線程和非UI線程的切換問題。也許你會用handler不斷的post,不過說真的,用Handler的時候難道你不顫抖麼,那但是一個很容易內存泄露的魔鬼呀~哈哈,好吧其實我不是說這個,主要是用handler寫出來的代碼 實在 太 醜 了 !!

原來在java當中,咱們這麼寫:

handler.post(new Runnable(){
    @Override
    public void run(){
        //todo    
    }
});

MainActivity.this.runOnUiThread(
    public void run(){
        //todo
    }
});

而在Kotlin當中呢,咱們只須要這麼寫:

async() {
    //do something asynchronously
    uiThread {
        //do something on UI thread
    }
}

本身感覺一下吧。

下面咱們再來提一個有意思的東西,咱們從作Android開發一開始就要編寫xml,印象中這個對於我來講真的是一件痛苦的事情,由於它的工做機制並不如代碼那樣直接(以致於我如今不少時候竟然喜歡用Java代碼直接寫佈局)——固然,最主要的問題並非這個,而是解析xml須要耗費CPU。Kotlin有辦法能夠解決這個問題,那就是DSL了。下面給出一個例子:

linearLayout {
    button("Login") {
        textSize = 26f
    }.lparams(width = wrapContent) {
        horizontalMargin = dip(5)
        topMargin = dip(10)
    }
}

一個LinearLayout包含了一個Button,這段代碼你能夠直接寫到你的代碼中靈活複用,就像這樣:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(linearLayout {
            button("This is a button") {
                onClick {
                    toast("clicked!")
                }
            }.lparams {
                width = matchParent
                verticalMargin = dip(5)
            }
        })
    }

這樣作的好處真是很多:

  • 比起xml的繁瑣來,這真是要清爽不少

  • 佈局自己也是代碼,能夠靈活複用

  • 不再用findViewById了,難道你不以爲在這個上面浪費的生命已經足夠多嗎

  • 事件監聽很方便的嵌到佈局當中

  • DSL方式的佈局沒有運行時的解析的負擔,你的邏輯代碼怎麼運行它就怎麼運行

  • Anko還增長了更多好玩的特性,有興趣的能夠參考:Anko@Github

3.8 方法數之痛

我曾經嘗試用Scala寫了個Android的HelloWorld,一切都配置好之後,僅僅引入了Scala常見的幾個庫,加上support-v4以及appcompat這樣常見的庫,結果仍是報錯了。是的,65K。。。並且用Scala開發Android的話,基於gradle的構建會讓整個app的build過程異常漫長,有時候你會以爲本身悟出了廣義相對論的奧義,哦不,你必定是暈了,時間並無變慢。

相比之下,Kotlin的標準庫只有7000個方法,比support-v4還要小,這正反映了Kotlin的設計理念:100% interoperable with Java。其實咱們以前就提到,Java有的Kotlin就直接拿來用,而Scala的標準庫要有5W多個方法,想一想就仍是想一想算了。

四、小結

目前Kotlin 1.0已經release,儘管像0xffffffff識別成Long類型這樣的bug仍然沒有解詳情

val int: Int = 0xffffffff // error
val anotherInt: Int = 0xffffffff.toInt() // correct

不過,Kotlin的教學資源和社區建設也已經相對成熟,按照官方的說法,Kotlin能夠做爲生產工具投入開發,詳情能夠參考:Kotlin 1.0 Released: Pragmatic Language for JVM and Android

勇於吃螃蟹,多少有些浪漫主義色彩,咱們這些程序員多少能夠有些浪漫主義特質,不過在生成環境中,穩定高於一切仍然是不二法則。追求新技術,一方面會給團隊帶來開發和維護上的學習成本,另外一方面也要承擔將來某些狀況下由於對新技術不熟悉而產生未知問題的風險——老闆們最怕風險了~~

基於這一點,毫無疑問,Kotlin能夠做爲小工具、測試用例等的開發工具,這是考慮到這些代碼一般體量較小,維護人數較少較集中,對項目總體的影響也較小;而對於核心代碼,則視狀況而定吧。

就我我的而言,長期下去,Kotlin很大可能會成爲個人主要語言,短時間內則仍然採用溫和的改革方式慢慢將Kotlin滲透進來。

一句話,Kotlin是用來提高效率的,若是在你的場景中它作不到,甚至成了拖累,請放開它


若是你以爲內容意猶未盡,若是你想了解更多相關信息,請掃描如下二維碼,關注咱們的公衆帳號,能夠獲取更多技術類乾貨,還有精彩活動與你分享~

圖片描述

騰訊Bugly簡介

騰訊Bugly,專業的App Crash監測平臺。監測信息實時上報,讓開發同窗能夠第一時間瞭解到App的質量狀況,爲移動開發節省大量人力與精力,提高移動產品質量。目前騰訊內部全部的移動端產品均在使用,現已免費對外開放。

相關文章
相關標籤/搜索