zoukankan      html  css  js  c++  java
  • 用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中,我们这样做:

    1 fab.onClick { fabClick() }

     

    这里我用Anko函数,但是用setOnclickListener会更简单一些。

     

    至于恢复columns的值,仅在onCreate中是必须的,所以我将它移到声明那里。但是,类似的事情可以通过属性委托(property delegation)来实现:

    1 private val columns by lazy { resources.getInteger(R.integer.num_columns) }

    在Activity已经实例化和我们可以访问resource后,在调用属性时,lazy委托才赋值。

    属性声明

    在Java语言中,必须在Activity已经准备好后才能对field进行赋值。但是,如果我们不想处理不必要的null和不确定变量,那么在Kotlin语言中,在创建属性时就需要有值。所以在声明时就直接赋值是非常通用的做法。

    再就是,许多这些属性有需要上下文的问题。所以在此,lazy是十分有用的:

    1 private val dribbblePrefs by lazy { DribbblePrefs.get(ctx) }
    2 private val designerNewsPrefs by lazy { DesignerNewsPrefs.get(ctx) }

     

    当然,这些声明可以与我们需要的一样复杂。如:DataManager需要扩展类和重载方法:

    1 private val dataManager by lazy {
    2     object : DataManager(this, filtersAdapter) {
    3         override fun onDataLoaded(data: MutableList<out PlaidItem>?) {
    4             feedAdapter.addAndResort(data)
    5             checkEmptyState()
    6         }
    7     }
    8 }

     

    这样一来,我们就可以只在声明部分见到对属性的声明,而不是在onCreate中间进行声明。加之,我们可以确保在使用它们时,它们不为null,所以就可以省去不必要的NullPointerException。

    标准函数的使用

    Kotlin语言标准库提供了一套很好、十分有用的函数。关于标准库,你可以阅读Cedric的文章第一部分第二部分

    例如,我们有apply()函数,它是所为调用它的对象扩展函数运行的,其返回同一个对象。这方面的一个完整例子是展显(inflate)no_filters ViewStub。首先,说明为lazy,所以直到调用stub时才展显(inflate),其次,对这个展显(inflation)结果进行初始化:

    1 private val noFilterEmptyText by lazy {
    2     // create the no filters empty text
    3     (stub_no_filters.inflate() as TextView).apply {
    4         ...
    5         onClick { drawer.openDrawer(GravityCompat.END) }
    6     }
    7 }

     

    如你所见,这个函数应用在展显(inflation)结果,apply()赋值给noFilterEmptyText,返回相同的对象。另一个很好的例子是在代码内部。SpannableStringBuilder就是如此:

     1 text = SpannableStringBuilder(emptyText).apply {
     2     // show an image of the filter icon
     3     setSpan(ImageSpan(ctx, R.drawable.ic_filter_small, ImageSpan.ALIGN_BASELINE),
     4             filterPlaceholderStart,
     5             filterPlaceholderStart + 1,
     6             Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
     7     // make the alt method (swipe from right) less prominent and italic
     8     setSpan(ForegroundColorSpan(
     9             ContextCompat.getColor(ctx, R.color.text_secondary_light)),
    10             altMethodStart,
    11             emptyText.length,
    12             Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
    13     setSpan(StyleSpan(Typeface.ITALIC),
    14             altMethodStart,
    15             emptyText.length,
    16             Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    17 }

     

    apply()函数对我们的视图初始化也很有用。首先,明显地将代码拆分为代码块,更易阅读。其次,在类内部执行代码,所以可以使用对象的所有public方法,而无需在前面添加对象名称。

     1 stories_grid.apply {
     2     adapter = feedAdapter
     3     val columns = resources.getInteger(R.integer.num_columns)
     4     val gridManager = GridLayoutManager(ctx, columns).apply {
     5         setSpanSizeLookup { pos -> if (pos == feedAdapter.dataItemCount) columns else 1 }
     6     }
     7     layoutManager = gridManager
     8     addOnScrollListener { recycler, dx, dy ->
     9         gridScrollY += dy
    10         if (gridScrollY > 0 && toolbar.translationZ != -1f) {
    11             toolbar.translationZ = -1f
    12         } else if (gridScrollY == 0 && toolbar.translationZ != 0f) {
    13             toolbar.translationZ = 0f
    14         }
    15     }
    16     addOnScrollListener(object : InfiniteScrollListener(gridManager, dataManager) {
    17         override fun onLoadMore() = dataManager.loadAllDataSources()
    18     })
    19     setHasFixedSize(true)
    20 }

    这里将adapter(适配器)、layout manager(布局管理器)和listener(侦听器)加到RecyclerView中。大家见过由Java语言的getter和setter方法所产生的综合属性的用法。而我们只layoutManager = gridManager,替代了setLayoutManager(gridManager)。

    Lambda表达式

    虽然无处不在使用函数,但是,还是有一些可以进行简化地方。可以通过用lambda表达式来代替在Java语言中需要创建对象才能进行的调用。非常好的例子是closeDrawerRunnable。用Java语言需要这样编写:

    1 final Runnable closeDrawerRunnable = new Runnable() {
    2     @Override
    3     public void run() {
    4         drawer.closeDrawer(GravityCompat.END);
    5     }
    6 };
    7     ...
    8 drawer.postDelayed(closeDrawerRunnable, 2000);
    9 };

     

    而用Kotlin语言是这样:

    1 val closeDrawerRunnable = { drawer.closeDrawer(GravityCompat.END) }
    2 ...
    3 drawer.postDelayed(closeDrawerRunnable, 2000)

     

    之前,我们见过onClick的例子,同样也可以帮助setOnApplyWindowInsetsListener:

    1 drawer.setOnApplyWindowInsetsListener { v, insets ->
    2     ...
    3 }

    4 无效处理

    Kotlin语言的另一个出色的特性是提升处理无效的方法。原本为此需要大量的代码。例如animateToolbar方法,为了确保TextView处理的是non-null,在Java语言中,需要这样做:

    1 View t = toolbar.getChildAt(0);
    2 if (t != null && t instanceof TextView) {
    3     TextView title = (TextView) t;
    4     ...
    5 }

     

    而用Kotlin语言,可以这样:

    1 val title = toolbar.getChildAt(0) as? TextView
    2 title?.apply {
    3     ...
    4 }

    现在的优点是,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代码。

  • 相关阅读:
    结构化系统开发和面向对象开发方法
    十五周总结
    第十四周总结
    第十三周总结
    分答
    第十周总结
    DFD
    判定表
    第八周总结
    开发方法对比
  • 原文地址:https://www.cnblogs.com/figozhg/p/5041855.html
Copyright © 2011-2022 走看看