Kotlin京东业务实战 | 强大又简洁的JVM语言

作 者 简 介

赵振华——京东商城资深工程师

9年以上开发经验,熟悉主流移动开发框架,热衷于探索新技术,曾负责过店铺、JDReact架构等项目的开发工作,目前专注于京东App领券中心频道开发,以及移动端技术难点攻克和新技术调研实施。

2011年JetBrains推出Kotlin项目,这是一个面向JVM的新语言,主要是解决Java之前被诟病已久的问题,而且积极借鉴了 Scala、Ruby等新语言在开发效率和简洁性上的优势。

2017年Google开始力推Kotlin,在I/O大会上谷歌宣布Kotlin正式成为Android官方支持开发语言。

2018年4月18号《JAVA编程思想》作者Bruce大神给6967名京东兄弟分享了kotlin语言,认为kotlin为未来语言发展的趋势之一。

Kotlin已经越走越近,抱着“为了让移动开发更简单”的理念,让Kotlin在京东业务中落地。

>>>> Kotlin简介

Kotlin是一门运行在JVM之上的语言,由Jetbrains创建。Kotlin是一门非常简单的语言,其主要目标之一就是提供强大语言的同时又保持简单且精简的语法。

>>>> 为什么选择 Kotlin

  • 简洁:这一点对于Android来说非常重要。项目所需要的库应该尽可能的小。Android对于方法数量有严格的限制,Kotlin依赖库只额外增加了大约6000个方法。同时用Kotlin开发的项目,方法数量也会大幅减少。
  • 安全:Java最大的一个问题就是null。如果没有对变量或是参数进行null判断,那么程序当中就有可能抛出大量的NullPointerException,然而在编码时这些又是难以检测到的。Kotlin使用了显式的null,这会强制我们在必要时进行null检查。
  • 互操作:Kotlin可与Java语言无缝通信。这意味着我们可以在Kotlin代码中使用任何已有的Java库;因此,即便这门语言还很年轻,但却已经可以使用成百上千的库了。除此之外,Kotlin代码还可以为Java代码所用,这意味着我们可以使用这两种语言来构建软件。你可以使用Kotlin开发新特性,同时使用Java实现的其他部分代码。
  • 工具友好:可用任何 Java IDE 或者使用命令行构建,包括常用的IntelliJ IDEA,Android Studio,Eclipse,命令行等。

对比其他语言,Kotlin语法和Java很像,非常容易上手,推荐以循序渐进的方式开发项目;由于项目中允许同时存在Java和Kotlin代码文件,并且允许Java与Kotlin互调,使得开发者可以很方便的在已有项目中引入Kotlin;新模块用Kotlin,稳定模块勿需用Kotlin重写。

>>>> Kotlin的一些特性

Kotlin拥有大量非常打动人心的特性,这里无法一一进行介绍,不过我们来看一下其中最为重要的一些。

>>>> Null安全

如前所述,Kotlin是null安全的。如果一个类型可能为null,那么我们就需要在类型后面加上一个?。这样,每次在使用该类型的变量时,我们都需要进行null检查。比如说,如下代码将无法编译通过:

var artist: Artist? = null?
artist.print()

第2行会显示一个错误,因为没有对变量进行null检查。

Null曾经被戏称为“十亿美金的错误”,Null虽然好用,但是导致很多错误的元凶往往都是它。在Kotlin中,编译器是可以识别你的引用是否是null,进而提醒你。默认kotlin中所有的对象都是不为Null的。

>>>> 数据类

在Java中,如果想要创建数据类或是POJO类(只保存了一些状态的类),我们需要创建一个拥有大量字段、getters与setters的类,也许还要提供toString与equals方法:

public class Artist {
    private long id;
    private String name;
    private String url;
    private String mbid;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getMbid() {
        return mbid;
    }

    public void setMbid(String mbid) {
        this.mbid = mbid;
    }

    @Override public String toString() {
        return "Artist{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", url='" + url + '\'' +
                ", mbid='" + mbid + '\'' +
                '}';
    }
}

在Kotlin中,上述代码可以写成下面这样:

