踩到一个 Koltin 加 Android api 27 的坑。

Support library 升级到 27.x.x 之后,发现项目一堆报错:

error

它说我的 onCreateView 方法覆写了 nothing。Are You Kidding me?

对比了一下两个版本的 support library,发现了问题。

27.x.x 版本:

@Nullable
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
        @Nullable Bundle savedInstanceState) {
    return null;
}

26.x.x 版本

@Nullable
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
        @Nullable Bundle savedInstanceState) {
    return null;
}

就是这个 LayoutInflater 前的 @NonNull 注解导致的,在 kotlin 中,任何类型都分 nullable 和 non-null 两种,并且不能隐式转换,在 kotlin 调用 java 代码时如果 java 代码没有使用注解声明一个变量是否可为空,例如 String str,kotlin 会将这个变量标记为 String! 类型,表示我也不知道这个变量是否可为空,使用时需要自行处理,当 java 代码中使用了 @NonNull@Nullable 注解标注了一个变量的类型,那么 kotlin 就明确地知道了它要么是 String 不可为空,要么是 String? 可为空。StringString? 可以理解为两个不相同的类型,因此上面的方法覆写就会报错,认为方法签名不相同,不能使用 override 关键字。

同理,在 fragment 中:

@Nullable
public Context getContext() {
    return mHost == null ? null : mHost.getContext();
}

因此在调用 context 的方法时必须 context!!.getString() 而不能 context.getString()

WTTTTTTTF!

即便在我明确认定 context 不为空的情况下,仍然需要加 !! 使用。于是我想了一个办法:

override fun getContext() = super.getContext()!!

fun getNullableContext() = super.getContext()

这样我就可以在明确知道 context 不为空的地方直接调用 context.getString() 啦,需要判断是否为空的地方调用 getNullableContext()

然而问题并没有结束,因为有些地方我需要使用 getActivity() 而不只是 getContext(),可是:

@Nullable
final public FragmentActivity getActivity() {
    return mHost == null ? null : (FragmentActivity) mHost.getActivity();
}

注意到那个“万恶”的 final 了吗?

WTTTTTTTTTTTTTTF!

只能在每个使用 getActivity() 并确定它不为空的地方手动加 !! 了。。。暂时没有想到更好的解决办法。

最后说两句:

  • 第一,新的变动并不是为了给写代码添加麻烦而加上的,要明确 fragment 中的 getContext()getActivity() 在一些情况下就是有可能为空的,不要简单地在所有地方加上 !! 就算完事,该判断的地方还是得判断的。

  • 第二,虽然 27 版本的升级带来了这样的兼容问题,但整体而言,对于是否可为空类型在 java 和 kotlin 之间的调用语法更加完善了。兼容适配的过程很痛苦,但是 —— 忍着! - -#