Android | Bitmap基础知识点整理

Bitmap介绍

Bitmap 是一种图像表示,用于存储图像数据的像素矩阵。它使用一个矩阵保存图像的像素信息。每个像素都有颜色信息,通常以 RGB(红色、绿色、蓝色)三通道表示。Android 还支持透明度通道,即 ARGB 模式,常见的有:

  • ARGB_8888:表示每个像素包含 32 位(8 位 Alpha,8 位 Red,8 位 Green,8 位 Blue),是最常用的配置,图像质量高。
  • RGB_565:每个像素包含 16 位(5 位 Red,6 位 Green,5 位 Blue),图像质量稍低,但占用的内存较少。
  • ALPHA_8:每个像素只包含 8 位透明度,常用于遮罩。

更详细的参见下面要介绍的 BitmapFactory.Config


创建 Bitmap

创建 Bitmap 的方法有多种,具体方式取决于使用场景。比如可以通过 BitmapFactory 从资源、文件、字节数组等创建:

创建Bitmap
列举其中的几种创建方式:

//1、从资源文件创建 Bitmap
val bitmapFromResource = BitmapFactory.decodeResource(resources, R.drawable.example_image)

//2、从文件创建 Bitmap
val bitmapFromFile = BitmapFactory.decodeFile("/path/to/image.jpg")

//3、从字节数组创建 Bitmap
val byteArray: ByteArray = ...
val bitmapFromByteArray = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)

//4、从从输入流解码成 Bitmap 
lifecycleScope.launch {
      flow<Bitmap> {
         runCatching {
            val inputStream = URL("https://img0.baidu.com/it/u=264197520,340791108&fm=253&fmt=auto&app=120&f=JPEG?w=570&h=380").openStream()
            val bitmap = BitmapFactory.decodeStream(inputStream)
            emit(bitmap)
          }
      }
      .flowOn(Dispatchers.IO)
      .collectLatest { bm ->
          log("在线图片大小 -> width:${bm.width}, height:${bm.height}, " +
                            "allocationByteCount:${bm.allocationByteCount}=${bm.allocationByteCount / 1024} KB")
    }
}
//执行结果:在线图片大小 -> width:570, height:380, allocationByteCount:866400=846 KB

//5、创建一个宽度 200px,高度 200px 的 ARGB_8888 格式的空白 Bitmap
val emptyBitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888)
emptyBitmap.eraseColor(Color.RED) // 直接用颜色填充空白 Bitmap
BitmapFactory.Options

用于控制解码位图的设置类,它提供了一系列选项来配置图片的加载和处理。使用 BitmapFactory.Options 可以更有效地处理图片资源,尤其在内存优化、缩放加载、复用位图等方面。下面列举一些常用字段:

  • inJustDecodeBounds:只获取图片的宽高等信息,不实际加载图片数据。当只想知道图片的尺寸而不想占用内存时,可以设置该值为 true。
  • outWidthoutHeight:用于获取图片的宽度和高度信息。当inJustDecodeBounds设置为true时,options.outWidth 和 options.outHeight 可以分别获取到图片的宽度和高度,而不会实际加载整个图片到内存中。示例:
val options = BitmapFactory.Options().apply {
    inJustDecodeBounds = true
}
BitmapFactory.decodeResource(resources, R.drawable.sample_image, options)
val imageWidth = options.outWidth
val imageHeight = options.outHeight
  • inSampleSize:用于缩小图片的大小加载。比如当 inSampleSize = 2 时,图片宽高都会被缩小为原来的一半(1/4的像素),从而减少内存开销。
val options = BitmapFactory.Options().apply {
    inSampleSize = 2
}
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.sample_image, options)
  • inPreferredConfig:设置解码时的位图配置(BitmapFactory.Config 类型,默认是 Bitmap.Config.ARGB_8888),它决定了位图像素的存储格式和颜色深度,直接影响内存占用。
