原文标题: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 sizeAPK大小) 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)的更多相关文章

  1. 用Kotlin语言重新编写Plaid APP:经验教训(II)

    原文标题:Converting Plaid to Kotlin: Lessons learned (Part 2) 原文链接:http://antonioleiva.com/plaid-kotlin- ...

  2. 用React Native编写跨平台APP

    用React Native编写跨平台APP React Native 是一个编写iOS与Android平台实时.原生组件渲染的应用程序的框架.它基于React,Facebook的JavaScript的 ...

  3. 释放Android的函数式能量(I):Kotlin语言的Lambda表达式

    原文标题:Unleash functional power on Android (I): Kotlin lambdas 原文链接:http://antonioleiva.com/operator-o ...

  4. Kotlin 语言高级安卓开发入门

    过去一年,使用 Kotlin 来为安卓开发的人越来越多.即使那些现在还没有使用这个语言的开发者,也会对这个语言的精髓产生共鸣,它给现在 Java 开发增加了简单并且强大的范式.Jake Wharton ...

  5. 用Xamarin和Visual Studio编写iOS App

    一说开发 iOS app,你立马就会想到苹果的开发语言 Objective C/Swift 和 Xcode.但是,这并不是唯一的选择,我们完全可以使用别的语言和框架. 一种主流的替换方案是 Xamar ...

  6. 认识一下Kotlin语言,Android平台的Swift

    今天在CSDN首页偶然看到一个贴子JetBrains正式公布Kotlin 1.0:JVM和Android上更好用的语言 看完后,感觉Kotlin语法非常简洁,有一系列动态语言的特点,Lambda表达式 ...

  7. Hybrid App经验解读 一

    郑昀编纂 关键词:Hybrid,Zepto,Fastclick,Backbone,sui,SPA,pushState,跨域,CORS click 事件还是 tap 事件? Zepto 的 show/h ...

  8. Kotlin语言编程技巧集

    空语句 Kotlin 语言中的空语句有 {} Unit when (x) { 1 -> ... 2 -> ... else -> {} // else -> Unit } Wh ...

  9. Kotlin语言学习笔记(2)

    类(classes) // 类声明 class Invoice { } // 空的类 class Empty // 主体构造器(primary constructor) class Person co ...

随机推荐

  1. Ognl表达式基本原理和使用方法

    Ognl表达式基本原理和使用方法 1.Ognl表达式语言 1.1.概述 OGNL表达式 OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,他是一个 ...

  2. Hello Jexus

    一.关于 CentOS CentOS(Community Enterprise Operating System)是Linux发行版之一,它是来自于Red Hat Enterprise Linux依照 ...

  3. ASP.NET MVC 视图(三)

    ASP.NET MVC 视图(三) 前言 上篇对于Razor视图引擎和视图的类型做了大概的讲解,想必大家对视图的本身也有所了解,本篇将利用IoC框架对视图的实现进行依赖注入,在此过程过会让大家更了解的 ...

  4. Java在DOS命令下的运行及其API文档制作过程

    该文档主要描述java程序在DOS命令下的运行,以及一些常用的命令 常用DOS命令: d: 回车 盘符切换 dir(directory):列出当前目录下的文件以及文件夹 md (make direct ...

  5. 详解mmseg

    本文先介绍下mmseg的概念和算法,再说下mmseg4j-solor的3个分词器用法 1.mmseg概念 mmseg是用于中文切词的算法,即Maximum Matching Segment,最大匹配分 ...

  6. ASP.NET MVC5+EF6+EasyUI 后台管理系统(52)-美化EasyUI皮肤和图标

    系列目录 我很久以前就想更新系统的皮肤功能,Easyui 自带的皮肤已经无法满足客户的审美. 皮肤颜色来源于AdminLTE系统.我的颜色全部都这里取的.,所以一共取了11个颜色.1个皮肤=2个ban ...

  7. ASP.NET MVC5+EF6+EasyUI 后台管理系统(60)-系统总结

    系列目录 前言: 起初写这个框架的时候,可以说在当时来说并不是很流行的设计模式,那是在2012年,面向对象的编程大家都很熟悉, 但是“注入.控制反转(DI,IOC,依赖注入).AOP切面编程”新兴名词 ...

  8. ASP.NET Core中的依赖注入(3): 服务的注册与提供

    在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象.ASP.NET Core ...

  9. eclipse打开文件所在目录

    设置 添加扩展工具,添加步骤如下: Run-->External Tools-->External Tools Configurations... new 一个 programlocati ...

  10. 学习总结 之 WebApi服务监控 log4net记录监控日志

    在请求WebApi 的时候,我们更想知道在请求数据的时候,调用了哪个接口传了什么参数过来,调用这个Action花了多少时间,有没有人恶意请求.我们可以通过记录日志,对Action进行优化,可以通过日志 ...