十五、迁移到 Kotlin

如果您有一个要迁移到 Kotlin 的遗留项目或现有的 Java 模块,迁移应该很容易。成功的人都想过这个。正如您所记得的,Kotlin 是可互操作的。正因为如此,有些模块不需要完全迁移;相反,它们可以包含在 Kotlin 项目中。由你决定。所以,让我们准备我们的迁移吧!

在本章中,我们将涵盖以下主题:

  • 准备迁移
  • 转换类
  • 重构和清理

准备迁移

正如我们所说的,我们需要决定是将我们的模块完全重写到 Kotlin 中,还是继续用 Kotlin 编写我们的代码,但用纯 Java 保留它的遗产。我们该怎么办?在这一章中,我们将一点点地演示每一个。

我们目前的项目,在这一点上,没有任何东西可以迁移。因此,我们将创建一些代码。如果您没有带有包结构的 Java 源目录,请创建它。现在,添加以下包:

  • activity
  • model

这些包相当于我们在 Kotlin 源代码中已经有的包。在activity包中,添加以下类:

  • MigrationActivity.java代码如下:
       package com.journaler.activity; 

       import android.os.Bundle; 
       import android.support.annotation.Nullable; 
       import android.support.v7.app.AppCompatActivity; 

       import com.journaler.R; 

       public class MigrationActivity extends AppCompatActivity { 

        @Override 
        protected void onCreate(@Nullable Bundle savedInstanceState) { 
          super.onCreate(savedInstanceState); 
          setContentView(R.layout.activity_main); 
        } 

        @Override 
        protected void onResume() { 
          super.onResume(); 
        } 
       }
  • MigrationActivity2.java:确保和MigrationActivity.java实现完全一样。我们只需要一些代码库来呈现和移植。 在安卓manifest文件中注册这两个活动,如下所示:
        <manifest xmlns:android=
        "http://schemas.android.com/apk/res/android" 
        package="com.journaler"> 
        ... 
        <application 
         ... 
        > 
        ... 
         <activity 
            android:name=".activity.MainActivity" 
            android:configChanges="orientation" 
            android:screenOrientation="portrait"> 
            <intent-filter> 
              <action android:name="android.intent.action.MAIN" /> 
              <category android:name=
              "android.intent.category.LAUNCHER" /> 
            </intent-filter> 
         </activity> 

         <activity 
            android:name=".activity.NoteActivity" 
            android:configChanges="orientation" 
            android:screenOrientation="portrait" /> 

         <activity 
            android:name=".activity.TodoActivity" 
            android:configChanges="orientation" 
            android:screenOrientation="portrait" /> 

         <activity 
            android:name=".activity.MigrationActivity" 
            android:configChanges="orientation" 
            android:screenOrientation="portrait" /> 

         <activity 
            android:name=".activity.MigrationActivity2" 
            android:configChanges="orientation" 
            android:screenOrientation="portrait" /> 
        </application> 

      </manifest> 

如您所见,Java 代码与 Kotlin 代码站在一起,没有任何问题。你的安卓项目可以两者兼得!现在,想一想,你真的需要做任何转换吗,或者你可以保留现有的 Java 内容吗?让我们在model包中添加类:

  • Dummy.java代码如下:
        package com.journaler.model; 

        public class Dummy { 

          private String title; 
          private String content; 

          public Dummy(String title) { 
            this.title = title; 
          } 

          public Dummy(String title, String content) { 
            this.title = title; 
            this.content = content; 
          } 

          public String getTitle() { 
            return title; 
          } 

          public void setTitle(String title) { 
            this.title = title; 
          } 

          public String getContent() { 
            return content; 
          } 

         public void setContent(String content) { 
           this.content = content; 
         } 

       } 
  • Dummy2.java代码如下:
        package com.journaler.model; 

        import android.os.Parcel; 
        import android.os.Parcelable; 

        public class Dummy2 implements Parcelable { 

          private int count; 
          private float result; 

          public Dummy2(int count) { 
            this.count = count; 
            this.result = count * 100; 
         } 

         public Dummy2(Parcel in) { 
           count = in.readInt(); 
           result = in.readFloat(); 
         } 

         public static final Creator<Dummy2>
         CREATOR = new Creator<Dummy2>() { 
           @Override 
           public Dummy2 createFromParcel(Parcel in) { 
             return new Dummy2(in); 
           } 

           @Override 
           public Dummy2[] newArray(int size) { 
             return new Dummy2[size]; 
           } 
         }; 

         @Override 
         public void writeToParcel(Parcel parcel, int i) { 
           parcel.writeInt(count); 
           parcel.writeFloat(result); 
         } 

         @Override 
         public int describeContents() { 
           return 0; 
         } 

         public int getCount() { 
           return count; 
         } 

         public float getResult() { 
           return result; 
         } 
       }