val options = BitmapFactory.Options().apply {
    inPreferredConfig = Bitmap.Config.RGB_565
}
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.sample_image, options)

BitmapFactory.Config 定义了像素存储格式的枚举类型,它决定了每个像素使用多少位以及如何存储颜色值,详细配置如下:
BitmapFactory.Config

  • inMutable:是否生成可变的位图。当设置为 true 时,生成的 Bitmap 可以被修改。
val options = BitmapFactory.Options().apply {
    inMutable = true
}
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.sample_image, options)
bitmap?.setPixel(0, 0, Color.RED)  // 修改像素
  • inBitmap:复用已存在的位图内存,减少内存分配和回收的开销。
val options = BitmapFactory.Options()
options.inMutable = true //设置为可变位图
//创建第一个位图
val reusableBitmap = BitmapFactory.decodeResource(resources, R.drawable.icon_cat_w, options)
reusableBitmap.run {
     log("1、reusableBitmap -> height:$height, width:$width, byteCount:$byteCount, allocationByteCount:$allocationByteCount")
}
//准备复用第一个已存在的位图
options.inBitmap = reusableBitmap
//复用第一个位图
val targetBitmap = BitmapFactory.decodeResource(resources, R.drawable.icon_compose, options)
targetBitmap.run {
      log("2、targetBitmap -> height:$height, width:$width, byteCount:$byteCount, allocationByteCount:$allocationByteCount")
}
log("3、reusableBitmap: ${reusableBitmap.hashCode()}, targetBitmap: ${targetBitmap.hashCode()}")

执行结果:

1、reusableBitmap -> height:786, width:1100, byteCount:3458400, allocationByteCount:3458400
2、targetBitmap -> height:458, width:424, byteCount:776768, allocationByteCount:3458400
3、reusableBitmap: 98561878, targetBitmap: 98561878

可以看到3处hashCode的值是一样的,说明Bitmap确实复用了 需要注意宽高只能从大往小复用,反之复用会报错

  • inDensityinTargetDensity:inDensity 是指图片资源的像素密度。inTargetDensity 是指当前设备的目标像素密度。如果两者不同,系统会根据这些密度信息缩放图片以适应屏幕分辨率
  • outMimeType:表示解码后的图像格式(MIME类型),如 image/png、image/jpeg 等。此字段不会影响解码过程,但会在解码后保存图像的类型信息。
  • inScaled:是否根据 inDensity 和 inTargetDensity 缩放图像。如果设置为 true,图像将根据这两个值进行缩放。

Bitmap常用方法

  • getWidth()、getHeight():返回 Bitmap 的宽度和高度。
  • getByteCount() / getAllocationByteCount():getByteCount()返回位图实际像素数据所占用的内存大小,与实际图像大小有关,且不受回收重用机制的影响; 而getAllocationByteCount() 返回 Bitmap 所占的内存分配的大小,这个大小不仅包括实际像素数据,还可能包括为了复用内存而预分配的额外空间。比如在使用 inBitmap(在 BitmapFactory.Options 中启用复用机制)时有意义,因为这个方法会返回实际分配的内存大小,可能比 getByteCount() 大。即:getAllocationByteCount() >= getByteCount()
  • getConfig(): BitmapFactory.Config类型,具体值上面已经介绍过了。
  • recycle():手动释放 Bitmap 所占用的内存资源,当 Bitmap 不再需要时调用它来避免内存泄漏。
  • isRecycled():isRecycled() 用来判断 Bitmap 是否已经被回收。调用 recycle() 方法后,Bitmap 被标记为回收状态,内存被释放,此时该对象不再可用,访问它会导致异常。
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.sample_image)
// 回收 bitmap
bitmap.recycle()
// 检查是否已经回收
if (bitmap.isRecycled) {
    println("Bitmap 已经被回收")
}
  • isMutable():isMutable() 用来检查 Bitmap 是否可以被修改。默认情况下,解码后的位图是不可变的(immutable),如果 Bitmap 是可变的(mutable),你可以对它进行像素操作。
  • setPixel / getPixel:获取或设置 Bitmap 指定位置的像素颜色值。
  • copy(Config config, boolean isMutable):复制一个位图,且互相不影响。示例:
val originalBitmap = BitmapFactory.decodeResource(resources, R.drawable.sample_image)

// 复制一个新的Bitmap,使用相同的颜色配置,并使其可变
val copiedBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true)

// 修改新Bitmap的一个像素
copiedBitmap.setPixel(10, 10, Color.RED)

// 打印两者的宽高,验证是两个不同的位图对象
println("Original bitmap width: ${originalBitmap.width}, height: ${originalBitmap.height}")
println("Copied bitmap width: ${copiedBitmap.width}, height: ${copiedBitmap.height}")
println("hashCode: ${originalBitmap.hashCode()}, ${copiedBitmap.hashCode()}")

执行结果:

17:28:14.286  E  Original bitmap width: 132, height: 132
17:28:14.286  E  Copied bitmap width: 132, height: 132
17:28:14.286  E  hashCode: 6852914, 124731011

可以看到宽高都是一样的,且两个Bitmap的hashCode并不一样,可以理解copy是一次深拷贝。

  • eraseColor(@ColorInt int c):将位图的所有像素填充为指定的颜色。如:bitmap.eraseColor(Color.BLUE)
  • compress(CompressFormat format, int quality, OutputStream stream):将位图压缩为指定格式(如 PNG、JPEG)并输出到一个流中。通常用于保存或传输位图。示例:
val outputStream = FileOutputStream(File("/path/to/output.jpg"))
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
outputStream.close()

Bitmap 常见优化手段

由于 Bitmap 对象占用大量内存,在使用和处理大量图像时,需要对内存进行有效管理。以下是常见的优化手段:

使用 inSampleSize 对大图片进行缩放

为了避免加载大图片时内存溢出,可以通过 BitmapFactory.Options 中的 inSampleSize 对图像进行缩放加载。

val options = BitmapFactory.Options()
options.inJustDecodeBounds = true // 只加载图片的尺寸,不加载图片内容
BitmapFactory.decodeResource(resources, R.drawable.large_image, options)

val imageHeight = options.outHeight
val imageWidth = options.outWidth

// 计算合适的 inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth = 100, reqHeight = 100)

// 重新加载缩小后的图片
options.inJustDecodeBounds = false
val scaledBitmap = BitmapFactory.decodeResource(resources, R.drawable.large_image, options)

// 计算缩放比例的方法
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
}
回收 Bitmap

Bitmap 不再需要时,应当调用 recycle() 方法来释放内存。

bitmapFromResource.recycle()

注意:在调用 recycle() 之后不能再访问这个 Bitmap,否则会抛出异常。

Bitmap 与 Drawable 互转

  • 从 Bitmap 转换为 Drawable
val drawable = BitmapDrawable(resources, bitmap)
  • 从 Drawable 转换为 Bitmap
fun drawableToBitmap(drawable: Drawable): Bitmap {
    if (drawable is BitmapDrawable) {
        return drawable.bitmap
    }
    
    val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    drawable.setBounds(0, 0, canvas.width, canvas.height)
    drawable.draw(canvas)
    return bitmap
}