data class Artist (
    var id: Long,
    var name: String,
    var url: String,
    var mbid: String)

使用一行代码创建一个包含 getters、 setters、 equals()、 hashCode()、 toString() 以及 copy() 的 POJO。

>>>> 互操作

Kotlin提供了一些非常棒的互操作特性,这对于Android开发帮助非常大。其中之一就是拥有单个方法的接口与lambda表达式之间的映射。例如下面这个单击监听器:

viewHolder.mCouponItemBottom?.setOnClickListener(
    object : View.OnClickListener {
      override fun onClick(v: View) {
        Toast.makeText(mContext, "Click", Toast.LENGTH_LONG).show()
      }
    }
)

可以写成这样:

viewHolder.mCouponItemBottom?.setOnClickListener {
    Toast.makeText(mContext, "Click", Toast.LENGTH_LONG).show()
}

>>>> Lambda表达式

Lambda表达式会极大程度的精简代码,借助于Lambda表达式,我们可以做到之前无法实现或是实现起来非常麻烦的事情。借助于Lambda表达式,我们可以以一种更加函数式的方式来思考问题。Lambda表达式其实就是一种指定类型,并且该类型定义了一个函数的方式。

lambda的标准形式基本声明满足三个条件:含有实际参数,含有函数体,以上内部必须被包含在花括号内部。

val sum = { x: Int, y: Int -> x + y }

>>>> Kotlin编译分析

Kotlin代码比Java的简洁,更易于编写维护,所以我们认为转换是值得的。 但很多开发者都担心Kotlin编译可能没有Java快,影响开发效率,反而得不偿失。

>>>> 过程分析

上图是Java编译器的编译过程,Kotlin和Java的编译过程是很相似的,区别在于Kotlin与Java相比重要的细节在编译后端(目标代码生成)环节。

Kotlin编译器在目标代码生成环节做了很多类似于Java封装的事情,比如自动生成Getter/Setter代码的生成、Companion转变成静态类、修改类属性为final不可继承(open修饰即可继承)等等工作。

Kotlin将我们本来在代码层做的一些封装工作转移到了编译后端阶段,使得语言更加简洁。

>>>> 速度分析

在相同gradle版本,相同设备的情况下,通过重复执行gradle指令,对几个不同的编译场景进行了基准测试,对比Kotlin和Java的编译时间。发现Java在clean构建比Kotlin 快10-15%,增量编译时Kotlin比Java编译速度略快。对于大多数开发人员来说,更常见的情况是增量编译,Kotlin对增量编译进行了大量改进,保证了编译速度。

由此可见,开发人员不需要担心Kotlin的编译时间,Kotlin的编译速度和Java一样快。

>>>> 京东业务实现

>>>> 环境配置

1、安装 Kotlin 插件

Android Studio3.0(preview)版本开始将内置安装 Kotlin插件。如果你正在使用的是早期版本, 需要通过File | Settings | Plugins | Install JetBrains plugin…搜索并安装Kotlin插件。

2、Jdlib工程中配置 Kotlin

新增 apply plugin: ‘kotlin-android’ 及其依赖。

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
dependencies {
        classpath "org.jetbrains.kotlin:kotlin-android-extensions:1.2.41"
    }
dependencies {
        ...
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }

3、插件工程中配置 Kotlin

配置方式与Jdlib相同,注意需要将compile修改为provided,防止类库重复引用。如果不配置,插件代码不能打到apk中,调用时报ClassNotFoundException异常。

provided "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

4、添加混淆配置

如果调试Kotlin代码,总是报ClassNotFoundException异常,或者NoSuchMethod异常,你要考虑加入混淆配置。缺失混淆配置,Kotlin类库代码会被优化掉,编译正常,但无法运行。

# Kotlin
-dontwarn kotlin.**
-keep class kotlin.** {*;}
-keepclassmembers class **$WhenMappings {
    <fields>;
    <methods>;
}

>>>> 业务实现

1、业务重写

将整个模块改为Kotlin语言实现,修复语法方面的编译错误。Android Studio提供将Java转为Kotlin的插件,可以转换大部分代码。

