为什么修改Compose的remember值后调用JNI会抛出空指针异常
问题根因
- 第一个核心原因是JNI 调用突破了 Kotlin 空安全约束:你在 Kotlin 层声明的
external fun output(input: String): String返回值为不可空String类型,但 JNI 层返回NULL时 Kotlin 不会做非空校验,会直接将 null 赋值给不可空类型的状态text_output,造成「不可空类型实际持有 null」的隐蔽问题。而 Compose 重组的调用链和普通线性执行逻辑不同,空值引发的崩溃栈会被重组链路掩盖,所以会出现看起来和 JNI 调用无关的interactionSource空指针报错。
初始输入未修改时,固定字符串的 JNI 处理逻辑没有异常,返回值合法所以不会触发问题;修改输入后如果输入内容触发了 C++ 字符串处理的边界问题(比如特殊字符、长度异常)就会导致 JNI 返回 null,进而触发崩溃。 - 第二个可能的触发原因是Compose 依赖版本未对齐:如果你的项目中
compose-foundation、compose-material等核心依赖版本不一致,Button 组件的默认参数interactionSource会找不到默认实现,修改输入触发全量重组时就会传入 null 触发空指针。
修复方案
1. 修复 JNI 空安全问题
首先修改 Kotlin 层的 external 函数声明,将返回值改为可空类型,调用时增加空校验:
class PseudoStream{ companion object{ // 返回值改为可空类型 String? external fun output(input: String): String? } }
按钮点击逻辑增加空值处理:
Button(onClick = { text_output.value = PseudoStream.output(text_input.value) ?: "处理失败" } ){ Text("Modify") }
同时检查 C++ 侧逻辑,确保所有执行路径都不会返回 NULL,比如在 Create_Java_String 函数中增加边界判断:
jstring Create_Java_String(JNIEnv *environment, const std::string& mystr){ if(mystr.empty()){ return environment->NewStringUTF(""); } return environment->NewStringUTF(mystr.data()); }
2. 对齐 Compose 依赖版本
在项目根目录的 build.gradle 中统一声明 Compose 版本:
buildscript { ext { compose_version = '1.4.3' // 可替换为任意官方稳定版本 } }
所有 Compose 相关依赖都使用统一版本号:
dependencies { implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.foundation:foundation:$compose_version" implementation "androidx.compose.material:material:$compose_version" // 其他compose依赖也统一使用该版本变量 }
可选优化
可以用 Kotlin 属性委托简化 Compose 状态的声明,避免每次调用都写 .value:
import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue // 状态声明简化 var textInput by remember { mutableStateOf("Initial value ") } var textOutput by remember { mutableStateOf("") } // 使用时直接读写变量即可 TextField( value = textInput, onValueChange = { textInput = it }, ) Button(onClick = { textOutput = PseudoStream.output(textInput) ?: "处理失败" }){ Text("Modify") } Text(textOutput)
内容的提问来源于stack exchange,提问作者Artaza Sameen
相关产品推荐
相关产品推荐

