Kotlin开发Android应用

Posted on | 1451 words | ~3 mins
Android Kotlin Android Studio

最近在尝试用Kotlin开发Android应用。作为一个Android+Kotlin双小白,开始阶段踩了不少坑,在这里做个记录。

小朋友上了小学,体育课增加了跳绳考核。为了达标,在家里经常练习一分钟跳绳。每次都要大人用计时器加人肉计数辅助练习。想想一边盯着手机上的计时器,一边盯着小孩动作计数,就觉得很麻烦。数多了,数少了,是家常便饭。我经常被小朋友吐槽跟不上他的节奏,总是数少了。遂决定开发一个根据声音频率计数且自动倒计时一分钟的Android应用解救家里的大人。

赶时髦用了Kotlin开发Android。学一遍Kotlin语法自不必说。学的过程中,深深感受到Kotlin的野心。Kotlin囊括了所有现代编程语言的特征,语法和语法糖及其丰富。但这也导致,从C++,python,C#/Java一路走来的程序员,感觉太过“啰嗦”。为啥就不引入静态类,非要费劲巴拉的引入Companion类,解决的问题还不是内嵌静态类的那些事情。object真的有必要吗?实现一个单例类能死吗?Java的lambda没有object关键词,不也过得很好?各种内心OS满满的。

今天先撇开槽点,单说说Kotlin+Android Studio开发新手坑。

ButterKnife

ButterKnife使用annotation的方案将layout中的view与Activity中类成员变量bind到一起。网上有大把的使用教程,这里就不从头介绍了。大体上,用起来样子如下:

 1@BindView(R.id.mStopWatchTextView)
 2lateinit var mStopWatchTextView: TextView
 3
 4@BindView(R.id.mRecordButton)
 5lateinit var mRecordButton: ImageButton
 6
 7@OnClick(R.id.mRecordButton)
 8fun onClick(view: View) {
 9    when (view.tooltipText) {
10        this@MainActivity.resources.getString(R.string.start_text) -> start()
11        this@MainActivity.resources.getString(R.string.stop_text) -> stop()
12        this@MainActivity.resources.getString(R.string.reset_text) -> reset()
13    }
14}
15
16override fun onCreate(savedInstanceState: Bundle?) {
17    super.onCreate(savedInstanceState)
18    setContentView(R.layout.activity_main)
19}

不过运行程序,立刻就会Crash

12019-11-22 22:44:31.645 9672-9672/xyz.dev66.jumpropecounter E/AndroidRuntime: FATAL EXCEPTION: main
2    Process: xyz.dev66.jumpropecounter, PID: 9672
3    java.lang.RuntimeException: Unable to start activity ComponentInfo{xyz.dev66.jumpropecounter/xyz.dev66.jumpropecounter.MainActivity}: kotlin.UninitializedPropertyAccessException: lateinit property mRecordButton has not been initialized

还好这个问题不难,Google一下就见分晓。在onCreate方法中要加个初始化ButterKnife.bind(this)

1override fun onCreate(savedInstanceState: Bundle?) {
2    super.onCreate(savedInstanceState)
3    setContentView(R.layout.activity_main)
4    ButterKnife.bind(this)
5}

Source Compatibility and Target Compatibility

再次运行程序,依旧Crash。不过错误信息换了

12019-11-22 22:48:26.267 9907-9907/xyz.dev66.jumpropecounter E/AndroidRuntime: FATAL EXCEPTION: main
2    Process: xyz.dev66.jumpropecounter, PID: 9907
3    java.lang.NoSuchMethodError: No static method metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; in class Ljava/lang/invoke/LambdaMetafactory; or its super classes (declaration of 'java.lang.invoke.LambdaMetafactory' appears in /apex/com.android.runtime/javalib/core-oj.jar)
4        at butterknife.internal.DebouncingOnClickListener.<clinit>(DebouncingOnClickListener.java:12)
5        at xyz.dev66.jumpropecounter.MainActivity_ViewBinding.<init>(MainActivity_ViewBinding.java:33)

错误提到了DebouncingOnClickListener.java:12这个位置。Kotlin是JVM语言。也就是最终编译为Java运行的一种语言。DebouncingOnClickListener.java这个文件其实就是Kotlin编译为Java的生成文件。打开看一下第12行

1private static final Runnable ENABLE_AGAIN = () -> enabled = true;

看上去是一个Lambda表达式。而错误提示貌似是Android SDK找不到对应的方法,无法编译。如果Google关键词java.lang.NoSuchMethodError: No static method metafactory,只能找到一个包含上述搜索词的文章Accessing hidden method causes crash · Issue #53 · stripe …。文章是github上的issue,其中有一个人提到了解决方法issuecomment-520144968,即设置 sourceCompatibilitytargetCompatibility

1android {
2    compileOptions {
3        sourceCompatibility JavaVersion.VERSION_1_8
4        targetCompatibility JavaVersion.VERSION_1_8
5    }
6}

然而依旧百思不得其解。过了好几天,突然隐约回忆起有人说过Android SDK对lambda表达式支持的并不好。恍然大悟,Kotlin将一些逻辑转化成了Java的lambda表达式。而默认Android Studio编译时用的Android SDK版本并不能理解lambda表达式。尝试在Android Studio的File -> Project Structure中修改Source Compatibility和Target Compatibility属性到Java SDK 1.8版本:

Source Compatible

果然再次运行,顺利启动。