用 Kotlin 开发 Android 项目的感受
空指针安全
简单一点的例子,那就是 String 和 String? 是两种不同的类型。String 已经确定是不会为空,一定有值;而 String?则是未知的,也许有值,也许是空。在使用对象的属性和方法的时候,String 类型的对象可以毫无顾忌的直接使用,而 String?类型需要你先做非空判断。
fun demo() {val string1: String = "string1"val string2: String? = nullval string3: String? = "string3"println(string1.length) //7println(string2?.length) //nullprintln(string3?.length) //7}
尽管 string2 是一个空对象,也并没有因为我调用了它的属性/方法就报空指针。而你所需要做的,仅仅是加一个"?"。
转型与智能转换
我写过这样子的 JAVA 代码
if(view instanceof TextView) {TextView textView = (TextView) view;textView.setText("text");}
if(view is TextView) {TextView textView = view as TextViewtextView.setText("text")}
if(view instanceof TextView) {((TextView) view).setText("text");}
if(view is TextView) {(view as TextView).setText("text")}
至少我个人而言,在习惯了 as Class 顺畅的写法之后,是再难以忍受 JAVA 中前置的写法,哪怕有 cast 快捷键的存在,仍然很容易打断我写代码的顺序和思路
事实上,Kotlin 此处可以更简单:
if(view is TextView) {view.setText("text")}
接着上面的空安全来举个例子,常规思路下,既然 String 和 String? 是不同的类型,是不是我有可能会写出这样的代码?
val a: A? = A()if (a != null) {println(a?.b)}
智能转换还有一个经常出现的场景,那就是 switch case 语句中。在 Kotlin 中,则是 when 语法。
fun testWhen(obj: Any) {when(obj) {is Int -> {println("obj is a int")println(obj + 1)}is String -> {println("obj is a string")println(obj.length)}else -> {println("obj is something i don‘t care")}}}fun main(args: Array<String>) {testWhen(98)testWhen("98")}
可以看出,在已经判断出是 String 的条件下,原本是一个 Any 类的 obj 对象,我可以直接使用属于 String 类的 .length 属性。
Kotlin 的智能程度远不止如此,即便是现在,在编写代码的时候还会偶尔蹦一个高亮警告出来,这时候我才知道原来我的写法是多余的,Kotlin 已经帮我处理了好了。
比 switch 更强大的 when
通过上面智能转化的例子,已经展示了一部分 when 的功能。但相对于 JAVA 的 switch,Kotlin 的 when 带给我的惊喜远远不止这么一点。
例如:
fun testWhen(int: Int) {when(int) {in 10 .. Int.MAX_VALUE -> println("${int} 太大了我懒得算")2, 3, 5, 7 -> println("${int} 是质数")else -> println("${int} 不是质数")}}fun main(args: Array<String>) {(0..10).forEach { testWhen(it) }}
0 不是质数1 不是质数2 是质数3 是质数4 不是质数5 是质数6 不是质数7 是质数8 不是质数9 不是质数10 太大了我懒得算
容器的操作符
自从迷上 RxJava 之后,我实在很难再回到从前,这其中就有 RxJava 中许多方便的操作符。而 Kotlin 中,容器自身带有一系列的操作符,可以非常简洁的去实现一些逻辑。
例如:
(0 until container.childCount).map { container.getChildAt(it) }.filter { it.visibility == View.GONE }.forEach { it.visibility = View.VISIBLE }
再用 map 操作符配合取出 child 的代码将这个 Int 的集合转化为了 childView 的集合;
然后在用 filter 操作符对集合做筛选,选出 childView 中所有可见性为 GONE 的作为一个新的集合;
最终 forEach 遍历把所有的 childView 都设置为 VISIBLE。
这里再贴上 JAVA 的代码作为对比。
for(int i = 0; i < container.childCount - 1; i++) {View childView = container.getChildAt(i);if(childView.getVisibility() == View.GONE) {childView.setVisibility(View.VISIBLE);}}
线程切换
既然上面提到了 RxJava,不得不想起 RxJava 的另一个优点——线程调度。Kotlin 中有一个专为 Android 开发量身打造的库,名为 anko,其中包含了许多可以简化开发的代码,其中就对线程进行了简化。
async {val response = URL("https://www.baidu.com").readText()uiThread {textView.text = response}}
抛开内部的实现,你再也不需要为了一个简简单单的异步任务去写一大堆的无效代码。
一个关键字实现单例
没错,就是一个关键字就可以实现单例:
object Log {fun i(string: String) {println(string)}}fun main(args: Array<String>) {Log.i("test")}
自动getter/setter及class简洁声明
JAVA 中类的标准写法下,一个属性对应了 get 和 set 两个方法,需要手动写的代码量相当大。
而 Kotlin 中是这样的:
class Person(var name: String)val person = Person("张三");
class Person(var name: String = "张三")val person = Person()
data class Column(var subId: String?,var subTitle: String?,var subImg: String?,var subContentnum: Int?,)
DSL 式编程
说起 dsl ,Android 开发者接触的最多的或许就是 gradle 了
例如:
android {compileSdkVersion 23buildToolsVersion "23.0.2"defaultConfig {applicationId "com.zll.demo"minSdkVersion 15targetSdkVersion 23versionCode 1versionName "1.0"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile(‘proguard-android.txt‘), ‘proguard-rules.pro‘}}}
那么在 Android 项目的代码中使用 DSL 是一种什么样的感觉呢?
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val homeFragment = HomeFragment()val columnFragment = ColumnFragment()val mineFragment = MineFragment()setContentView(tabPages {backgroundColor = R.color.whitedividerColor = R.color.colorPrimarybehavior = ByeBurgerBottomBehavior(context, null)tabFragment {icon = R.drawable.selector_tab_homebody = homeFragmentonSelect { toast("home selected") }}tabFragment {icon = R.drawable.selector_tab_searchbody = columnFragment}tabImage {imageResource = R.drawable.selector_tab_photoonClick { showSheet() }}tabFragment {icon = R.drawable.selector_tab_minebody = mineFragment}})}
没错,上面的代码就是用来构建这个主界面的 viewPager + fragments + tabBar 的。以 tabPages 作为开始,设置背景色,分割线等属性;再用 tabFrament 添加 fragment + tabButton,tabImage 方法则只添加 tabButton。所见的代码都是在做配置,而具体的实现则被封装了起来。
前面提到过 anko 这个库,其实也可以用来替代 xml 做布局用:
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)verticalLayout {textView {text = "这是标题"}.lparams {width = matchParentheight = dip(44)}textView {text = "这是内容"gravity = Gravity.CENTER}.lparams {width = matchParentheight = matchParent}}}
委托/代理
通过 Kotlin 中的委托功能,我们能轻易的写出一个 SharedPreference 的代理类
class Preference<T>(val context: Context, val name: String?, val default: T) : ReadWriteProperty<Any?, T> {val prefs by lazy {context.getSharedPreferences("xxxx", Context.MODE_PRIVATE)}override fun getValue(thisRef: Any?, property: KProperty<*>): T = with(prefs) {val res: Any = when (default) {is Long -> {getLong(name, 0)}is String -> {getString(name, default)}is Float -> {getFloat(name, default)}is Int -> {getInt(name, default)}is Boolean -> {getBoolean(name, default)}else -> {throw IllegalArgumentException("This type can‘t be saved into Preferences")}}res as T}override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) = with(prefs.edit()) {when (value) {is Long -> putLong(name, value)is String -> putString(name, value)is Float -> putFloat(name, value)is Int -> putInt(name, value)is Boolean -> putBoolean(name, value)else -> {throw IllegalArgumentException("This type can‘t be saved into Preferences")}}.apply()}}
class EntranceActivity : BaseActivity() {private var userId: String by Preference(this, "userId", "")override fun onCreate(savedInstanceState: Bundle?) {testUserId()}fun testUserId() {if (userId.isEmpty()) {println("userId is empty")userId = "default userId"} else {println("userId is $userId")}}}
userId is empty
userId is default userId
userId is default userId
...
第一次启动 app 的时候从 SharedPreference 中取出来的 userId 是空的,可是后面却不为空。由此可见,userId = "default userId" 这句代码成功的将 SharedPreference 中的值修改成功了。
也就是说,在这个 Preference 代理的帮助下,SharedPreference 存取操作变得和普通的对象调用、赋值一样的简单。
扩展,和工具类说拜拜
很久很久以前,有人和我说过,工具类本身就是一种违反面向对象思想的东西。可是当时我就想了,你不让我用工具类,那有些代码我该怎么写呢?直到我知道了扩展这个概念,我才豁然开朗。
fun ImageView.displayUrl(url: String?) {if (url == null || url.isEmpty()) {imageResource = R.mipmap.ic_launcher} else {Glide.with(context).load(ColumnServer.SERVER_URL + url).into(this)}}...val imageView = findViewById(R.id.avatarIv) as ImageViewimageView.displayUrl(url)
- 我给 ImageView 这个类扩展了一个名为 displayUrl 的方法,这个方法接收一个名为 url 的 String?类对象。如不出意外,会通过 Glide 加载这个 url 的图片,显示在当前的 imageView 上;
- 我在另一个地方通过 findViewById 拿到了一个 ImageView 类的实例,然后调用这个 imageView 的displayUrl 方法,试图加载我传入的 url
通过扩展来为 ImageView 添加方法,相比于通过继承 ImageView 来写一个 CustomImageView,再添加方法而言,侵入性更低,不需要在代码中全写 CustomImageView,也不需要在 xml 布局中将包名写死,造成移植的麻烦。
这事用工具类当然也可以做,比如做成 ImageUtil.displayUrl(imageView, url),但是工具类阅读起来并没有扩展出来的方法读起来更自然更流畅。
扩展是 Kotlin 相比于 JAVA 的一大杀器
向 findViewById 说 NO
不同于 JAVA 中,在 Kotlin 中 findViewById 本身就简化了很多,这得益于 Kotlin 的类型推断以及转型语法后置:
val onlyTv = findViewById(R.id.onlyTv) as TextView
当然不是。在官方库 anko 的支持下,这事又有了很多变化。
例如
val onlyTv = find<TextView>(R.id.onlyTv)val onlyTv: TextView = find(R.id.onlyTv)
让我们点过去看看 find 的源码:
inline fun <reified T : View> Activity.find(id: Int): T = findViewById(id) as T
在 anko 的帮助下,你只需要根据布局的 id 写一句 import 代码,然后你就可以把布局中的 id 作为 view 对象的名称直接使用。不仅 activity 中可以这样玩,你甚至可以 viewA.viewB.viewC,所以大可不必担心 adapter 中应当怎么写。
import kotlinx.android.synthetic.main.activity_main.*...onlyTv.text="不需要声明直接可以用"
也许有的朋友会发现这和 Google 出品的 databinding 实在是有异曲同工之妙,那如果我告诉你,databinding 库本身就有对 kotlin 的依赖呢?
简单粗暴的 startActivity
我们原本为了 startActivity,不得不 new 一个 Intent 出来,特别是当我要传递参数的时候:
Intent intent = new Intent(LoginActivity.this, MainActivity.class);intent.putExtra("name", "张三");intent.putExtra("age", 27);startActivity(intent);
在 anko 的帮助下,startActivity 是这样子的:
startActivity<MainActivity>()startActivity<MainActivity>("name" to "张三", "age" to 27)startActivityForResult<MainActivity>(101, "name" to "张三", "age" to 27)
有参也好说,这个方法支持你传入 vararg params: Pair<String, Any>
有没有觉得代码写起来、读起来流畅了许多?
玲珑小巧的 toast
JAVA 中写一个 toast 大概是这样子的:
Toast.makeText(context, "this is a toast", Toast.LENGTH_SHORT).show();
不得不说真的是又臭又长,虽然确实是有很多考量在里面,但是对于使用来说实在是太不便利了,而且还很容易忘记最后一个 show()。我敢说没有任何一个一年以上的 Android 开发者会不去封装一个 ToastUtil 的。
让我们看看 anko 是怎么做的了:
context.toast("this is a toast")
toast("this is a toast")
longToast("this is a toast")
用 apply 方法进行数据组合
假设有如下 A、B、C 三个 class:
class A(val b: B)class B(val c: C)class C(val content: String)
C c = new C("content");B b = new B(c);A a = new A(b);
当然,在 JAVA 中也是有解决方案的,比如使用 Builder 模式来进行相应配置。(说到这里,其实用 Builder 模式基本上也可以说是 JAVA 语言的 DSL)
但是在更为复杂的情况下,即便是有设计模式的帮助,也很难保证代码的可读性。那么 Kotlin 有什么好方法来解决这个问题吗?
Kotlin 中有一个名为 apply 的方法,它的源码是这样子的:
@kotlin.internal.InlineOnlypublic inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
public fun <T> T.apply(block: T.() -> Unit): T {block()return this}
- 首先,我们可以看出 T 是一个泛型,而且后面没有给 T 增加约束条件,那么这里的 T 可以理解为:我这是在给所有类扩展一个名为【apply】的方法;
- 第一行最后的【: T】表明,我最终是要返回一个 T 类,其实就是返回调用方法的这个对象自身;
- 在 return this 之前,我执行了一句 block(),这意味着 block 本身一定是一个方法。我们可以看到,apply 方法接收的 block 参数的类型有点特殊,不是 String 也不是其他什么明确的类型,而是【T.() -> Unit】;
- 【T.() -> Unit】表示的意思是:这是一个上下文在 T 对象中,返回一个 Unit 类对象的方法。由于 Unit 和 JAVA 中的 Void 一致,所以可以理解为不需要返回值。
有了上面的解析,我们再来看一下这句代码:
val textView = TextView(context).apply {text = "这是文本内容"textSize = 16f}
或许你会觉得这和 JAVA 比起来并没有什么优势。别着急,我们慢慢来:
layout.addView(TextView(context).apply {text = "这是文本内容"textSize = 16f})
上面的A、B、C 问题用 Kotlin 来实现是可以这么写的:
val a = A().apply {b = B().apply {c = C("content")}}
文章来自:http://www.cnblogs.com/baiqiantao/p/8ffb4e6df798972b966e603dd32477f8.html