|
|
|
|
移动端

Kotlin如何成为我们Android开发的主要语言

Kotlin是一个基于JVM的新的编程语言,由JetBrains开发。JetBrains,作为目前广受欢迎的Java IDE IntelliJ的提供商,在Apache许可下已经开源其Kotlin编程语言。与Java相比,Kotlin的语法更简洁、更具表达性,而且提供了更多的特性,比如,高阶函数、操作符重载、字符串模板。它与Java高度可互操作,可以同时用在一个项目中。

作者:刘志勇 译来源:移动开发前线|2016-11-24 17:21

开发者盛宴来袭!7月28日51CTO首届开发者大赛决赛带来技术创新分享

Kotlin如何成为我们Android开发的主要语言

版权声明

作者:Dima Kovalenko

译者:刘志勇

本文由作者授权翻译并发布,未经许可不得转载。

原文:https://medium.com/uptech-team/how-kotlin-became-our-primary-language-for-android-3af7fd6a994c#.d16qsda0x

引言

Kotlin是一个基于JVM的新的编程语言,由JetBrains开发。JetBrains,作为目前广受欢迎的Java IDE IntelliJ的提供商,在Apache许可下已经开源其Kotlin编程语言。与Java相比,Kotlin的语法更简洁、更具表达性,而且提供了更多的特性,比如,高阶函数、操作符重载、字符串模板。它与Java高度可互操作,可以同时用在一个项目中。

Kotlin的定位非常有特点,它并不像Scala那样另起炉灶,Scala是一切尽量自己来,将类库,尤其是集合类都自己来了一遍。实在不够用了再用java的;而Kotlin是对现有Java的增强,尽量用Java的,不够用了再扩展,尤其体现在二者的容器库上,但同时始终保持对java的兼容。这种特点导致Kotlin的学习曲线极低。这是Kotlin官网首页重点强调的:“100% interoperable with Java™”。这意味着什么呢?或者换个问法:我什么时候可以开始在我的项目中引入Kotlin呢?我的回答是:现在就可以视你对kotlin的掌握程度,逐步引入kotlin的代码。

Dima Kovalenko在博客中分享了他们团队使用Kotlin开发商业应用程序的心得和经验,并提供了一些参考资料。希望本文能对广大Android开发程序员有所启发。

几个月前,我们的团队决定开始新的尝试:完全应用Kotlin编程语言开发一个商业应用程序,这是JetBrains公司设计并开源的一种新编程语言。以前,我们有过Kotlin的经验,但那只是小规模应用:将应用程序的一部分转换到一种新的语言,或者应用在花里胡哨的项目。然而,用新的编程语言来开发商业应用程序,我们遇到了一些困难:

  • 我们深深扎根于基于Java的Android开发。切换到Kotlin相当困难,对于以前没有函数式编程经验的人员而言,尤为困难。
  • 有些东西只是不工作。 Dagger也没有立即很好的使用。

所有这些问题,都可能会导致项目无法按期交付、并带来应用程序的稳定性问题。

一个人应该有强烈的转型动力。我们的激励是:相信Kotlin将是Android平台开发的颠覆者——并且这是对我们有挑战的趣事。

让我们打开Kotlin的参考书,开始开发Voter应用程序。Kotlin是一种与Java具有100%互操作性的JVM语言,如果您熟悉Java,那么学习Kotlin就会很容易。然而,如果想充分利用这个编程语言,理解函数式编程概念是至关重要的。

学习函数式编程需要一段时间。所以要有耐心。

至少在学习之初,函数式编程并不容易。我强烈建议使用Martin Ordersky的“Scala中的函数式编程”的课程来学习。Scala有时势不可挡,但它提供了一个极好的函数式编程思维的概述。你可以把Kotlin看成Scala的一个更简化的版本。

为什么我们转向Kotlin阵营

函数式编程风格

Kotlin与Java是100%可互操作的。此外,Kotlin是一种函数式语言。后者允许将表达性的代码编写得更优雅。

1. 纯函数

纯函数(没有副作用的函数)是最重要的函数概念,它允许我们大大降低代码复杂性并消除大多数可变状态。

在JavaScript、Java和C#这些命令式编程语言中,副作用无处不在。这使得调试非常困难,因为变量可以在程序中的任何位置更改。所以当出现一个错误时,由于变量可以在错误的时间更改为错误的值,那么你到哪里去寻找错误呢?到处寻找错误吗?这可不好玩啊!

