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
相关产品推荐
相关产品推荐

