一、Bitmap 内存回收
从3.0开始,Bitmap 像素数据和 Bitmap 对象一起存放在 Dalvik 堆中,而在3.0之前,Bitmap 像素数据存放在 Native 内存中。
所以,在3.0之前,Bitmap 像素数据在 Nativie 内存的释放是不确定的,容易内存溢出而 Crash,官方强烈建议调用 recycle()(当然是在确定不需要的时候);而在3.0之后,则是强调Bitmap的复用。
使用 LruCache 对 Bitmap 对象进行缓存,当再次使用到这个 Bitmap 的时候直接获取,而不用重走编码流程。
Android3.0(API 11之后)引入了 BitmapFactory.Options.inBitmap 字段,设置此字段之后解码方法会尝试复用一张存在的 Bitmap 。这意味着 Bitmap 的内存被复用,避免了内存的回收及申请过程,显然性能表现更佳。不过,使用这个字段有几点限制:
- 声明可被复用的 Bitmap 必须设置 inMutable 为 true;
- Android4.4(API 19)之前只有格式为 jpg、png,同等宽高(要求苛刻),inSampleSize 为1的 Bitmap 才可以复用;
- Android4.4(API 19)之前被复用的 Bitmap 的 inPreferredConfig 会覆盖待分配内存的 Bitmap 设置的 inPreferredConfig;
- Android4.4(API 19)之后被复用的 Bitmap 的内存必须大于需要申请内存的 Bitmap 的内存;
二、Bitmap 内存大小
Bitmap 类有两个获取存储 Bitmap 像素所占用内存字节数的方法:
getByteCount
getByteCount() 方法是 API12 加入的,代表存储 Bitmap 的像素需要的最少内存。
public final int getByteCount() {
return getRowBytes() * getHeight();
}
getAllocationByteCount()
从 API19 开始,加入了 getAllocationByteCount() 方法,代表在内存中为 Bitmap 分配的内存大小,代替了 getByteCount() 方法。
public final int getAllocationByteCount() {
if (mBuffer == null) {
//mBuffer 代表存储 Bitmap 像素数据的字节数组。
return getByteCount();
}
return mBuffer.length;
}
在不复用 Bitmap 时,getByteCount() 和 getAllocationByteCount 返回的结果是一样的。
在通过复用 Bitmap 来解码图片时,如果被复用的 Bitmap 的内存比待分配内存的 Bitmap 大,那么 getByteCount() 表示新解码图片占用内存的大小(并非实际内存大小,实际大小是复用的那个Bitmap的大小),getAllocationByteCount() 表示被复用 Bitmap真实占用的内存大小(即 mBuffer 的长度)。
Bitmap 大小的计算
上面是获取内存大小的方法,下面是 Bitmap 占用内存的计算公式:
Bitamp 占用内存大小 = 宽度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一个像素所占的内存
上面公式中:
- 宽度像素和高度像素就是 Bitmap 原始的宽度和高度的像素规格;
- 一个像素所占的内存与图片的色彩模式有关,这个色彩模式在 Bitmap 类里面通过枚举类 Config 标识:
public enum Config {
ALPHA_8 (1),
RGB_565 (3),
@Deprecated
ARGB_4444 (4),
ARGB_8888 (5);
}
- ARGB_8888:每个像素占四个字节,A、R、G、B 分量各占8位,是 Android 的默认设置;
- RGB_565:每个像素占两个字节,R分量占5位,G分量占6位,B分量占5位;
- ARGB_4444:每个像素占两个字节,A、R、G、B分量各占4位,成像效果比较差;
- Alpha_8: 只保存透明度,共8位,1字节;
- 在 BitmapFactory 的内部类 Options 有两个成员变量 inDensity 和 inTargetDensity,其中 inDensity 就 Bitmap 的像素密度,也就是 Bitmap 的成员变量 mDensity,默认是设备屏幕的像素密度,可以通过 Bitmap#setDensity(int) 设置,inTargetDensity 是图片的目标像素密度,在加载图片时就是 drawable 目录的像素密度。
在从资源目录加载图片时,这个时候调用的是 BitmapFactory#decodeResource 方法,内部调用的是 decodeResourceStream 方法:
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, Options opts) {
if (opts == null) {
opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
return decodeStream(is, pad, opts);
}
会根据设备屏幕像素密度到对应 drawable 目录去寻找图片,这个时候 inTargetDensity/inDensity = 1,图片不会做缩放,宽度和高度就是图片原始的像素规格,如果没有找到,会到其他 drawable 目录去找,这个时候 drawable 的屏幕像素密度就是 inTargetDensity,会根据 inTargetDensity/inDensity 的比例对图片的宽度和高度进行缩放。
三、Bitmap 的创建
Bitmap 有一系列的 createXXX 方法用来创建 Bitmap 对象,但是我们一般都使用 BitmapFactory 的一系列 decodeXXX 方法来生成 Bitmap:
public static Bitmap decodeFile(String pathName, Options opts)
public static Bitmap decodeFile(String pathName)
public static Bitmap decodeResourceStream(Resources res, TypedValue value, InputStream is, Rect pad, Options opts)
public static Bitmap decodeResource(Resources res, int id, Options opts)
public static Bitmap decodeResource(Resources res, int id)
public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)
public static Bitmap decodeByteArray(byte[] data, int offset, int length)
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)
public static Bitmap decodeStream(InputStream is)
public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)
public static Bitmap decodeFileDescriptor(FileDescriptor fd)
上面的方法大致可以分为四类:
- 从本地文件中解码图片:decodeFile。从本地文件中解压的原始图片并不会对图片进行缩放;
- 从资源文件中解码图片:decodeResource。会根据 inTargetDensity/inDensity 对图片进行缩放;
- 从输入流中解码图片:decodeStream。获取网络图片的时候使用此方法,其实从本地文件和资源文件中解压图片,最终调用的也是该方法;
- 从字节数组中解码图片:decodeByteArray。这个字节数组是输入流传化为的字节数组;
- decodeFileDescriptor。也是从本地文件中解码图片,但是并不是通过流的方式解码,比 decodeFile 方法省内存;
四、Bitmap 压缩
1.质量压缩
调用 Bitmap#compress 方法:
public boolean compress(CompressFormat format, int quality, OutputStream stream) {
checkRecycled("Can't compress a recycled bitmap");
// do explicit check before calling the native method
if (stream == null) {
throw new NullPointerException();
}
if (quality < 0 || quality > 100) {
throw new IllegalArgumentException("quality must be 0..100");
}
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "Bitmap.compress");
boolean result = nativeCompress(mNativePtr, format.nativeInt,
quality, stream, new byte[WORKING_COMPRESS_STORAGE]);
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
return result;
}
第一个参数 format 标识压缩格式,CompressFormat 是一个枚举类,有三个值,是三种图片格式 JPEG,PNG,WEBP
public enum CompressFormat {
JPEG (0),
PNG (1),
WEBP (2);
}
- PNG是一种无损压缩的图像存储格式,相同像素宽高的图像保存为PNG在文件大小上比JPEG往往要大的多,一般是JPEG大小的几倍左右;
- JPEG是一种有损压缩的图像存储格式,不支持alpha通道,由于它具有高压缩比,在压缩过程中把重复的数据和无关紧要的数据会选择性的丢失,所以如果不需要用到alpha通道,那么大都图片格式都用该格式;
- Webp图片格式是Google推出的一个支持alpha通道的有损压缩格式,据Google官方表明,同质量情况下Webp图像要比JPEG、PNG图像小25%~45%左右;
第二个参数 quality 表示压缩质量
这种方式是在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的,不会减少图片的像素,它压缩的是存储大小,即你放到 disk 上的大小,但是解码成 bitmap 后占的内存是不变的。
2.尺寸压缩
前面的 decodeXXX 方法中有一个 BitmapFactory.Options 类型的参数,解码图片时,设置 BitmapFactory.Options 类的 inJustDecodeBounds 属性为 true,可以在 Bitmap 不被加载到内存的前提下,获取 Bitmap 的原始宽高。而设置 BitmapFactory.Options 的 inSampleSize 属性可以真实的压缩 Bitmap 占用的内存,加载更小内存的 Bitmap。
设置 inSampleSize 之后,Bitmap 的宽、高都会缩小 inSampleSize 倍。
inSampleSize 比1小的话会被当做1,任何 inSampleSize 的值会被取接近2的幂值。
3.色彩模式压缩
Bitmap 的色彩模式默认为 Bitmap.Config.ARGB_8888,可以通过 BitmapFactory.Options.inPreferredConfig 属性来修改解码图片的色彩模式,每个像素占用的字节减少了,宽度和高度像素不变的情况下,占用的内存大小也会减少;
4.Matrix
Matrix 矩阵变换,可以对 bitmap 进行非常多的操作,其中一项是对 bitmap 进行等比缩放,这种方式可以精确的缩放到符合我们预期的 bitmap 大小,奥代码如下:
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
Matrix matrix = new Matrix();
float rate = computeScaleRate(bitmapWidth, bitmapHeight);
matrix.postScale(rate, rate);
Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, matrix, true);
5.Bitmap#createScaledBitmap
通过 public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)
直接指定图片压缩后的宽度和高度
五、Bitmap 与 Drawable 的转换
Bitmap-->Drawable
通过 BitmapDrawable 的构造方法:
@Deprecated
public BitmapDrawable(Bitmap bitmap)
public BitmapDrawable(Resources res, Bitmap bitmap)
Drawable-->Bitmap
根据已有的 Drawable 创建一个新的 Bitmap
public static Bitmap createBitmap(int width, int height, Config config)