常见编译问题:

a、代码编译出错,定义变量时,没有赋初始值,变量可以为空,需要加?修饰符

var mCouponItemLayout: RelativeLayout
var mCouponItemTop: LinearLayout
var mCouponIcon: SimpleDraweeView
var mCouponName: TextView
var mCouponItemTopTv1: TextView
var mCouponItemTopTv2: TextView
var mCouponItemMiddle: LinearLayout

以下为修改后的代码:

var mCouponItemLayout: RelativeLayout? = null
var mCouponItemTop: LinearLayout? = null
var mCouponIcon: SimpleDraweeView? = null
var mCouponName: TextView? = null
var mCouponItemTopTv1: TextView? = null
var mCouponItemTopTv2: TextView? = null
var mCouponItemMiddle: LinearLayout? = null

b、代码编译出错,变量可以为空时,使用变量需要加?修饰符,自动判断是否为空,防止出现空指针异常

viewHolder.mCouponItemValueDiscount.text = entity.quota
viewHolder.mCouponItemValuePrice.text = entity.minOrderAmount

以下为修改后的代码:

viewHolder.mCouponItemValueDiscount?.text = entity.quota
viewHolder.mCouponItemValuePrice?.text = entity.minOrderAmount

c、代码编译出错,由于FontsUtil的参数有@NonNull标签,需要保证只有在确保变量不是null的情况下才能这么调用,否则它会抛出异常

FontsUtil.changeTextFont(viewHolder.mCouponItemMoneyTag, FontsUtil.MULTI_BOLD)
FontsUtil.changeTextFont(viewHolder.mCouponItemValueDiscount, FontsUtil.MULTI_REGULAR)

以下为修改后的代码:

FontsUtil.changeTextFont(viewHolder.mCouponItemMoneyTag!!, FontsUtil.MULTI_BOLD)
FontsUtil.changeTextFont(viewHolder.mCouponItemValueDiscount!!, FontsUtil.MULTI_REGULAR)

2、运行调试

编译成功后进行代码调试,修改运行时异常问题,可以正常使用debug工具,Kotlin模块与Java模块互相直接调用,显示效果和交互效果与Java模块没有差别。

Kotlin语言提供了类型的自动判断,自动拆装箱,字符串拼接,lambda表达式,空判断等一系列功能,功能精简了很多,语法与js有相似,同时去掉了findViewById(),省去了很多if try等语句,业务代码量减少很多。

统计业务模块的Java实现和Kotlin实现的代码量,不包含xml布局文件,代码量减少超过20%;代码减少最多的为pojo类,减少比例甚至超过80%;业务逻辑代码减少10%,同时代码会更加简洁直观,有助于提高代码可维护性。

3、ABTest上线

由于业务量级比较大,为防止新技术对业务稳定性产生影响,计划通过Java实现代码,Kotlin 实现代码两套代码并存,使用ABTest方式逐渐放量,待稳定后切到Kotlin

Kotlin在设计上避免了常见的编程错误,从而减少了应用程序崩溃和系统故障。此外,由于Kotlin 是快速失败机制,可以立即报告任何可能导致失败的问题。因此 Kotlin 在降低应用崩溃率上有很大作用,非常值得期待!

>>>> 常见问题

  • 环境配置出错,出现无法编译或编译正常但apk中无kotlin代码,运行时报kotlin代码找不到; 解决方法:在插件代码中配置kotlin环境,在gradle中增加classpath,compile等,如果不配置环境,kotlin代码不会做编译;
  • 正常出包后,无法运行,调试kotlin代码,总是报Intrinsics的ClassNotFoundException异常,通过反编译分析apk,发现丢失部分代码; 解决方法:在jdlib代码中,加入遗漏的kotlin-android-extensions依赖,同时修改proguard,解决ClassNotFoundException异常;
  • 运行过程中报checkExpressionValueIsNotNull NoSuchMethod异常,分析apk与kotlin源码,发现kotlin中Intrinsics类部分方法打包后丢失; 解决方法:在主站代码中配置属性方法混淆,保证kotlin类中属性方法全部不做混淆优化;
  • 为了保证插件包大小,并且与主站代码不重复,引用类库时使用provided; 解决方法:使用provided引入类库,在编译时使用,最终不会被编译到apk;后期将配置环境放到aura中更方便。