Bitmap 的内存管理变化

  • Android 2.x 及之前版本
    在 Android 2.x(API Level 10 及以下)中,Bitmap 对象的像素数据存储在 Native 层的堆内存中,而 Bitmap 对象本身存储在 Dalvik 堆 中(Java 层)。因此,当处理 Bitmap 时,虽然使用的是 Java 对象,但其实际像素数据存储在 Native 层。Java 垃圾回收器(GC)无法直接管理这些 Native 堆中的资源,所以必须调用 Bitmap.recycle() 方法来手动释放像素内存。
  • Android 3.0(API Level 11)
    从 Android 3.0 开始,Bitmap 的像素数据的内存管理发生了变化。为了简化内存管理,Android 将 Bitmap 对象的 像素数据存储从 Native 层移到了 Dalvik 层的堆内存。这样,Java 层和像素数据都位于 Dalvik 堆中,简化了内存管理,因为 Java 垃圾回收器能够一并管理和回收这些对象。优点是方便了管理,且Java 的垃圾回收机制能管理整个 Bitmap 对象的内存;缺点也很明显,Dalvik 虚拟机的堆内存相对有限,大量或大尺寸的 Bitmap 操作容易导致内存不足的问题(OOM)。
  • Android 8.0(API Level 26)及之后的版本
    从 Android 8.0 开始,Bitmap 像素数据的存储再次发生变化。Bitmap 的像素数据又回到了 Native 堆,而 Bitmap 对象本身依旧存放在 Java 堆中。Android 8.0 引入了更加先进的内存管理机制,同时减少了 Dalvik/ART 堆内存的压力,不会轻易遇到内存不足问题。
