问题背景及现象
在历史项目中,为替代编写大量的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后,同样的代码报错了,异常堆栈如下:

问题分析

从问题堆栈可以看出在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,如下图所示:

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

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

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

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

至此我们确认1.6.0版本对应的kotlin-android-extensions插件存在异常,初始化Map代码没有在构造函数刚开始就执行。
解决方案
互联网搜索关键词kotlin android extensions 1.6.0 crash,如下图所示:

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

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

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

按照评论升级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