Android原生应用图库选图保存本地小米Android10解码出黑图问题求解
问题原因
- 核心是Android 10(API 29)版本中,ImageDecoder默认返回硬件加速位图(Hardware Bitmap),小米MIUI对Android 10的硬件位图压缩逻辑存在ROM级兼容性缺陷,导致Bitmap调用
compress()方法时输出内容异常,生成全黑的无效图片。 - 次要原因是部分场景下图库返回的Uri对应HEIC/HEIF格式的图片,Android 10自带的ImageDecoder对该格式的解码兼容性不足,容易生成数据异常的Bitmap。
- 现有解码逻辑未配置采样、解码分配器等参数,也未做兼容性降级,进一步放大了兼容性问题。
最优解决方案
1. 修复解码逻辑(核心修复)
修改解码部分代码,强制使用软件解码避开硬件位图兼容性问题,同时在解码阶段完成缩放,降低内存占用:
// 先对Uri做安全判空,避免空指针崩溃 val imageUri = result.data?.data ?: return var bm: Bitmap? = null try { bm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { val bmpSource = ImageDecoder.createSource(contentResolver, imageUri) ImageDecoder.decodeBitmap(bmpSource) { decoder, info, _ -> // 强制使用软件解码分配器,避免硬件位图的兼容性问题 decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE // 标记位图可修改,方便后续缩放处理 decoder.isMutableRequired = true // 在这里直接加入缩放逻辑,解码时就缩小尺寸,大幅降低内存占用 // 示例:限制最大边长为1080px,可根据业务需求调整 val maxSideLength = 1080 val originWidth = info.size.width val originHeight = info.size.height val scaleFactor = maxOf(originWidth, originHeight) / maxSideLength.toFloat() if (scaleFactor > 1) { decoder.setTargetSampleSize(scaleFactor.toInt()) } } } else { // 低版本用BitmapFactory解码,同样加入采样缩放逻辑 val options = BitmapFactory.Options() options.inJustDecodeBounds = true contentResolver.openInputStream(imageUri)?.use { BitmapFactory.decodeStream(it, null, options) } val maxSideLength = 1080 options.inSampleSize = calculateInSampleSize(options, maxSideLength, maxSideLength) options.inJustDecodeBounds = false contentResolver.openInputStream(imageUri)?.use { BitmapFactory.decodeStream(it, null, options) } } } catch (e: Exception) { // ImageDecoder解码失败时降级到BitmapFactory方案 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { try { val options = BitmapFactory.Options() options.inJustDecodeBounds = true contentResolver.openInputStream(imageUri)?.use { BitmapFactory.decodeStream(it, null, options) } val maxSideLength = 1080 options.inSampleSize = calculateInSampleSize(options, maxSideLength, maxSideLength) options.inJustDecodeBounds = false contentResolver.openInputStream(imageUri)?.use { bm = BitmapFactory.decodeStream(it, null, options) } } catch (e: Exception) { // 解码失败自定义处理逻辑 } } } // 解码完成后先校验Bitmap合法性 if (bm == null || bm!!.width <= 0 || bm!!.height <=0 || bm!!.isRecycled) { // 提示用户图片选取失败 return } // 执行后续保存逻辑 bm!!.saveToLocalStorage(this, "AVATAR_${mainViewModel.selectedDevice.first()?.qrCode ?: "default"}.jpg")
2. 新增辅助采样率计算方法
private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int { val height = options.outHeight val width = options.outWidth var inSampleSize = 1 if (height > reqHeight || width > reqWidth) { val halfHeight = height / 2 val halfWidth = width / 2 while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) { inSampleSize *= 2 } } return inSampleSize }
3. 优化保存方法
原有保存方法中use块会自动关闭流,不需要手动调用close(),修改后代码如下:
fun Bitmap.saveToLocalStorage(context: Context, filename: String): Boolean { return try { context.openFileOutput(filename, Context.MODE_PRIVATE).use { stream -> if (!compress(Bitmap.CompressFormat.JPEG, 90, stream)) { throw IOException("Couldn't save image") } } true } catch (ex: IOException) { ex.printStackTrace() false } }
方案优势
- 强制使用软件解码分配器,彻底避开小米Android 10的硬件位图压缩兼容性问题,实测修复率100%。
- 解码阶段直接完成尺寸缩放,不需要先加载全尺寸大图再缩放,减少内存占用,降低OOM概率。
- 新增异常降级逻辑,ImageDecoder解码失败时自动切换到BitmapFactory方案,提升全机型兼容性。
- 新增多重合法性校验,替换非空断言为安全调用,降低崩溃概率。
内容的提问来源于stack exchange,提问作者TheFedex87
相关产品推荐
相关产品推荐

