使用 Kotlin 编写 Android 应用时要注意的一个小坑

2/26/2020 posted in  Android comments

nullable 是个好东西,但可惜 Java 没有呀~

0x00 Background

前段时间在fix 一个 Google Play 发现的一个issue的时候发现了一个非常古怪的问题。

java.lang.IllegalArgumentException

一个很简单的 Exception,但是在代码中怎么找怎么找,都找不到对应的到底是谁的参数有问题,看 crash stack 也非常的不明确,只是模糊的告诉我们,在一个 PhoneStateListener 的实现中。仔细检查了我们的实现以后发现:

override fun onCallStateChanged(state: Int, phoneNumber: String) {
    // codes
}

这里面的代码非常的简单,几乎没有可能的地方会抛这个问题。

0x01 Root cause

主要的原因在于,Kotlin 在实现或继承 Java 层的接口/方法的时候,如果一个参数被加上了 @Nullable 的注解,则会被自动实现成 Type? 的形式,但是如果原本的接口不带这个注解,那么实现的时候,Kotlin中无论是 Type or Type? 这两种写法都是被接受的,因此,在这个例子中,
Java 的接口是这样定义的:

// android/telephony/PhoneStateListener.java
public void onCallStateChanged(int state, String phoneNumber) {
        // default implementation empty
}

而我们的实现中,这个 phoneNumber 的类型被强制变成了 @NonNull String,因此当调用方传入 null 之后,Java层是正确的,但是回调到 Kotlin 的时候, String? 并不能强制转换成 String, 因此会抛出 java.lang.IllegalArgumentException

0x02 Solution

找到了原因之后,fix 起来就非常的简单了,将原有接口的实现变更为:

override fun onCallStateChanged(state: Int, phoneNumber: String?) {
    // codes
}

然后在内部进行 null check, 以确保不会发生其它error。

0x03 Afterwards

Kotlin/Java 混用,在现在的 Android Project 中应该是非常常见的场景了,平时开发过程中可能一个不注意就踩到了一些坑,因此需要格外小心一些隐式的转换之类,这类问题编译器无法发现,被抓到的时候可能栈也不是特别清楚,需要仔细甄别。

说到这里,实名表扬 Firebase crashlytics, Google Play中只能看到栈,但是 crashlytics 中能够看到

Fatal Exception: java.lang.IllegalArgumentException
Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter phoneNumber
CallPhoneStateReceiver$onReceive$1.onCallStateChanged

非常实用了。