>>>> 使用案例

Pinterest

Pinterest 已成功将 Kotlin 引入了他们的应用程序,每个月有 1 亿 5 千万人使用。

Gradle

Gradle 正引入 Kotlin 作为编写脚本的语言。

Evernote

Evernote 最近 将 Kotlin 整合到了他们的 Android 客户端。

Uber

Uber 团队使用 Kotlin 来构建内部工具。

Corda

Corda是一个开源分布式分类账号平台,由各大银行提供支持,完全由 Kotlin 构建。

Coursera

Coursera Android 应用程序部分用 Kotlin 编写。

Pivotal

Spring 采用 Kotlin 的语言特性来提供更简洁的 API

Atlassian

Trello Android应用程序中的所有新代码都用 Kotlin

>>>> 反馈与建议

  • 邮箱:zhaozhenhua8@jd.com

>>>> 参考资料

  • 对话《JAVA编程思想》作者Bruce 地址: http://tech.jd.com/course/toDetail?courseId=710
  • kotlin中文网站 地址:https://www.kotlincn.net

原文发布于微信公众号 - 京东技术(jingdongjishu)

原文发表时间:2018-08-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏极客编程

用go语言创建区块链

本文你将用Go语言创建自己的区块链、理解哈希函数是如何保持区块链的完整性、掌握如何创造并添加新的块、实现多个节点通过竞争生成块、通过浏览器来查看整个链、了解所有...

1122
来自专栏Hongten

Java中四种XML解析技术

在平时工作中,难免会遇到把 XML 作为数据存储格式。面对目前种类繁多的解决方案,哪个最适合我们呢?在这篇文章中,我对这四种主流方案做一个不完全评测,仅仅针对遍...

1702
来自专栏生信宝典

生信宝典之傻瓜式 (一) 如何提取指定位置的基因组序列

为了一件小事也不得不写程序时,你是否会觉得心里很烦?本栏目旨在分享生物信息分析中的一些小技巧,纯傻瓜式操作,助你事半功倍,心情倍儿爽。 想要提取拟南芥1号染色体...

2428
来自专栏NetCore

[原创]Fluent NHibernate之旅二--Entity Mapping

接着上一篇,今天我们说说ORM中的Mapping。如果你要体验NHibernate的强大,首先你就要学会配置,包括SessionFactory和Mapping的...

2399
来自专栏技术与生活

设计模式-命令模式

Client:确定具体的命令和接受者; Command:抽象命令接口,一般是接口类或者抽象类 ConcreteCommand:具体的命令执行,调用接受者 Inv...

1315

当Vert.x符合Reactive eXtensions(Vert.x简介的第5部分)

这篇文章是我介绍Eclipse Vert.x系列的第五篇文章。在上一篇文章中,我们看到了Vert.x如何与数据库交互。我们使用Future对象来驯服Vert.x...

2082
来自专栏Flutter入门到实战

最全的BAT大厂面试题整理

版权声明:本文为博主原创文章,未经博主允许不得转载。https://www.jianshu.com/p/c70989bd5f29

1852
来自专栏Android 技术栈

java 常用十种设计模式示例归纳 | 已打包请带走

一个Demo,集合常用的十种设计模式,每个模式使用易被人们接受的案例讲述,按模式分包,使用设计模式前后对比,界面显示定义讲解,让你更深刻的了解每种设计模式。 ...

2.6K2
来自专栏青蛙要fly的专栏

Android技能树 — Android存储路径及IO操作小结

这次是讲Android存储路径及IO的基本操作。因为我们在开发的时候会经常这种方便的需求。这篇文章的内容我写的可能很少,都没有细写。别吐槽。o( ̄︶ ̄)o

902
来自专栏Java技术栈

史上最全 BAT 大厂面试题整理!(速度收藏)

3814

扫码关注云+社区

领取腾讯云代金券