Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,经过平常工做经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处。html
写了许久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了,真的爽爆有木有!
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代码。截止到如今,你什么都不用作,程序就已经能够跑起来了。
咱们都知道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) }
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对于常见的状况做了比较详细的阐述,这里就再也不赘述。
最初学Java的时候,学到一个概念叫JavaBean,当时就要被这个概念给折磨死了。明明很简单的一个东西,结果搞得很复杂的样子,并且因为当时对于这些数据类的设计概念不是很清晰,于是也并不懂得去覆写诸如equals和hashcode这样重要的方法,一旦用到HashMap这样的集合框架,老是出了问题都不知道找谁。
Kotlin提供了一种很是简单的方式来建立这样的数据类,例如:
data class Coordinate(val x: Double, val y: Double)
仅仅一行代码,Kotlin就会建立出一个完整的数据类,并自动生成相应的equals、hashcode、toString方法。是否是早就受够了getter和setter?反正我是受够了。
第一次见到空类型安全的设计是在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的日子。有没有以为很是赞!
扩展类,就是在现有类的基础上,添加一些属性或者方法,固然扩展的这些成员须要导入当前扩展成员所在的包才能够访问到。下面给出一个例子:
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在编译以后仍是会被擦除。
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?) { } })
记得以前在浏览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
我曾经作过一段时间的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。
其实咱们对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",实际上这也不是很难。
写了不少代码,却发现它们干不了多少事情,终究仍是会苦恼的。好比我一直比较痛苦的一件事儿就是:
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
我曾经尝试用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,专业的App Crash监测平台。监测信息实时上报,让开发同窗能够第一时间了解到App的质量状况,为移动开发节省大量人力与精力,提高移动产品质量。目前腾讯内部全部的移动端产品均在使用,现已免费对外开放。