摘要:的空安全設(shè)計,主要是在類型后面加表示可空,否則就不能為。換句話說,這里的提供了初始化的方法,不過真正初始化這個動作發(fā)生的時機(jī)卻是在第一次被使用時了。至于技術(shù),實際上是的一個應(yīng)用,也就是屬性代理了。
1、Hello, Kotlin
Bugly 技術(shù)干貨系列內(nèi)容主要涉及移動開發(fā)方向,是由 Bugly 邀請騰訊內(nèi)部各位技術(shù)大咖,通過日常工作經(jīng)驗的總結(jié)以及感悟撰寫而成,內(nèi)容均屬原創(chuàng),轉(zhuǎn)載請標(biāo)明出處。
1.1 Kotlin的身世寫了許久Java,有沒有發(fā)現(xiàn)其實你寫了太多冗余的代碼?
后來你體驗了一下Python,有沒有覺得不寫分號的感覺真是超級爽?
你雖然勤勤懇懇,可到頭來卻被NullPointerException折磨的死去活來,難道就沒有受夠這種日子么?
直到有一天你發(fā)現(xiàn)自己已經(jīng)寫了好幾十萬行代碼,發(fā)現(xiàn)居然全是getter和setter!
哈哈,實際上你完全可以不用這么痛苦,用Kotlin替代Java開發(fā)你的程序,無論是Android還是Server,你都能像之前寫Java一樣思考,同時又能享受到新一代編程語言的特性,說到這里你是不是開始心動了呢?下面我就通過這篇文章來給大家介紹一下Kotlin究竟是何方神圣。
話說,Kotlin是JetBrain公司搞出來的,運行在JVM上的一門靜態(tài)類型語言,它是用波羅的海的一個小島的名字命名的。從外觀上,乍一看還以為是Scala,我曾經(jīng)琢磨著把Scala作為我的下一門語言,不過想想用Scala來干嘛呢,我又不做大數(shù)據(jù),而它又太復(fù)雜了o(╯□╰)o
用Kotlin創(chuàng)建一個數(shù)據(jù)類
data class Mondai(var index: Int = 0, var title: String = "", val ans: ArrayList= ArrayList(), var correct: Int = 0, var comment: String = "", var color: String = "", private var lives: Int = 50)
最初是在intelliJ的源碼中看到Kotlin的,那時候Kotlin的版本還不太穩(wěn)定,所以源碼總是編譯不過,真是要抓狂啊,還罵『什么破玩意兒!為什么又出來新語言了?Groovy還沒怎么學(xué)會,又來個Kotlin!』話說,Kotlin,難道是『靠它靈』的意思??
其實經(jīng)過一年多的發(fā)展,Kotlin 1.0已經(jīng)release,feature基本完善,api也趨于穩(wěn)定,這時候嘗試也不會有那種被坑的感覺了。過年期間也算清閑,于是用Kotlin做了個app,簡單來說,就是幾個感覺:
思路與寫Java時一樣,不過更簡潔清爽
少了冗余代碼的煩惱,更容易專注于功能的開發(fā),整個過程輕松愉快
擴(kuò)展功能使得代碼寫起來更有趣
空安全和不可變類型使得開發(fā)中對變量的定義和初始化傾注了更多關(guān)注
啊啊,我再也不用寫那個findViewById了,真的爽爆有木有!
1.2 第一個Kotlin程序Kotlin開發(fā)當(dāng)然使用JetBrain系列的IDE,實際上intelliJ idea 15發(fā)布時就已經(jīng)內(nèi)置了Kotlin插件,更早的版本則需要到插件倉庫中下載安裝Kotlin插件——在安裝時你還會看到有個Kotlin Extensions for Android,不要管他,已經(jīng)過時了。安裝好以后,我們就可以使用Kotlin進(jìn)行開發(fā)了。
接下來我們用Android Studio創(chuàng)建一個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的擴(kuò)展,同時也添加了kotlin的gradle插件。
接下來就可以編寫kotlin代碼了——等等,Android Studio會幫我們生成一個MainActivity,你可以直接在菜單
Code -> Convert Java file to Kotlin file
將這個java代碼轉(zhuǎn)換為kotlin代碼。截止到現(xiàn)在,你什么都不用做,程序就已經(jīng)可以跑起來了。
2、完美為Java開發(fā)者打造 2.1 通用的集合框架我們都知道Jvm上面的語言,像什么Java、Groovy、Jython啥的,都是要編成虛擬機(jī)的字節(jié)碼的,一旦編成字節(jié)碼,在一定程度上大家就都平等了。
英雄不問出身啊
有人做過一個非常形象的比喻:Java虛擬機(jī)語言就是打群架。Kotlin正是充分利用了這一點,它自己的標(biāo)準(zhǔn)庫只是基于Java的語言框架做了許多擴(kuò)展,你在Kotlin當(dāng)中使用的集合框架仍然跟你在Java當(dāng)中一樣。
舉個例子,如果你想要在Kotlin中使用ArrayList,很簡單,Java的ArrayList你可以隨意使用,這個感覺跟使用Java沒有任何區(qū)別,請看:
//實際上就是創(chuàng)建一個ArrayList val list = arrayListOf(1,2,3,4) list.add(5) list.remove(3) for(item in list){ println(item) }
當(dāng)然,Kotlin標(biāo)準(zhǔn)庫也對這些做了擴(kuò)展,我們在享用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的標(biāo)準(zhǔn)庫更多的是對Java庫的擴(kuò)展,基于這個設(shè)計思路,你絲毫不需要擔(dān)心Kotlin對Java代碼的引用,你甚至可以在Kotlin當(dāng)中使用Java反射,反正只要是Java有的,Kotlin都有,于是有人做出這樣的評價:
Kotlin就是Java的一個擴(kuò)展
這樣說Kotlin顯然是不公平的,但就像微信剛面世那會兒要為QQ接收離線消息一樣,總得抱幾天大腿嘛。
有關(guān)從Kotlin中調(diào)用Java的官方文檔在此[Calling Java code from Kotlin
](https://kotlinlang.org/docs/r...,其中最常見的就是Getter/Setter方法對應(yīng)到Kotlin屬性的調(diào)用,舉個例子:
準(zhǔn)備一個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開發(fā)時,就可以這樣:
view.background = ... textView.text = ...
反過來在Java中調(diào)用Kotlin也毫無壓力,官方文檔Calling Kotlin from Java對于常見的情況作了比較詳細(xì)的闡述,這里就不再贅述。
3、簡潔,可靠,有趣 3.1 數(shù)據(jù)類最初學(xué)Java的時候,學(xué)到一個概念叫JavaBean,當(dāng)時就要被這個概念給折磨死了。明明很簡單的一個東西,結(jié)果搞得很復(fù)雜的樣子,而且由于當(dāng)時對于這些數(shù)據(jù)類的設(shè)計概念不是很清晰,因而也并不懂得去覆寫諸如equals和hashcode這樣重要的方法,一旦用到HashMap這樣的集合框架,總是出了問題都不知道找誰。
Kotlin提供了一種非常簡單的方式來創(chuàng)建這樣的數(shù)據(jù)類,例如:
data class Coordinate(val x: Double, val y: Double)
僅僅一行代碼,Kotlin就會創(chuàng)建出一個完整的數(shù)據(jù)類,并自動生成相應(yīng)的equals、hashcode、toString方法。是不是早就受夠了getter和setter?反正我是受夠了。
3.2 空安全與屬性代理第一次見到空類型安全的設(shè)計是在Swift當(dāng)中,那時候還覺得這個東西有點兒意思哈,一旦要求變量不能為空以后,因它而導(dǎo)致的空指針異常的可能性就直接沒有了。想想每次QA提的bug吧,說少了都得有三分之一是空指針吧。
Kotlin的空安全設(shè)計,主要是在類型后面加?表示可空,否則就不能為null。
val anInt: Int = null // 錯誤 val anotherInt: Int? = null // 正確
使用時,則:
val nullable: Int? = 0 val nonNullable: Int = 2 nullable.toFloat() // 編譯錯誤 nullable?.toFloat() // 如果null,什么都不做,否則調(diào)用toFloat nullable!!.toFloat() // 強(qiáng)制轉(zhuǎn)換為非空對象,并調(diào)用toFloat;如果nullable為null,拋空指針異常 nonNullable.toFloat() // 正確
而對于Java代碼,比如我們在覆寫Activity的onCreate方法時,有個參數(shù)savedInstanceState:
override fun onCreate(savedInstanceState: Bundle!)
這表示編譯器不再強(qiáng)制savedInstanceState是否可null,開發(fā)者在覆寫時可以自己決定是否可null。當(dāng)然,對于本例,onCreate的參數(shù)是可能為null的,因此覆寫以后的方法應(yīng)為:
override fun onCreate(savedInstanceState: Bundle?)
通常來講,教科書式的講法,到這里就該結(jié)束了。然而直到我真正用Kotlin開始寫代碼時,發(fā)現(xiàn),有些需求實現(xiàn)起來真的有些奇怪。
還是舉個例子,我需要在Activity當(dāng)中創(chuàng)建一個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當(dāng)中呢?
class DemoActivity : Activity(){ private var aTextView: TextView? = null override fun onCreate(savedInstanceState: Bundle?){ super.onCreate(savedInstanceState) setContentView(R.layout.main) //當(dāng)然有更好用的方式,暫且先這么寫 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的屬性代理的一個實例,它提供了延遲加載的機(jī)制。換句話說,這里的lazy提供了初始化aTextView的方法,不過真正初始化這個動作發(fā)生的時機(jī)卻是在aTextView第一次被使用時了。lazy默認(rèn)是線程安全的,你當(dāng)然也可以關(guān)掉這個配置,只需要加個參數(shù)即可:
private val aTextView: TextView by lazy(LazyThreadSafetyMode.NONE){ findViewById(R.id.a_textview) as TextView }
好,這時候肯定有人要扔西紅柿過來了(再扔點兒雞蛋唄),你這lazy只能初始化val啊,萬一我要定義一個var成語,又需要延遲初始化,關(guān)鍵還不為null,怎么辦??
class Demo { lateinit var anJsonObject: JsonObject fun initDemo(){ anJsonObject = JsonObject("{...}") } }
lateinit的使用還是有很多限制的,比如只能在不可null的對象上使用,比須為var,不能為primitives(Int、Float之類)等等,不過這樣逼迫你一定要初始化這個變量的做法,確實能減少我們在開發(fā)中的遺漏,從而提高開發(fā)效率。
至于lazy技術(shù),實際上是Delegate Properties的一個應(yīng)用,也就是屬性代理了。在Kotlin當(dāng)中,聲明成員屬性,除了直接賦值,還可以用Delegate的方式來聲明,這個Delegate需要根據(jù)成員的類型(val或者var)來提供相應(yīng)的getValue和setValue方法,比如一個可讀寫的Delegate,需要提供下面的方法:
public interface ReadWriteProperty{ /** * 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(val context: Context, val name: String, val default: T) : ReadWriteProperty { 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 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 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類,我們就可以完全不需要關(guān)心SharedPreference了,下面給出使用的示例代碼:
class WhateverActivity : Activity(){ var aInt: Int by Preference(this, "aInt", 0) fun whatever(){ println(aInt)//會從SharedPreference取這個數(shù)據(jù) aInt = 9 //會將這個數(shù)據(jù)寫入SharedPreference } }
于是我們再也不需要重復(fù)寫那些getSharedPreference,也不用edit、commit,再見那些edit之后忘了commit的日子。有沒有覺得非常贊!
3.3 擴(kuò)展類擴(kuò)展類,就是在現(xiàn)有類的基礎(chǔ)上,添加一些屬性或者方法,當(dāng)然擴(kuò)展的這些成員需要導(dǎo)入當(dāng)前擴(kuò)展成員所在的包才可以訪問到。下面給出一個例子:
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) }
我們已經(jīng)介紹過data class,Coordinate有兩個成員分別是x和y,我們知道通常表示一個二維平面,有這倆夠了;然而我們在圖形學(xué)當(dāng)中經(jīng)常會需要求得其極坐標(biāo),所以我們擴(kuò)展了Coordinate,增加了一個屬性theta表示角度(反正切的值域為-π/2 ~ π/2,所以這個式子不適用于二三象限,不過這不是重點了),增加了一個R方法來獲得點的半徑,于是我們在main方法中就可以這么用:
fun main(args: Array) { val coord = Coordinate(3.0,4.0) println(coord.theta) println(coord.R()) }
那么這個擴(kuò)展有什么限制呢?
在擴(kuò)展成員當(dāng)中,只能訪問被擴(kuò)展類在當(dāng)前作用域內(nèi)可見的成員,本例中的x和y都是public的(Kotlin默認(rèn)public,這個我們后面會提到),所以可以在擴(kuò)展方法和屬性中直接訪問。
擴(kuò)展成員與被擴(kuò)展類的內(nèi)部成員名稱相同時,擴(kuò)展成員將無法被訪問到
好的,基本知識就是這些了,下面我們再給出一個實際的例子。
通常我們在Java中會自定義一些LogUtils類來打日志,或者直接用android.util.log來輸出日志,不知道大家是什么感受,我反正每次因為要輸入Log.d還要輸入個tag簡直煩的要死,而且有時候恰好這個類還沒有tag這個成員,實踐中我們通常會把當(dāng)前類名作為TAG,但每個類都要做這么個工作,是在是沒有什么趣味可言(之前我是用LiveTemplates幫我的,即便如此也沒有那種流暢的感覺)。
有了Kotlin的這個擴(kuò)展功能,日子就會好過得多了,下面我創(chuàng)建的一個打日志的方法:
package com.benny.utils import android.util.Log inline funT.debug(log: Any){ Log.d(T::class.simpleName, log.toString()) }
有了這個方法,你可以在任何類的方法體中直接寫:
debug(whatever)
然后就會輸出以這個類名為TAG的日志。
嗯,這里需要簡單介紹Kotlin在泛型中的一個比較重要的增強(qiáng),這個在Java中無論如何也是做不到的:inline、reified。我們再來回頭看一下debug這個方法,我們發(fā)現(xiàn)它可以通過泛型參數(shù)T來獲取到T的具體類型,并且拿到它的類名——當(dāng)然,如果你愿意,你甚至可以調(diào)用它的構(gòu)造方法來構(gòu)造一個對象出來——為什么Kotlin可以做到呢?因為這段代碼是inline的,最終編譯時是要編譯到調(diào)用它的代碼塊中,這時候T的類型實際上是確定的,因而Kotlin通過reified這個關(guān)鍵字告訴編譯器,T這個參數(shù)可不只是個擺設(shè),我要把它當(dāng)實際類型來用呢。
為了讓大家印象深刻,我下面給出類似功能的Java的代碼實現(xiàn):
public static void debug(Class> clazz, Object log){ Log.d(clazz.getSimpleName(), log.toString()); }
而你如果說希望在Java中也希望像下面這樣拿到這個泛型參數(shù)的類型,是不可以的:
public staticvoid debug(Object log){ Log.d(T.getSimpleName(), log.toString());//錯誤,T是泛型參數(shù),無法直接使用 }
就算我們在調(diào)用處會寫道 debug < Date >("blabla"),但這個Date在編譯之后還是會被擦除。
3.4 函數(shù)式支持(Lambdas)Java 8已經(jīng)開始可以支持Lambda表達(dá)式了,這種東西對于Java這樣一個『根紅苗正』的面向?qū)ο缶幊陶Z言來說還真是顯得不自然,不過對于Kotlin來說,就沒那么多顧忌了。
通常我們需要執(zhí)行一段異步的代碼,我們會構(gòu)造一個Runnable對象,然后交給executor,比如這段java代碼:
executor.submit(new Runnable(){ @Override public void run(){ //todo } });
用Kotlin怎么寫呢?
executor.submit({ //todo })
一下子省了很多代碼。
那么實際當(dāng)中我們可能更常見到下面的例子,這是一段很常見的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庫的幫助下,我們甚至可以繼續(xù)簡化OnClickListener的設(shè)置方式:
textView.onClick{ /*todo*/ }
當(dāng)然,好玩的不止這些,如果結(jié)合上一節(jié)我們提到的擴(kuò)展方法,我們就很容易看到Kotlin的標(biāo)準(zhǔn)庫提供的類似with和apply這樣的方法是怎么工作的了:
public inline funwith(receiver: T, block: T.() -> R): R = receiver.block() public inline fun T.apply(block: T.() -> Unit): T { block(); return this }
我們通常會在某個方法體內(nèi)創(chuàng)建一個對象并返回它,可我們除了調(diào)用它的構(gòu)造方法之外還需要做一些其他的操作,于是就要創(chuàng)建一個局部變量。。。有了apply這個擴(kuò)展方法,我們就可以這么寫:
fun getStringBuilder: StringBuilder{ return StringBuilder().apply{ append("whatever") } }
這樣返回的StringBuilder對象實際上是包含"whatever"這個字符串的。
至于說Kotlin對于RxJava的友好性,使得我突然有點兒相信緣分這種東西了:
Observable.create3.5 Pattern Matching> { it.onStart() try { it.onNext(dummyObjs) } catch(e: Exception) { it.onError(e) } finally { it.onCompleted() } }.subscribe(object : Subscriber >() { override fun onCompleted() { } override fun onNext(t: ArrayList ?) { } override fun onError(e: Throwable?) { } })
記得之前在瀏覽Scala的特性時,看到:
object HelloScala{ // do something }
覺得很新鮮,這時候有個朋友不屑的說了句,Scala的模式匹配才真正犀利——Kotlin當(dāng)中也有這樣的特性,我們下面就來看個例子:
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表達(dá)式就是一個增強(qiáng)版的switch——Java 7以前的switch實際上支持的類型非常有限,Java 7當(dāng)中增加的對String的支持也是基于int類型的——我們可以看到when不再像switch那樣只匹配一個數(shù)值,它的子式可以是各種返回Boolean的表達(dá)式。
when表達(dá)式還有一種寫法更革命:
when { x.isOdd() -> print("x is odd") x.isEven() -> print("x is even") else -> print("x is funny") }
只要是返回Boolean的表達(dá)式就可以作為when的子式,這樣when表達(dá)式的靈活性可見一斑。當(dāng)然,與Scala相比,Kotlin還是要保守一些的,下面給出一個Scala類似的例子,大家感受一下,這實際上也可以體現(xiàn)出Kotlin在增加Java的同時也盡量保持簡單的設(shè)計哲學(xué)(大家都知道,畢竟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)
運行結(jié)果如下:
a tuple with : 1 , 3 [I@2d554825 3.0, 4.03.6 如果你是一個SDK開發(fā)者
我曾經(jīng)做過一段時間的SDK開發(fā),SDK的內(nèi)部有很多類其實是需要互相有訪問權(quán)限的,但一旦類及其成員是public的,那么調(diào)用方也就可以看到它們了;而protected或者default這樣的可見性對于子包卻是不可見的。
用了這么久Java,這簡直是我唯一強(qiáng)烈感到不滿的地方了,甚至于我突然明白了C++的friend是多么的有用。
Kotlin雖然沒有提供對于子包可見的修飾符,不過它提供了internal:即模塊內(nèi)可見。換句話說,internal在模塊內(nèi)相當(dāng)于public,而對于模塊外就是private了——于是乎我們?nèi)绻_發(fā)SDK,那么可以減少api層的編寫,那些用戶不可見的部分直接用internal豈不更好。當(dāng)然有人會說我們應(yīng)當(dāng)有proguard做混淆,我想說的是,proguard自然是要用到的,不過那是SDK這個產(chǎn)品加工的下一個環(huán)節(jié)了,我們?yōu)槭裁床荒茉诖a級別把這個事情做好呢?
關(guān)于Kotlin的默認(rèn)可見性究竟是哪個還有人做出過討論,有興趣的可以參考這里:Kotlin’s default visibility should be internal。
3.7 DSL其實我們對DSL肯定不會陌生,gradle的腳本就是基于groovy的DSL,而Kotlin的函數(shù)特性顯然也是可以支持DSL的。比如,我們最終要生成下面的xml數(shù)據(jù):
我們可以構(gòu)建下面的類:
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 "${component} " } } 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 " ${file} " } } 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 " " } } fun main(args: Array ) { val xml = project { version = "4" component { name = "Encoding" file { url = "PROJECT" charset = "UTF-8" } } } println(xml) }
我們看到在main方法當(dāng)中,我們用kotlin定義的dsl寫出了一個Project對象,它有這與xml描述的一致的結(jié)構(gòu)和含義,如果你愿意,可以構(gòu)造相應(yīng)的方法來輸出這樣的xml,運行之后的結(jié)果:
當(dāng)然,這個例子做的足夠的簡陋,如果你有興趣也可以抽象出"Element",并為之添加"Attributes",實際上這也不是很難。
3.7 Kotlin與Android的另一些有趣的東西寫了很多代碼,卻發(fā)現(xiàn)它們干不了多少事情,終究還是會苦惱的。比如我一直比較痛苦的一件事兒就是:
Button button = (Button) findViewById(R.id.btn);
如果我需要很多個按鈕和圖片,那么我們要寫一大片這樣的findViewById。。媽呀。。。這活我干不了啦。。
不過用Kotlin的Android擴(kuò)展插件,我們就可以這樣:
先上布局文件:
main.xml
在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() } textView.text = "你好" } }
注意到:
import kotlinx.android.synthetic.main.load_activity.*
導(dǎo)入這一句之后,我們就可以直接在代碼中使用start、textView,他們分別對應(yīng)于main.xml中的id為start的按鈕和id為textView的TextView。
于是你就發(fā)現(xiàn)你再也不用findViewById了,多么愉快的一件事?。。‘?dāng)然,你還會發(fā)現(xiàn)Toast的調(diào)用也變得簡單了,那其實就是一個擴(kuò)展方法toast();而startActivity呢,其實就是一個inline加reified的應(yīng)用——這我們前面都提到過了。
還有一個惡心的東西就是UI線程和非UI線程的切換問題。也許你會用handler不斷的post,不過說真的,用Handler的時候難道你不顫抖么,那可是一個很容易內(nèi)存泄露的魔鬼呀~哈哈,好吧其實我不是說這個,主要是用handler寫出來的代碼 實在 太 丑 了 !!
原來在java當(dāng)中,我們這么寫:
handler.post(new Runnable(){ @Override public void run(){ //todo } }); MainActivity.this.runOnUiThread( public void run(){ //todo } });
而在Kotlin當(dāng)中呢,我們只需要這么寫:
async() { //do something asynchronously uiThread { //do something on UI thread } }
自己感受一下吧。
下面我們再來提一個有意思的東西,我們從做Android開發(fā)一開始就要編寫xml,印象中這個對于我來說真的是一件痛苦的事情,因為它的工作機(jī)制并不如代碼那樣直接(以至于我現(xiàn)在很多時候居然喜歡用Java代碼直接寫布局)——當(dāng)然,最主要的問題并不是這個,而是解析xml需要耗費CPU。Kotlin有辦法可以解決這個問題,那就是DSL了。下面給出一個例子:
linearLayout { button("Login") { textSize = 26f }.lparams(width = wrapContent) { horizontalMargin = dip(5) topMargin = dip(10) } }
一個LinearLayout包含了一個Button,這段代碼你可以直接寫到你的代碼中靈活復(fù)用,就像這樣:
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的繁瑣來,這真是要清爽很多
布局本身也是代碼,可以靈活復(fù)用
再也不用findViewById了,難道你不覺得在這個上面浪費的生命已經(jīng)足夠多嗎
事件監(jiān)聽很方便的嵌到布局當(dāng)中
DSL方式的布局沒有運行時的解析的負(fù)擔(dān),你的邏輯代碼怎么運行它就怎么運行
Anko還增加了更多好玩的特性,有興趣的可以參考:Anko@Github
3.8 方法數(shù)之痛我曾經(jīng)嘗試用Scala寫了個Android的HelloWorld,一切都配置好以后,僅僅引入了Scala常見的幾個庫,加上support-v4以及appcompat這樣常見的庫,結(jié)果還是報錯了。是的,65K。。。而且用Scala開發(fā)Android的話,基于gradle的構(gòu)建會讓整個app的build過程異常漫長,有時候你會覺得自己悟出了廣義相對論的奧義,哦不,你一定是暈了,時間并沒有變慢。
相比之下,Kotlin的標(biāo)準(zhǔn)庫只有7000個方法,比support-v4還要小,這正反映了Kotlin的設(shè)計理念:100% interoperable with Java。其實我們之前就提到,Java有的Kotlin就直接拿來用,而Scala的標(biāo)準(zhǔn)庫要有5W多個方法,想想就還是想想算了。
4、小結(jié)目前Kotlin 1.0已經(jīng)release,盡管像0xffffffff識別成Long類型這樣的bug仍然沒有解詳情:
val int: Int = 0xffffffff // error val anotherInt: Int = 0xffffffff.toInt() // correct
不過,Kotlin的教學(xué)資源和社區(qū)建設(shè)也已經(jīng)相對成熟,按照官方的說法,Kotlin可以作為生產(chǎn)工具投入開發(fā),詳情可以參考:Kotlin 1.0 Released: Pragmatic Language for JVM and Android。
敢于吃螃蟹,多少有些浪漫主義色彩,我們這些程序員多少可以有些浪漫主義特質(zhì),不過在生成環(huán)境中,穩(wěn)定高于一切仍然是不二法則。追求新技術(shù),一方面會給團(tuán)隊帶來開發(fā)和維護(hù)上的學(xué)習(xí)成本,另一方面也要承擔(dān)未來某些情況下因為對新技術(shù)不熟悉而產(chǎn)生未知問題的風(fēng)險——老板們最怕風(fēng)險了~~
基于這一點,毫無疑問,Kotlin可以作為小工具、測試用例等的開發(fā)工具,這是考慮到這些代碼通常體量較小,維護(hù)人數(shù)較少較集中,對項目整體的影響也較??;而對于核心代碼,則視情況而定吧。
就我個人而言,長期下去,Kotlin很大可能會成為我的主要語言,短期內(nèi)則仍然采用溫和的改革方式慢慢將Kotlin滲透進(jìn)來。
一句話,Kotlin是用來提升效率的,如果在你的場景中它做不到,甚至成了拖累,請放開它。
如果你覺得內(nèi)容意猶未盡,如果你想了解更多相關(guān)信息,請掃描以下二維碼,關(guān)注我們的公眾賬號,可以獲取更多技術(shù)類干貨,還有精彩活動與你分享~
騰訊Bugly簡介騰訊Bugly,專業(yè)的App Crash監(jiān)測平臺。監(jiān)測信息實時上報,讓開發(fā)同學(xué)可以第一時間了解到App的質(zhì)量情況,為移動開發(fā)節(jié)省大量人力與精力,提升移動產(chǎn)品質(zhì)量。目前騰訊內(nèi)部所有的移動端產(chǎn)品均在使用,現(xiàn)已免費對外開放。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/37761.html
摘要:讓你收獲滿滿碼個蛋從年月日推送第篇文章一年過去了已累積推文近篇文章,本文為年度精選,共計篇,按照類別整理便于讀者主題閱讀。本篇文章是今年的最后一篇技術(shù)文章,為了讓大家在家也能好好學(xué)習(xí),特此花了幾個小時整理了這些文章。 showImg(https://segmentfault.com/img/remote/1460000013241596); 讓你收獲滿滿! 碼個蛋從2017年02月20...
摘要:作為開發(fā)年的開發(fā)者該如何系統(tǒng)的提升自己如今,國內(nèi)移動互聯(lián)網(wǎng)的紅利期已過,開發(fā)工程師也從最初的一人難求,到現(xiàn)在的一個崗位百人競爭,僧多粥少的情況直接導(dǎo)致整個行業(yè)對求職者的要求越來越高。另外,開發(fā)越來越規(guī)范,間接導(dǎo)致項目對質(zhì)量要求的提升。 ...
閱讀 2723·2023-04-25 22:15
閱讀 1804·2021-11-19 09:40
閱讀 2149·2021-09-30 09:48
閱讀 3213·2021-09-03 10:36
閱讀 2026·2021-08-30 09:48
閱讀 1854·2021-08-24 10:00
閱讀 2725·2019-08-30 15:54
閱讀 699·2019-08-30 15:54