android Bitmap用法总结 Bitmap用法总结 1、Drawable → Bitmap public static Bitmap drawableToBitmap(Drawable drawable) { Bitmap bitmap = Bitmap .createBitmap( drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); // canvas.setBitmap(bitmap); drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); drawable.draw(canvas); return bitmap; } 2、从资源中获取Bitmap Resources res=getResources(); Bitmap bmp=BitmapFactory.decodeResource(res, R.drawable.pic); 3、Bitmap → byte[] private byte[] Bitmap2Bytes(Bitmap bm){ ByteArrayOutputStream baos = new ByteArrayOutputStream(); bm.compress(Bitmap.CompressFormat.PNG, 100, baos); return baos.toByteArray(); } 4、byte[] → Bitmap private Bitmap Bytes2Bimap(byte[] b){ if(b.length!=0){ return BitmapFactory.decodeByteArray(b, 0, b.length); } else { return null; } } 5、保存bitmap static boolean saveBitmap2file(Bitmap bmp,String filename){ CompressFormat format= Bitmap.CompressFormat.JPEG; int quality = 100; OutputStream stream = null; try { stream = new FileOutputStream("/sdcard/" + filename); } catch (FileNotFoundException e) { // TODO Auto-generated catch block Generated by Foxit PDF Creator © Foxit Software http://www.foxitsoftware.com For evaluation only. e.printStackTrace(); } return bmp.compress(format, quality, stream); } 6、将图片按自己的要求缩放 // 图片源 Bitmap bm = BitmapFactory.decodeStream(getResources() .openRawResource(R.drawable.dog)); // 获得图片的宽高 int width = bm.getWidth(); int height = bm.getHeight(); // 设置想要的大小 int newWidth = 320; int newHeight = 480; // 计算缩放比例 float scaleWidth = ((float) newWidth) / width; float scaleHeight = ((float) newHeight) / height; // 取得想要缩放的matrix参数 Matrix matrix = new Matrix(); matrix.postScale(scaleWidth, scaleHeight); // 得到新的图片 Bitmap newbm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true); // 放在画布上 canvas.drawBitmap(newbm, 0, 0, paint); 相关知识链接:http://www.eoeandroid.com/thread-3162-1-1.html 7、bitmap的用法小结 BitmapFactory.Options option = new BitmapFactory.Options(); option.inSampleSize = 2; //将图片设为原来宽高的1/2,防止内存溢出 Bitmap bm = BitmapFactory.decodeFile("",option);//文件流 URL url = new URL(""); InputStream is = url.openStream(); Bitmap bm = BitmapFactory.decodeStream(is); android:scaleType: android:scaleType是控制图片如何resized/moved来匹对ImageView的size。ImageView.ScaleType / android:scaleType值的意义区别: CENTER /center 按图片的原来size居中显示,当图片长/宽超过View的长/宽,则截取图片的居中部分 显示 CENTER_CROP / centerCrop 按比例扩大图片的size居中显示,使得图片长(宽)等于或大于View的长 (宽) CENTER_INSIDE / centerInside 将图片的内容完整居中显示,通过按比例缩小或原来的size使得图片 长/宽等于或小于View的长/宽 Generated by Foxit PDF Creator © Foxit Software http://www.foxitsoftware.com For evaluation only. FIT_CENTER / fitCenter 把图片按比例扩大/缩小到View的宽度,居中显示 FIT_END / fitEnd 把图片按比例扩大/缩小到View的宽度,显示在View的下部分位置 FIT_START / fitStart 把图片按比例扩大/缩小到View的宽度,显示在View的上部分位置 FIT_XY / fitXY 把图片 不按比例 扩大/缩小到View的大小显示 MATRIX / matrix 用矩阵来绘制,动态缩小放大图片来显示。 //放大缩小图片 public static Bitmap zoomBitmap(Bitmap bitmap,int w,int h){ int width = bitmap.getWidth(); int height = bitmap.getHeight(); Matrix matrix = new Matrix(); float scaleWidht = ((float)w / width); float scaleHeight = ((float)h / height); matrix.postScale(scaleWidht, scaleHeight); Bitmap newbmp = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true); return newbmp; } //将Drawable转化为Bitmap public static Bitmap drawableToBitmap(Drawable drawable){ int width = drawable.getIntrinsicWidth(); int height = drawable.getIntrinsicHeight(); Bitmap bitmap = Bitmap.createBitmap(width, height, drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0,0,width,height); drawable.draw(canvas); return bitmap; Generated by Foxit PDF Creator © Foxit Software http://www.foxitsoftware.com For evaluation only. } //获得圆角图片的方法 public static Bitmap getRoundedCornerBitmap(Bitmap bitmap,float roundPx){ Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap .getHeight(), Config.ARGB_8888); Canvas canvas = new Canvas(output); final int color = 0xff424242; final Paint paint = new Paint(); final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); final RectF rectF = new RectF(rect); paint.setAntiAlias(true); canvas.drawARGB(0, 0, 0, 0); paint.setColor(color); canvas.drawRoundRect(rectF, roundPx, roundPx, paint); paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); canvas.drawBitmap(bitmap, rect, rect, paint); return output; } //获得带倒影的图片方法 public static Bitmap createReflectionImageWithOrigin(Bitmap bitmap){ final int reflectionGap = 4; int width = bitmap.getWidth(); int height = bitmap.getHeight(); Matrix matrix = new Matrix(); matrix.preScale(1, -1); Bitmap reflectionImage = Bitmap.createBitmap(bitmap, 0, height/2, width, height/2, matrix, false); Bitmap bitmapWithReflection = Bitmap.createBitmap(width, (height + height/2), Config.ARGB_8888); Canvas canvas = new Canvas(bitmapWithReflection); canvas.drawBitmap(bitmap, 0, 0, null); Paint deafalutPaint = new Paint(); Generated by Foxit PDF Creator © Foxit Software http://www.foxitsoftware.com For evaluation only. canvas.drawRect(0, height,width,height + reflectionGap, deafalutPaint); canvas.drawBitmap(reflectionImage, 0, height + reflectionGap, null); Paint paint = new Paint(); LinearGradient shader = new LinearGradient(0, bitmap.getHeight(), 0, bitmapWithReflection.getHeight() + reflectionGap, 0x70ffffff, 0x00ffffff, TileMode.CLAMP); paint.setShader(shader); // Set the Transfer mode to be porter duff and destination in paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN)); // Draw a rectangle using the paint with our linear gradient canvas.drawRect(0, height, width, bitmapWithReflection.getHeight() + reflectionGap, paint); return bitmapWithReflection; } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_小马快跑_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值