Kotlin 開發Android 實戰(一)- 項目配置和語言轉換
在過去的一年中,在Android開發圈有一個越來越火的話題,就是JetBrains開發的新JVM語言Kotlin。這個團隊還開發了IntelliJ Idea,也就是Android Studio的基礎。Kotlin旨在通過全新的語言特色來替代老舊而不cool的Java,又由于Kotlin可以100%兼容Java,所以你在項目中可以想用多少用多少。而又因為Kotlin的標準庫很小,很適合在資源有限的移動設備上開發使用。
Kotlin能干所有Java能干的(不止),且語法更準確,代碼更好看,而且在IntelliJ和Android Studio中有很好的支持。我從2009年開始從事深層次Android開發工作,我尤其關心Kotlin可以給Android開發者帶來什么。所以我就不說虛的了,直接開始寫Kotlin代碼,讓大家感受其語言特點,希望最終能帶給大家有用的信息。
在第一部分中,我會通過最簡單的方式在一個新Android項目中集成Kotlin。
在Android項目中配置Kotlin
官方文檔講了如何一步步安裝Kotlin插件,并使用插件在Android項目中自動修改Gradle文件來添加對Kotlin的支持。我不建議大家這么做,因為這樣自動完成的結果可能并不完美,即使修改后的Gradle文件工作正常,也會打亂Android項目中Gradle文件的一般形式。
說實話,我一直不喜歡有些Android Studio插件直接修改Android build文件,因為經常搞得很亂,我又得一點一點清理直到符合我的風格。Gradle構建文件也是源代碼,而這些插件并不擅長修改已經存在的代碼。所以如果你也和我一樣有些挑剔,那就多花一分鐘和我一起手動配置。
下面我們要分四步完成Kotlin的配置。
-
新建一個Android項目。
-
修改Gradle代碼來添加Kotlin Gradle插件與標準庫。
-
在IntelliJ或Android Studio中添加Kotlin插件。
-
將Java類文件轉換成Kotlin。
首先,直接以默認方式新建一個Android項目,此時應該自帶一個Activity。之后,要在兩個build.gradle文件中添加五行重要代碼,我都在其后添加了注釋。下面讓我們先修改最高層的build.gradle腳本,添加兩行代碼。
這樣就會在項目構建時添加Kotlin Gradle插件。請注意上面在ext.kotlin_version中標注的kotlin版本字符串,我們一會還要在app模塊的compile dependencies中用到它,而且兩個地方版本必須相符。你最好使用 官方文檔 中最新版本。
然后,在app模塊自己的build.gradle文件中緊隨Android plugin添加kotlin-android plugin。這樣整個項目就整合了Kotlin,在build項目時會編譯Kotlin文件,這樣最后所有的類文件都會打包在一個app中。
慣例上,Kotlin文件存放在src/main/kotlin路徑中,但也可以把他們和Java文件一起放在/src/main/java路徑中。這里我們還是按照慣例,并在Gradle中標注一個新的Kotlin源路徑。
不要忘了新建這個路徑,一會就要用到了。最后需要添加一個Kotlin依賴,直接使用build.gradle中的kotlin版本變量。
不過這個包有多大呢?好問題!每當我們添加新的依賴時,都應該搞清楚這個包有多大。不過對于這個問題,我會在后面的文章中回答。
這就是Kotlin Gradle插件,走完這些步驟后,就可以在項目中運行Kotlin代碼了。不過你還需要添加IDE對Kotlin的支持,所以如果你還沒有安裝IntelliJ或Android Studio的Kotlin插件,那就趕快安裝。安裝Kotlin插件就像安裝其他任何插件一樣,可以在Preferences->Plugins->Insall JetBrains plugin下找到。安裝后要重啟IDE,做完這一步后,準備工作就完成了。我發現IDE對Kotlin的支持甚至和Java語言一樣好。這也可以理解,畢竟IDE和Kotlin都是JetBrains開發的嘛。
快速將Java轉成Kotlin
IDE插件有一個很有趣的功能就是將Java文件直接轉成Kotlin。這個插件可以很智能地將Java語言風格轉換成Kotin風格并保持運行兼容。如果你創建了一個Android項目,那就找到自動生成的MainActivity,在左邊的項目結構中選中,并觸發IDE的action "Convert Java File to Kotlin File"。你可以按下快捷鍵Command+Shift+A(OSX)來選擇action。這個插件甚至有專門針對這個action的快捷鍵Option+Shift+Command+K(OSX)。其實官方并不建議直接轉換Java文件,但直到現在我還沒遇到過什么問題。
如果你按我說的操作轉換了Java文件,就會在原本.java文件的地方找到一個.kt文件。
你可以看到現在MainActivity在右下角有一個K標志(這里隱藏了.kt擴展名)。由于我們剛剛專門為Kotlin配置了一個路徑,我們把Kotlin文件拖進kotlin文件夾中。注意要保留kotlin文件中類的包名,不然項目無法運行。
如果你想在項目中只用Kotlin,那可以干脆刪除掉Java文件夾,把所有的Kotlin文件放在kotlin路徑中。之后,項目結構就像下面這樣。
從新的Activity中你可以大概知道Kotlin長什么樣。我下面說幾點Kotlin與Java很不一樣的地方:
-
在Kotlin中你見不到"new"關鍵字。
-
把類名當做方法并傳入參數就可以直接構造對象。
-
數據類型關鍵字被val(final)與var(variable)取代,Kotlin可以自己判斷數據是什么類型。
中場休息
剛才我們一步一步在Android項目中添加了Kotlin支持,現在我們要開始通過代碼直觀感受Kotlin的語言特色以及它能如何簡化Android開發。
當我第一次接觸Kotlin并了解其語言特色功能時,有一點讓我感觸很深,那就是type-safe builders。它讓你以陳述式語言風格來創建對象,其語法很類似Gradle,但Gradle和Groovy代碼是動態編寫的,而kotlin是靜態編寫的,所以編譯器可以在屬性的值不合法時告訴你。
type-safe builders的一個典型用法就是構建嵌套式數據結構,比如XML。在Android中有很多XML文件,如layouts和views。如果Kotlin可以以程序的方式動態編寫XML,它可能很善于處理層級問題。所以我決定嘗試使用Kotlin創建一個動態構建View層級的工具。如果是在Java中做這件事,代碼量之大可以想象。
注意:在后面的代碼中我會經常使用lambda,所以在繼續閱讀之前,確保你了解lambda的基本形式。簡而言之,lambda是要作為參數被傳入某方法或賦值給某變量的匿名方法的簡化表現形式。
type-safe builders可行還要歸功于Kotlin的一個特色功能:lambda with receiver。下面我們要看一個能真正有用的案例。kotlin可以在類的外面定義方法,我在這里就是這么干的。還要注意變量的名字是在類型之前的,這一點與Java語言正相反。
簡便起見,我將上面的方法命名為v,在之后的系列文章中我還會使用到這個方法。這個方法是這么調用的:
這段代碼和填充下面這段XML是等效的。
OK,如果這是你第一次看Kotlin代碼,相信有不少需要翻譯的。下面解釋一下剛才代碼中涉及的幾個語法。
<reified TV : View>
reify的意思是具體化。而作為Kotlin的一個方法泛型關鍵字,它代表你可以在方法體內訪問泛型指定的JVM類對象。這段代碼的意思就是v方法要使用命名為TV(意為Type of View,即View種類)的reified泛型,它指定類必須為View或其子類。必須以內聯方式聲明這個方法才有效。調用者要給TV指定一個具體的類型。
init: TV.() -> Unit
v方法有兩個參數,一個是Context,一個是lambda風格的init。init在這里很特殊,因為它是一種lambda with receiver類型的方法引用。lambda with receiver是一個要求特定類型的對象的代碼塊,這里要求的對象在lambda代碼中通過this關鍵字引用。在這里receiver對象就是reified泛型TV。
在我們這個例子中,v要創建一個類型為TV的對象,需要調用者告訴它如何創建。這個新創建的TV類型對象會成為給定的lambda的receiver,labmda通過view.init()在v中被調用,以便對view進行操作。“-> Unit”意思是lambda返回Unit類型,就如Java中的Void一樣,即什么也不返回。
總結一下這里的lambda with receiver:
-
v聲明了一個名叫init的參數,它是TV類型的lambda with receiver。
-
v創建并初始化一個TV對象,并在其上調用lambda來初始化它。
-
lambda在自己代碼塊中通過this關鍵字引用TV對象。
TV::class.java
通過TV::class.java表達你可以引用reified泛型TV的Class對象。這種表達在Kotlin中專門針對reified泛型,與Java相比,可以大量減少代碼。
到這里,估計你會有一些問題:
為什么v有兩個參數,而調用的時候似乎只給了一個?
這個只是因為Java程序員對Kotlin語法并不熟悉。在Java中,一個方法的所有的參數都要寫在圓括號內,所以當有匿名回調方法時會變得很長。但在Kotlin中,當lambda時最后一個參數時,有一個特殊語法,即lambda出現在緊隨圓括號的大括號中。你可以把所有的代碼都放在圓括號內,但大多數情況下這么寫代碼會顯得更加簡潔,而且可以使一組圓括號保持在一行里,更易于追蹤。另外,這種語法在傳入只有一個方法的匿名內部類比如Runnable時也可以派上用場。
layoutParams和text是變量嗎?
lambda with receiver的一個語法特色就是當操作this的方法或屬性時this關鍵字可以省略。但在上面的調用例子中layoutParams和text到底是什么?其實這些屬性是Kotlin提供的receiver類型(TV)的屬性。因為TextView有setLayoutParams()與setText()等方法,Kotlin會自動識別這些JavaBean風格的方法并為他們創建屬性,好像它們是類的成員變量一樣。所以這里text = "Hello"等價于this.setText("Hello")。下面是在安裝了Kotlin插件的Android Studio中的截圖,里面展示了自動完成的內容。
如你所見,Kotlin插件之處text屬性是從TextView(receiver對象)中JavaBean風格的getter/setter方法中派生出的。
構造器到底是怎么運作的?就不能new TV(context)嗎?
因為編譯器也不知道在v方法內部TV到底是什么類型,我們不能new它的對象。不過我們可以利用reified Class對象(TV::class.java)來獲取僅有一個context參數的構造器。View都有這樣一個構造器,不然我們也寫不出這個程序。我們通過構造器對象來獲得TV類型的實例,就如Java中的new關鍵字一樣。想一想我們可以通過一個方法適配所有View,而不再針對每個View單獨寫方法,這一點小麻煩還是很值的。在后續系列文章中我們也會優化這個方法。
這幾行代碼真的很大的簡化了我們的工作。如果你剛剛接觸Kotlin,建議你從頭再消化一遍,畢竟許多概念和Java差別很大。我自己也花了好多時間來理解這些概念。
這些都只是開始,這個方法有很多地方可以完善和升級,使之更加易用。比如我想用用一行代碼構造一整個View層級。所以不要走開,我會在后續文章探索Kotlin還能做什么。
來自: http://blog.chengdazhi.com/index.php/157