让我们再次检查项目的 Kotlin 部分是否看到这些类。在 Kotlin 源文件目录的根目录下创建一个新的.kt文件。姑且称之为kotlin_calls_java.kt:

    package com.journaler 

    import android.content.Context 
    import android.content.Intent 
    import com.journaler.activity.MigrationActivity 
    import com.journaler.model.Dummy2 

    fun kotlinCallsJava(ctx: Context) { 

      /** 
      * We access Java class and instantiate it. 
      */ 
      val dummy = Dummy2(10) 

      /** 
      * We use Android related Java code with no problems as well. 
      */ 
       val intent = Intent(ctx, MigrationActivity::class.java) 
       intent.putExtra("dummy", dummy) 
       ctx.startActivity(intent) 

    } 

如您所见,Kotlin 在使用 Java 代码时没有任何问题。因此,如果您仍然想继续迁移,您可以这样做。没问题。我们将在以下几节中介绍这一点。

危险信号

将庞大而复杂的 Java 类转换成 Kotlin 仍然是一个可以做的选择。无论如何,提供适当的单元或工具测试,以便在转换后重新测试这些类的功能。如果您的任何测试失败,请再次检查失败的原因。

要迁移的类可以通过以下两种方式迁移:

  • 自动转换
  • 手工重写

在庞大而复杂的类的情况下,这两种方法都会给我们带来某些缺点。全自动转换有时会给你带来不太好看的代码。所以,在你做了之后,你应该重新检查并重新格式化一些东西。第二种选择会花费你很多时间。

结论-您可以始终使用原始的 Java 代码。从你切换到 Kotlin 作为你的主要语言的那一刻起,你就可以用 Kotlin 写所有的新东西了。

更新依赖关系

如果你把安卓项目的 100%纯 Java 代码切换到 Kotlin,你必须从底层开始。这意味着您的第一次迁移工作将是更新您的依赖关系。您必须更改您的build.gradle配置,以便 Kotlin 被识别并且源代码路径可用。我们已经在第 1 章从安卓开始,在设置梯度部分解释了如何做到这一点;因此,如果您的项目中没有 Kotlin 相关的配置,那么您必须提供它。

让我们概括一下我们的梯度配置:

  • build.gradle根项目代表主build.gradle文件,如下图所示:
        buildscript { 
          repositories { 
            jcenter() 
            mavenCentral() 
          } 
          dependencies { 
            classpath 'com.android.tools.build:gradle:2.3.3' 
            classpath 'org.jetbrains.kotlin:kotlin-gradle-
            plugin:1.1.51' 
          } 
       } 

      repositories { 
       jcenter() 
       mavenCentral() 
      }
  • 主应用build.gradle解析应用的所有依赖关系,如下图所示:
        apply plugin: "com.android.application" 
        apply plugin: "kotlin-android" 
        apply plugin: "kotlin-android-extensions" 

        repositories { 
          maven { url "https://maven.google.com" } 
        } 

        android { 
         ... 
         sourceSets { 
          main.java.srcDirs += [ 
                'src/main/kotlin', 
                'src/common/kotlin', 
                'src/debug/kotlin', 
                'src/release/kotlin', 
                'src/staging/kotlin', 
                'src/preproduction/kotlin', 
                'src/debug/java', 
                'src/release/java', 
                'src/staging/java', 
                'src/preproduction/java', 
                'src/testDebug/java', 
                'src/testDebug/kotlin', 
                'src/androidTestDebug/java', 
                'src/androidTestDebug/kotlin' 
           ] 
          } 
          ... 
          } 
         ... 
        } 

        repositories { 
          jcenter() 
          mavenCentral() 
        } 

        dependencies { 
          compile "org.jetbrains.kotlin:kotlin-reflect:1.1.51" 
          compile "org.jetbrains.kotlin:kotlin-stdlib:1.1.51" 
           ... 
          compile "com.github.salomonbrys.kotson:kotson:2.3.0" 
            ... 

          compile "junit:junit:4.12" 
          testCompile "junit:junit:4.12" 

          testCompile "org.jetbrains.kotlin:kotlin-reflect:1.1.51" 
          testCompile "org.jetbrains.kotlin:kotlin-stdlib:1.1.51" 

          compile "org.jetbrains.kotlin:kotlin-test:1.1.51" 
          testCompile "org.jetbrains.kotlin:kotlin-test:1.1.51" 

          compile "org.jetbrains.kotlin:kotlin-test-junit:1.1.51" 
          testCompile "org.jetbrains.kotlin:kotlin-test-junit:1.1.51" 
          ... 
        }

这些都是你应该满足的与 Kotlin 相关的依赖。其中一个是 Kotson,为Gson库提供 Kotlin 绑定。

转换类

最后,我们将迁移我们的类。我们有两个自动选项。我们将两者都使用。定位MigrationActivity.java并打开。选择代码|将 Java 文件转换为Kotlin文件。转换需要几秒钟。现在,将文件从Java包拖放到Kotlin源包中。请注意以下源代码:

    package com.journaler.activity 

    import android.os.Bundle 
    import android.support.v7.app.AppCompatActivity 

    import com.journaler.R 

    class MigrationActivity : AppCompatActivity() { 

      override fun onCreate(savedInstanceState: Bundle?) { 
        super.onCreate(savedInstanceState) 
        setContentView(R.layout.activity_main) 
      } 

      override fun onResume() { 
        super.onResume() 
      } 

    } 

