Kotlin升级1.6.0 Crash ???

问题背景及现象

在历史项目中,为替代编写大量的findViewById,引入了kotlin-android-extensions插件,该插件可以直接用布局中声明的控件ID访问控件。举例如下:

<!-- custom_layout.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:id="@+id/hello_text"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>

布局如上,如果需要在Java代码中使用上述布局的TextView对象,代码如下:

import kotlinx.android.synthetic.main.custom_layout.view.*

hello_text.setOnClickListener{
    Toast.makeText(context,"Hello",Toast.LENGTH_SHORT).show()
}

在kotlin 1.5.31上,上述代码正常运行,但升级到1.6.0后,同样的代码报错了,异常堆栈如下:

Kotlin升级1.6.0 Crash ???
24-1-1

问题分析

Kotlin升级1.6.0 Crash ???
24-1-2

从问题堆栈可以看出在CustomView的b函数中调用Map.get方法所使用的Map对象为空,导致空指针异常,堆栈很明确,那我们只需要找到CustomView的b函数,查看Map初始化过程即可,打开CustomView类,代码如下:

package com.ams.myapplication

import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import android.widget.Toast
import kotlinx.android.synthetic.main.custom_layout.view.*

class CustomView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    init {
        inflate(context, R.layout.custom_layout, this)
        initAttrs(attrs)
    }

    private fun initAttrs(attrs: AttributeSet?) {
        // kotlin-android-extensions插件中提供的根据空间id访问控件对象的能力
        hello_text.setOnClickListener{
            Toast.makeText(context,"Hello",Toast.LENGTH_SHORT).show()
        }
    }
}

内容很简单,完全没有b函数的定义,怎么回事?

再次观察代码可以发现,这里使用了kotlin-android-extensions提供的能力替换了findViewById的操作,根据经验,我们知道一般情况下,这种能力是通过构造控件id和控件对象之间的映射关系来实现的,而承载映射关系最典型的数据结构就是Map,正好对上了堆栈中的数据结构,不妨大胆假设kotlin-android-extensions插件中也是这样实现的,那么怎么验证这一想法呢?查看CustomView的字节码即可。

CustomView字节码分析

将前文中编译出的有问题的apk拖入Android Studio中,Android Studio会自动反编译该apk,如下图所示:

Kotlin升级1.6.0 Crash ???
24-1-3

随后打开classes.dex,进入com/ams/myapplication,选中CustomView,右键弹出菜单,选择Show Bytecode就可以看到错误码了,如下图所示:

Kotlin升级1.6.0 Crash ???
24-1-4

打开CustomView的字节码,可以看到如下截图:

Kotlin升级1.6.0 Crash ???
24-1-8

其中红色区域为发生异常的b函数调用流程,从右到左看红色区域,可以得出如下调用流程:

Kotlin升级1.6.0 Crash ???
kotlin_android_extensions

进一步结合右图1,可以看出V0地址的Map初始化发生在c函数调用之后,这种情况下b处的Map.get必然空指针异常呀,示意图如下:

Kotlin升级1.6.0 Crash ???
kotlin_android_extensions.drawio

至此我们确认1.6.0版本对应的kotlin-android-extensions插件存在异常,初始化Map代码没有在构造函数刚开始就执行

解决方案

互联网搜索关键词kotlin android extensions 1.6.0 crash,如下图所示:

Kotlin升级1.6.0 Crash ???
24-1-10

可以看到截图部分强关联kotlin-android-extensions的导入包名,点击进入

Kotlin升级1.6.0 Crash ???
24-1-11

可以看到KT-49799的描述和我们遇到的问题完全一致,打开该链接

Kotlin升级1.6.0 Crash ???
24-1-12

可以看到堆栈也是发生在Map.get方法上,在底下的讨论区给予了我们解决方案,升级kotlin到1.6.10

Kotlin升级1.6.0 Crash ???
24-1-13

按照评论升级kotlin-Gradle-plugin版本到1.6.10,问题解决,代码如下:

classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"

KT-49799链接:https://youtrack.jetbrains.com/issue/KT-49799/NullPointerException-when-using-kotlin-android-extensions-synthetic-after-update-to-Kotlin-1.6.0


原文始发于微信公众号(小海编码日记):Kotlin升级1.6.0 Crash ???

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/67808.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!