请注意我们是如何操作数据而不更改其内容的。

  1. fun flatTree(tree: TreeNode): List<TreeNode>  
  2. = listOf(tree, *tree.children.flatMap(::flatTree).toTypedArray()) 

2. 高阶函数

高阶函数将函数用作参数,返回函数或将函数作为返回值的函数。

高阶函数无处不在。你只需将函数传递给集合,就能使代码更容易阅读。比如, titles.map {it.toUpperCase()}读取简单的英语,是不是很棒?

让我们设想一种情况,假设要计算不同类型的未读消息的数量。典型的方法是:

  1. private fun getUnreadCountFromUsers() { 
  2.  
  3.     val conversations = datasource.getConversations() 
  4.  
  5.     var count = 0 
  6.  
  7.     for (conversation in conversations) { 
  8.  
  9.       if (conversation.recipientId != null) { 
  10.  
  11.         for (message in conversation.messages) { 
  12.  
  13.           if (message.unread) { 
  14.  
  15.             count += 1 
  16.  
  17.           } 
  18.  
  19.         } 
  20.  
  21.       } 
  22.  
  23.     } 
  24.  
  25.   } 
  26.  
  27.  
  28.   private fun getNumberOfUnreadAttachmentsInGroupConversations() { 
  29.  
  30.     val conversations = datasource.getConversations() 
  31.  
  32.     var count = 0 
  33.  
  34.     for (conversation in conversations) { 
  35.  
  36.       if (conversation.groupId != null) { 
  37.  
  38.         for (message in conversation.messages) { 
  39.  
  40.           if (message.unread && message.type == MessageType.ATTACHMENT) { 
  41.  
  42.             count += 1 
  43.  
  44.           } 
  45.  
  46.         } 
  47.  
  48.       } 
  49.  
  50.     } 
  51.  

正如你所看到的,当引入新的需求时,代码变得难以理解、不可收拾。让我们看看如何使用高阶函数来解决这个问题:

  1. private fun getNumberOfAttachmentsInGroupConvesationsFun() { 
  2.  
  3.     return getCount({conv -> conv.groupId != null}, {it -> it.type == MessageType.ATTACHMENT && it.unread}) 
  4.  
  5.   } 
  6.  
  7.  
  8.   private fun getUnreadCountFromUsersFun() { 
  9.  
  10.     return getCount({conv -> conv.recipientId != null}, {message -> message.unread}) 
  11.  
  12.   } 
  13.  
  14.  
  15.   private fun getTotalNumberOfMessages() = getCount({true}, {true}) 
  16.  
  17.  
  18.   private fun getCount(convFilter: (Conversation) -> Boolean, messageFilter: (Message) -> Boolean) { 
  19.  
  20.     datasource.getConversations() 
  21.  
  22.         .filter(convFilter) 
  23.  
  24.         .flatMap { it.messages } 
  25.  
  26.         .filter(messageFilter) 
  27.  
  28.         .fold(0, { count, message -> count + 1}) 
  29.  

我们还可以想象一下用例,假设想将fold函数变量参数化。比方说,计算未读消息的乘积。

使用高阶函数的另一个例子是用简单的高阶函数代替多个监听器:

  1. BillingView : LinearLayout { 
  2.   var billingChangeListener: (() -> Unit)? = null 
  3.   ... 
  4. ... // in an activity far, far away 
  5. billingView.billingChangeListener { updateUI() } 

3. 不变性

不变性使得代码更容易编写,使用和推理代码(类不变性一次建立,然后不变——一劳永逸)。应用程序组件的内部状态将更加一致。Kotlin通过引入val关键字以及Kotlin集合来强制不变性,Kotlin集合在默认情况下是不可变的。 一旦val或者一个集合被初始化,你就可以确定它的有效性。(有关val关键字的更精确的定义,请参阅文末的更新)。

  1. data class Address(val line1: String, val city: String) 
  2. val items = listOf(Address("242 5th St""Los Angeles"),   Address("Dovzhenka St. 5""Kiev")) 

Null-safety

这个语言特性使我们仔细考虑了模型类中字段的可空性。以前,当不确定DTO中的字段是否已初始化时,@Nullable和@NotNull的注释就能提供帮助,但也很有限。现在,使用Kotlin,就能让你准确知道什么字段可以为null,什么字段被初始化(例如,Dagger注入的字段),并且你可以对这些字段有严格的控制。结果?几乎没有NullPointerExceptions。(在内部我们管?.叫做“鹅”操作符,因为它看起来像一个鹅的脖子。)

  1. brand?.let { badge.enabled = brand.isNewBadge } 
  2. // Can also be written as  
  3. badge.enabled = brand?.isNewBadge?:false 

Anko

Anko DSL是一个很了不起的的库,它大大简化了工作视图、线程和Android生命周期。据Github的描述,Anko是“令人愉快的Android应用程序开发”,事实证明,的确如此。

  1. selector(items = listOf("Like""Dislike") { 
  2.  
  3.     when (it) { 
  4.  
  5.       0 -> if (!liked) likePost() 
  6.  
  7.       else -> if (!disLiked) disLikePost() 
  8.  
  9.     } 
  10.  
  11.  
  12.  
  13. doAsync { 
  14.  
  15.     // Long background task 
  16.  
  17.     uiThread { 
  18.  
  19.       alert(R.string.could_not_log_in) { 
  20.  
  21.         yesButton { dismiss() } 
  22.  
  23.         cancellable = false 
  24.  
  25.       }.show() 
  26.  
  27.     } 
  28.  

注意,当uiThread在Activity内调用时,如果isFinishing为true,块将不会执行。我们实际上不使用这个功能,因为RxJava会处理应用程序中的所有线程,但它是一个很好的功能。

使用Anko而不是XML。虽然Anko还没有做好准备取代标准的Android UI构建,但有的时候,它非常方便。

  1. verticalLayout() { 
  2.  
  3.  
  4.   friendsPanel = friendsPanel.with(friendsData).lparams(width = matchParent) 
  5.  
  6.  
  7.   politicalMapCardView { 
  8.  
  9.     setMarker(quizManager.getMarker()) 
  10.  
  11.   }.lparams(width = matchParent) { topMargin = dip(10) } 
  12.  
  13.  
  14.   cardView() { 
  15.  
  16.     verticalLayout() { 
  17.  
  18.       topPadding = dip(5) 
  19.  
  20.       textView(getString(R.string.register_question)) 
  21.  
  22.       blueButtonView(text="Register here") { 
  23.  
  24.         onClick { browse("https://www.uptech.team") } 
  25.  
  26.       } 
  27.  
  28.     } 
  29.  
  30.   }.lparams(width = matchParent) { 
  31.  
  32.     topMargin = dip(10) 
  33.  
  34.     bottomMargin = dip(20) 
  35.  
  36.   } 
  37.  

如您所见,Anko DSL允许您在Android内置视图中使用自定义视图。这一点与标准XML相比有很大的优势。

Kotlin Android扩展:删除ButterKnife依赖

  1. @Bind(R.id.first_name) 
  2.  
  3.     protected EditText firstName; 
  4.  
  5.  
  6.     @Bind(R.id.last_name) 
  7.  
  8.     protected EditText lastName; 
  9.  
  10.  
  11.     @Bind(R.id.address_line1) 
  12.  
  13.     protected EditText addressLine1; 
  14.  
  15.  
  16.     @Bind(R.id.address_line2) 
  17.  
  18.     protected EditText addressLine2; 
  19.  
  20.  
  21.     @Bind(R.id.zip_code) 
  22.  
  23.     protected EditText zipCode; 
  24.  
  25.  
  26.     @Bind(R.id.state) 
  27.  
  28.     protected TextView state; 
  29.  
  30.  
  31.     @Bind(R.id.state_spinner) 
  32.  
  33.     protected HintSpinner stateSpinner; 
  34.  
  35.  
  36.     @Bind(R.id.city) 
  37.  
  38.     protected EditText city; 
  39.  
  40.  
  41.     @Bind(R.id.frag_shipping_address_save_btn) 
  42.  
  43.     protected Button saveBtn; 
  44.  
  45.  
  46.     @Bind(R.id.agreement) 
  47.  
  48.     protected TextView agreement; 
  49.  
  50.  
  51.     @Bind(R.id.email) 
  52.  
  53.     protected EditText email; 
  54.  
  55.  
  56.     @Bind(R.id.password
  57.  
  58.     protected EditText password
  59.  
  60.  
  61.     @Bind(R.id.create_account_container) 
  62.  
  63.     protected LinearLayout accountContainer; 
  64.  
  65.  
  66.     @Bind(R.id.member_container) 
  67.  
  68.     protected LinearLayout memberContainer; 
  69.  
  70.  
  71.     @Bind(R.id.logged_in_title) 
  72.  
  73.     protected TextView loggedInTitle; 
  74.  
  75.  
  76.     @Bind(R.id.user_email) 
  77.  
  78.     protected TextView userEmail; 
  79.  
  80.  
  81.     @Bind(R.id.sign_out) 
  82.  
  83.     protected TextView signOut; 
  84.  
  85.  
  86.     @Bind(R.id.scrollview) 
  87.  
  88.     protected ScrollView scrollView; 
  89.  
  90.  
  91.     @Bind(R.id.dummy) 
  92.  
  93. protected EditText dummyView; 

上面那段代码读起来无聊吗?我敢打赌你一直滚动而没有阅读。在Kotlin,你并不需要任何这些东西。您可以通过其@id XML参数引用视图属性,这些属性将与XML文件中声明的名称相同。更多信息可以在官方文档中找到。

其他整洁的功能

1. 扩展功能和构建器

  1. items = StoreInfo().apply { storeItems = fetchItems() }.let { manager.process(it) } 
  2.  
  3. container.apply { 
  4.  
  5.   removeAllViews() 
  6.  
  7.   items.forEach { addView(ShopItemView(context).withData(it)) } 
  8.  
  9.  
  10.  
  11. fun ShopItemView.withData(item: StoreItem): ShopItemView { 
  12.  
  13.   title = item.title 
  14.  
  15.   image = item.image 
  16.  
  17.   Brand.findById(item.id)?.let { brandName = it.name } 
  18.  

apply、let和扩展功能可以轻松地用于创建简洁的构建器。

2. 为初学者快速破解

在最初的前几天,你经常被一个问题难倒:你不知道如何在Kotlin中写一个相当简单的Java表达式。这里有一个简单的诀窍,就是是在Java中编写一段代码,然后将其粘贴到Kotlin文件中。感谢JetBrains的工程师们,它会自动转换为Kotlin。

黑客的工作方式就像一个魔术!

3. 摆脱不必要的依赖

Kotlin替换了许多第三方库,如ButterKnife、Google Autovalue、Retrolambda、Lombok和一些RxJava代码。

总结

作为一个软件开发团队,我们面临的主要挑战是提供优秀的产品,并有效地完成工作。虽说开始用Kotlin有效的开发软件,需要你有函数式编程的背景,但是,投入精力去学习是值得的,能给你巨大的回报。我相信,Kotlin是常规Android开发的一个重大改进,能让我们及时提供错误更少的、更加优秀的应用程序。

更新:val实际上并不意味着“不可变的”,而是“只读”。有关详细信息,请参阅下面的参考文章。

参考文献

《Kotlin 参考手册》 https://kotlinlang.org/docs/reference/

《所以,你要成为一名函数式程序员》 https://medium.com/@cscalfani/so-you-want-to-be-a-functional-programmer-part-1-1f15e387e536#.8cmkitum2

《为什么函数式编程很重要》 https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf

《Java使用不可变对象编程的6大好处》 https://www.linkedin.com/pulse/20140528113353-16837833-6-benefits-of-programming-with-immutable-objects-in-java

《Anko DSL对Android XML-First》 http://maximomussini.com/posts/anko-vs-android-xml/

《将应用转换为纯Kotlin的经验教训》 https://medium.com/keepsafe-engineering/lessons-from-converting-an-app-to-100-kotlin-68984a05dcb6#.jkxs39qko

《结果:应用程序投票选举:99.8%无故障用户》 https://play.google.com/store/apps/details?id=app.voter.xyz&hl=en

《Kotlin:val不意味着不可变,而意味着只读》 https://artemzin.com/blog/kotlin-val-does-not-mean-immutable-it-just-means-readonly-yeah/

【编辑推荐】

  1. Android自定义View
  2. 再见 Android!Google推新系统明年将问世
  3. 报告显示Android设备比iPhone更可靠
  4. React Native中的Android原生模块
  5. Android开发万能圆角ImageView
【责任编辑:枯木 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

Java程序员面试宝典

本书是程序员面试宝典系列中的一册,也是上一本《程序员面试宝典》的姊妹书。本书对程序设计面试中Java常见的题型和常用解答技巧进行了介绍...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