正如我们提到的,全自动转换并不能给出完美的代码。在下一节中,我们将进行重构和清理。第二种方法是将 Java 代码复制粘贴到Kotlin文件中。从MigrationActivity2复制所有源代码。创建一个同名的新 Kotlin 类,并粘贴代码。如果询问,请确认您希望执行自动转换。代码出现后,移除该类的 Java 版本。请注意,源代码与迁移后的MigrationActivity类相同。

DummyDummy2类重复这两种方法。您得到的类如下所示:

  • Dummy,第一Dummy类示例:
       package com.journaler.model 

       class Dummy { 

         var title: String? = null 
         var content: String? = null 

         constructor(title: String) { 
           this.title = title 
         } 

         constructor(title: String, content: String) { 
           this.title = title 
           this.content = content 
        } 

      } 
  • Dummy2,第二Dummy类示例:
        package com.journaler.model 

        import android.os.Parcel 
        import android.os.Parcelable 

        class Dummy2 : Parcelable { 

          var count: Int = 0 
          private set 
          var result: Float = 0.toFloat() 
          private set 

          constructor(count: Int) { 
            this.count = count 
            this.result = (count * 100).toFloat() 
          } 

          constructor(`in`: Parcel) { 
            count = `in`.readInt() 
            result = `in`.readFloat() 
          } 

          override fun writeToParcel(parcel: Parcel, i: Int) { 
            parcel.writeInt(count) 
            parcel.writeFloat(result) 
          } 

          override fun describeContents(): Int { 
            return 0 
          } 

         companion object { 

           val CREATOR: Parcelable.Creator<Dummy2>
           = object : Parcelable.Creator<Dummy2> { 
              override fun createFromParcel(`in`: Parcel): Dummy2 { 
                return Dummy2(`in`) 
            } 

           override fun newArray(size: Int): Array<Dummy2> { 
              return arrayOfNulls(size) 
            } 
          } 
        } 

    } 

Dummy2类在转换方面有问题。在这种情况下,你必须自己解决。修复源代码。问题发生在下面一行:

    override fun newArray(size: Int): Array<Dummy2> { ... 

通过从Array<Dummy2> int Array<Dummy2?>切换类型进行修复,如下所示:

    override fun newArsray(size: Int): Array<Dummy2?> { ... 

简单!

这正是您在进行迁移时可能面临的挑战!值得注意的是,在DummyDummy2类中,我们都通过切换到 Kotlin 显著减少了代码库。由于不再有 Java 实现,我们可以进行重构和清理。

重构和清理

为了在转换后得到最好的代码,我们必须执行重构和清理。我们将调整我们的代码库,以符合 Kotlin 标准和习惯用法。为此,你必须完整地阅读它。只有这样做了,我们才能认为我们的迁移完成了!

打开你的课程,阅读代码。还有很大的改进空间!做了一些工作后,你应该得到这样的东西:

MigrationActivity代码如下:

    ... 
    override fun onResume() = super.onResume() 
    ... 

可以看到MigrationActivity(和MigrationActivity2)没有太多的工作。两个班都很小。像DummyDummy2这样的班级预计会付出更大的努力:

  • Dummy类代码如下:
        package com.journaler.model 

        class Dummy( 
          var title: String, 
          var content: String 
          ) { 

            constructor(title: String) : this(title, "") { 
            this.title = title 
           } 

       } 
  • Dummy2类代码如下:
        package com.journaler.model 

        import android.os.Parcel 
        import android.os.Parcelable 

        class Dummy2( 
          private var count: Int 
        ) : Parcelable { 

          companion object { 
            val CREATOR: Parcelable.Creator<Dummy2> 
            = object : Parcelable.Creator<Dummy2> { 
              override fun createFromParcel(`in`: Parcel): 
              Dummy2 = Dummy2(`in`) 
              override fun newArray(size: Int): Array<Dummy2?> =
              arrayOfNulls(size) 
            }    
          } 

         private var result: Float = (count * 100).toFloat() 

         constructor(`in`: Parcel) : this(`in`.readInt()) 

         override fun writeToParcel(parcel: Parcel, i: Int) { 
           parcel.writeInt(count) 
         } 

         override fun describeContents() = 0 

        } 

与转换后的第一个 Kotlin 版本相比,这两个类版本在重构后有了显著的改进。尝试将当前版本与我们拥有的原始 Java 代码进行比较。你怎么想呢?

摘要

在这一章中,我们发现了迁移到 Kotlin 编程语言的秘密。我们演示了技术,并就如何以及何时进行迁移给出了建议。幸运的是,对我们来说,这似乎毕竟不是什么困难的事情!下一章将是我们的最后一章,所以,正如你已经知道的,是时候向世界发布我们的应用了!