用Kotlin语言重新编写Plaid APP:经验教训(I)
原文标题:Converting Plaid to Kotlin: Lessons learned (Part 1)
原文链接:http://antonioleiva.com/plaid-kotlin-1/
原文作者:Antonio Leiva(http://antonioleiva.com/about/)
原文发布:2015-11-03
经常有人问我用Kotlin语言编写Android APP有哪些优点。可问题是我从来没有直接将用Java语言开发的Android APP转到Kotlin语言,所以这是一个很难回答的问题。而且没有将特性置于其相关环境中,仅仅解释大量抽象概念,不是一个谈论编程语言优势的最佳方法。
所以,在测试Plaid APP之后,其开发者Nick Butcher,惊叹APP的精美外观和页面过渡,我想更多的了解它。比用Kotlin语言重新编写APP更好的方法是什么?
我只转换HomeActivity,就对比代码,有显著地提升啊。当然你可以阅读代码得出自己的结论。我首先声明,无论是否会发生,我的主要目标不是将整个APP转换到Kotlin语言。由于转换整个APP工作相当大的,所以不能确定我是否有时间(或需要)这样做。
视图绑定
Nick决定用Butterknife接收视图,它是Java语言的出色解决方案。但是,Kotlin语言提供Kotlin Android Extensions,它可自动地将视图绑定到Activity。这样,我们就节省所有@Bind代码。
然而,我们还需要做一些Butterknife提供的其它事情,如:onClick和资源绑定。对于前者,在Kotlin语言中十分简单,并没有真正地添加太多的公式化代码。在onoCrate中,我们这样做:
fab.onClick { fabClick() }
这里我用Anko函数,但是用setOnclickListener会更简单一些。
至于恢复columns的值,仅在onCreate中是必须的,所以我将它移到声明那里。但是,类似的事情可以通过属性委托(property delegation)来实现:
private val columns by lazy { resources.getInteger(R.integer.num_columns) }
在Activity已经实例化和我们可以访问resource后,在调用属性时,lazy委托才赋值。
属性声明
在Java语言中,必须在Activity已经准备好后才能对field进行赋值。但是,如果我们不想处理不必要的null和不确定变量,那么在Kotlin语言中,在创建属性时就需要有值。所以在声明时就直接赋值是非常通用的做法。
再就是,许多这些属性有需要上下文的问题。所以在此,lazy是十分有用的:
private val dribbblePrefs by lazy { DribbblePrefs.get(ctx) }
private val designerNewsPrefs by lazy { DesignerNewsPrefs.get(ctx) }
当然,这些声明可以与我们需要的一样复杂。如:DataManager需要扩展类和重载方法:
private val dataManager by lazy {
object : DataManager(this, filtersAdapter) {
override fun onDataLoaded(data: MutableList<out PlaidItem>?) {
feedAdapter.addAndResort(data)
checkEmptyState()
}
}
}
这样一来,我们就可以只在声明部分见到对属性的声明,而不是在onCreate中间进行声明。加之,我们可以确保在使用它们时,它们不为null,所以就可以省去不必要的NullPointerException。
标准函数的使用
Kotlin语言标准库提供了一套很好、十分有用的函数。关于标准库,你可以阅读Cedric的文章第一部分和第二部分。
例如,我们有apply()函数,它是所为调用它的对象扩展函数运行的,其返回同一个对象。这方面的一个完整例子是展显(inflate)no_filters ViewStub。首先,说明为lazy,所以直到调用stub时才展显(inflate),其次,对这个展显(inflation)结果进行初始化:
private val noFilterEmptyText by lazy {
// create the no filters empty text
(stub_no_filters.inflate() as TextView).apply {
...
onClick { drawer.openDrawer(GravityCompat.END) }
}
}
如你所见,这个函数应用在展显(inflation)结果,apply()赋值给noFilterEmptyText,返回相同的对象。另一个很好的例子是在代码内部。SpannableStringBuilder就是如此:
text = SpannableStringBuilder(emptyText).apply {
// show an image of the filter icon
setSpan(ImageSpan(ctx, R.drawable.ic_filter_small, ImageSpan.ALIGN_BASELINE),
filterPlaceholderStart,
filterPlaceholderStart + 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
// make the alt method (swipe from right) less prominent and italic
setSpan(ForegroundColorSpan(
ContextCompat.getColor(ctx, R.color.text_secondary_light)),
altMethodStart,
emptyText.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
setSpan(StyleSpan(Typeface.ITALIC),
altMethodStart,
emptyText.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
apply()函数对我们的视图初始化也很有用。首先,明显地将代码拆分为代码块,更易阅读。其次,在类内部执行代码,所以可以使用对象的所有public方法,而无需在前面添加对象名称。
stories_grid.apply {
adapter = feedAdapter
val columns = resources.getInteger(R.integer.num_columns)
val gridManager = GridLayoutManager(ctx, columns).apply {
setSpanSizeLookup { pos -> if (pos == feedAdapter.dataItemCount) columns else 1 }
}
layoutManager = gridManager
addOnScrollListener { recycler, dx, dy ->
gridScrollY += dy
if (gridScrollY > 0 && toolbar.translationZ != -1f) {
toolbar.translationZ = -1f
} else if (gridScrollY == 0 && toolbar.translationZ != 0f) {
toolbar.translationZ = 0f
}
}
addOnScrollListener(object : InfiniteScrollListener(gridManager, dataManager) {
override fun onLoadMore() = dataManager.loadAllDataSources()
})
setHasFixedSize(true)
}
这里将adapter(适配器)、layout manager(布局管理器)和listener(侦听器)加到RecyclerView中。大家见过由Java语言的getter和setter方法所产生的综合属性的用法。而我们只layoutManager = gridManager,替代了setLayoutManager(gridManager)。
Lambda表达式
虽然无处不在使用函数,但是,还是有一些可以进行简化地方。可以通过用lambda表达式来代替在Java语言中需要创建对象才能进行的调用。非常好的例子是closeDrawerRunnable。用Java语言需要这样编写:
final Runnable closeDrawerRunnable = new Runnable() {
@Override
public void run() {
drawer.closeDrawer(GravityCompat.END);
}
};
...
drawer.postDelayed(closeDrawerRunnable, 2000);
};
而用Kotlin语言是这样:
val closeDrawerRunnable = { drawer.closeDrawer(GravityCompat.END) }
...
drawer.postDelayed(closeDrawerRunnable, 2000)
之前,我们见过onClick的例子,同样也可以帮助setOnApplyWindowInsetsListener:
drawer.setOnApplyWindowInsetsListener { v, insets ->
...
}
4 无效处理
Kotlin语言的另一个出色的特性是提升处理无效的方法。原本为此需要大量的代码。例如animateToolbar方法,为了确保TextView处理的是non-null,在Java语言中,需要这样做:
View t = toolbar.getChildAt(0);
if (t != null && t instanceof TextView) {
TextView title = (TextView) t;
...
}
而用Kotlin语言,可以这样:
val title = toolbar.getChildAt(0) as? TextView
title?.apply {
...
}
现在的优点是,apply内代码是一个扩展函数,所以不再需要编写title了。第一行试图转换任何返回值到TextView中。如果子类是空或者不是TextView,title就是null。第二行与if (title != null) title.apply{}相同。只有title不为null,apply()函数才执行。
在整个Activity代码中,可以找到很多地提升之处。尽管,由于这Activity要处理的事情太多了(甚至包括Retrofit客户端的实例化),这些改进并不是非常出色。但是,这是理解用Kotlin语言开发应用的良好开端。
Kotlin语言与Java语言对比数字
最后,我想要分享一些数字,当然由于有一些外部因素的影响,可能不太准确。但是还是可以帮助我们获得一点概念。Kotlin语言并非完美,编译时间就是需要改进的例子。
Kotlin | Java | Comparison(对比) | |
Line count(行数) | 576 | 702 | -22% |
Character count(字符数) | 24001 | 30589 | -27% |
Clean compilation(完全编译) | 1m 40s | 1m 5s | +67% |
Compilation after 1 line change(修改1行后编译) | 29s | 10s | +190% |
APK size(APK大小) | 4.7MB | 4.1MB | +14% |
Method count(方法数) | 41615 | 30129 | +38% |
Kotlin语言编译器现有的主要问题是,不能局部编译,即使修改一行代码,它也要对所有类进行重新编译。在将来,这些都会改进,但是这就是现状。
如你所见,Kotlin语言 + Anko库增加约11000个方法。Anko库十分庞大(有大于3000个方法),如果不使用它的核心部分,可以想象要自己创建多少函数啊。对比如下:
总结
采用Kotlin语言编程是非常愉快的。可通过较少的代码干更多的工作。如果整个APP都采用Kotlin语言开发,就可以摆脱更多的公式化代码,这样本例将会获得更多的提升。但这已经是帮助理解Kotlin语言在那些方面可以提升代码的可读性和减少代码的好方法。
在我持续转换该APP到Kotlin语言,我会发现更多的有趣事情可告诉大家。敬请关注新文章!同时,大家还可以持续通过我的书和其它文章学习Kotlin语言。当然,大家也可以阅读完整的HomeActivity代码。
用Kotlin语言重新编写Plaid APP:经验教训(I)的更多相关文章
- 用Kotlin语言重新编写Plaid APP:经验教训(II)
原文标题:Converting Plaid to Kotlin: Lessons learned (Part 2) 原文链接:http://antonioleiva.com/plaid-kotlin- ...
- 用React Native编写跨平台APP
用React Native编写跨平台APP React Native 是一个编写iOS与Android平台实时.原生组件渲染的应用程序的框架.它基于React,Facebook的JavaScript的 ...
- 释放Android的函数式能量(I):Kotlin语言的Lambda表达式
原文标题:Unleash functional power on Android (I): Kotlin lambdas 原文链接:http://antonioleiva.com/operator-o ...
- Kotlin 语言高级安卓开发入门
过去一年,使用 Kotlin 来为安卓开发的人越来越多.即使那些现在还没有使用这个语言的开发者,也会对这个语言的精髓产生共鸣,它给现在 Java 开发增加了简单并且强大的范式.Jake Wharton ...
- 用Xamarin和Visual Studio编写iOS App
一说开发 iOS app,你立马就会想到苹果的开发语言 Objective C/Swift 和 Xcode.但是,这并不是唯一的选择,我们完全可以使用别的语言和框架. 一种主流的替换方案是 Xamar ...
- 认识一下Kotlin语言,Android平台的Swift
今天在CSDN首页偶然看到一个贴子JetBrains正式公布Kotlin 1.0:JVM和Android上更好用的语言 看完后,感觉Kotlin语法非常简洁,有一系列动态语言的特点,Lambda表达式 ...
- Hybrid App经验解读 一
郑昀编纂 关键词:Hybrid,Zepto,Fastclick,Backbone,sui,SPA,pushState,跨域,CORS click 事件还是 tap 事件? Zepto 的 show/h ...
- Kotlin语言编程技巧集
空语句 Kotlin 语言中的空语句有 {} Unit when (x) { 1 -> ... 2 -> ... else -> {} // else -> Unit } Wh ...
- Kotlin语言学习笔记(2)
类(classes) // 类声明 class Invoice { } // 空的类 class Empty // 主体构造器(primary constructor) class Person co ...
随机推荐
- Linux-Rhel6 恢复误删文件
需要工具extundelete(可通过xshell上传下载到linux)知识点:ifconifig eth0 查看ipfdisk /dev/sda 管理磁盘m ...
- MapReduce
2016-12-21 16:53:49 mapred-default.xml mapreduce.input.fileinputformat.split.minsize 0 The minimum ...
- linux常用命令(3)mkdir命令
mkdir命令1 命令格式:mkdir [选项]目录名2 命令功能:通过 mkdir 命令可以实现在指定位置创建以 DirName(指定的文件名)命名的文件夹或目录.要创建文件夹或目录的用户必须对所创 ...
- 理解JavaScript中的“this”
对于javascript的初学者来说,一般对“this”关键字都感到非常迷惑.本文的目的旨在让你全面的了解“this”,理解在每一个情景下如何使用“this”,希望通过本文,可以帮助同学们不在害怕“t ...
- ASP.NET Core MVC 配置全局路由前缀
前言 大家好,今天给大家介绍一个 ASP.NET Core MVC 的一个新特性,给全局路由添加统一前缀.严格说其实不算是新特性,不过是Core MVC特有的. 应用背景 不知道大家在做 Web Ap ...
- mono3.2.3+Jexus5.5+openSuSE13.1的asp.net
读书的时候,我似乎有系统地学习过asp.net,但是基本已经还掉了...工作之后有做过一个内部用的网站,但也没有正式使用,的确只能算是个课程设计型的东西,不能做产品.后来工作需求是做Win8下的APP ...
- 感悟 GNU C 以及将 Vim 打造成 C/C++ 的半自动化 IDE
C 语言在 Linux 系统中的重要性自然是无与伦比.不可替代,所以我写 Linux 江湖系列不可能不提 C 语言.C 语言是我的启蒙语言,感谢 C 语言带领我进入了程序世界.虽然现在不靠它吃饭,但是 ...
- 利用Netty构建自定义协议的通信
在复杂的网络世界中,各种应用之间通信需要依赖各种各样的协议,比如:HTTP,Telnet,FTP,SMTP等等. 在开发过程中,有时候我们需要构建一些适应自己业务的应用层协议,Netty作为一个非常优 ...
- scope.$apply是干嘛的
开始用angular做项目的时候,一定碰到过$scope.$apply()方法,表面上看,这像是一个帮助你进行数据更新的方法,那么,它为何存在,我们又该如何使用它呢. JavaScript执行顺序 J ...
- 初始Bootstrap
使用示例 ①下载Bootstrap框架 网址:http://v3.bootcss.com/getting-started/#download ②解压得到三个文件 ③将文件添加进项目后,在页面中 ...