You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin使用Coil将Uri转换为Bitmap抛出空指针异常如何修复

问题根因

空指针由3个问题共同导致:

  • 初始状态下bitmap值为null,协程异步加载图片需要耗时,Compose首次渲染时bitmap.value还没被赋值,你在Image组件里写的bitmap.value?.asImageBitmap()!!用了强制非空断言,直接把null值强转成非空类型触发崩溃。
  • uriToBitmap方法里有多处不安全的强制类型转换:如果Uri为空、没有文件访问权限、图片损坏,图片加载结果不是SuccessResult,或者返回的drawable不是BitmapDrawable类型,直接强转就会抛异常。
  • 协程使用错误:LaunchedEffect本身已经提供了和Compose生命周期绑定的协程作用域,你额外新建CoroutineScope(Dispatchers.IO).launch启动的协程不会随组件销毁自动取消,容易产生内存泄漏,还可能在组件销毁后给bitmap赋值触发异常。另外你把LaunchedEffect的key写死成了固定字符串"tobitmap",后续imageUri更新时不会触发重新加载,会一直显示旧图。
修复步骤
  • 移除所有不安全的!!非空断言,加载过程中、加载失败时显示占位图,不要在状态为null时强制渲染Image组件
  • 修正协程写法,直接复用LaunchedEffect自带的协程作用域,把key绑定到实际的imageUri上,Uri变化时自动重新加载
  • 改造uriToBitmap方法,增加异常捕获和类型校验,不要直接做强制类型转换

修复后的完整代码:

// 图片状态保持可空类型
var bitmap = remember { mutableStateOf<Bitmap?>(null) }
val currentImageUri = shoppingListScreenViewModel.state.value.imageUri

// key绑定实际的图片Uri,Uri变更时自动重新触发加载
LaunchedEffect(key1 = currentImageUri) {
    // LaunchedEffect自带协程作用域,直接withContext切换IO线程即可,不需要额外新建CoroutineScope
    bitmap.value = withContext(Dispatchers.IO) {
        uriToBitmap(context, currentImageUri).getOrNull()
    }
}

// 根据bitmap是否为空渲染对应内容,空值时显示占位
if (bitmap.value != null) {
    Image(
        bitmap = bitmap.value!!.asImageBitmap(),
        contentDescription = ""
    )
} else {
    // 替换成你项目里的占位图资源即可
    Image(
        painter = painterResource(id = R.drawable.default_placeholder),
        contentDescription = "图片加载中"
    )
}

// 改造图片转换方法,增加异常和类型校验
private suspend fun uriToBitmap(context: Context, uri: Uri?): Result<Bitmap> = runCatching {
    require(uri != null) { "图片Uri为空" }

    val loader = ImageLoader(context)
    val request = ImageRequest.Builder(context)
        .data(uri)
        .allowHardware(false)
        .build()

    // 先判断加载结果类型,不直接强转
    val drawableResult = when (val loadResult = loader.execute(request)) {
        is SuccessResult -> loadResult.drawable
        is ErrorResult -> throw loadResult.throwable
        else -> throw IllegalStateException("未知的图片加载结果")
    }

    // 校验drawable类型,避免强转崩溃
    val bitmapDrawable = drawableResult as? BitmapDrawable 
        ?: throw IllegalStateException("加载结果不是位图类型")

    Bitmap.createScaledBitmap(bitmapDrawable.bitmap, 80, 80, true)
}
额外优化建议

如果你不需要对位图做额外的自定义处理,完全可以直接用图片加载库自带的异步图片组件,不需要自己手动转Bitmap,代码更简洁也更稳定:

AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data(shoppingListScreenViewModel.state.value.imageUri)
        .size(80, 80)
        .allowHardware(false)
        .build(),
    contentDescription = ""
)

注意要给App配置对应Uri的访问权限,比如选择相册图片要加READ_MEDIA_IMAGES(Android13+)或者READ_EXTERNAL_STORAGE(旧版本)权限,拍照返回的Uri要配置FileProvider的访问权限,否则也会加载失败。

内容的提问来源于stack exchange,提问作者Raj Narayanan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 23:36:37