一张大图如何加载才不会发生OOM
这是面试官经常问的问题具体有两种策略
1.图片采样率缩放。
2.按区域加载按区域加载。
在说这两种策略之前需要先讲另一个问题,一张图片占多少内存,是由哪些因素决定的?
1.图片的宽高。
2.图片加载的模式。
3.图片的缩放比例。
一般认为图片的内存计算公式是这样的:
图片占用内存的大小 = 宽高单个像素占用的字节数
单个像素占用的字节数则是图片加载的模式决定的例如:
Android中默认使用ARGB_8888的模式加载图片,即一个像素点占用2byte。这里简单介绍一下各个模式:
1.ALPHA_8:像素点只有alpha通道,1个像素点占用1byte。这个模式下只有透明度,一般用来做遮罩层。
2.RGB_565:R通道占用5bit,G通道占用6bit,B通道占用5bit,总共占用2byte。这个模式没有ALPHA通道,对于色彩要求不高的情况,可以采用RGB_565,Glide默认使用这个模式。
3.ARGB_4444:ARGB四个通道分别占用4bit,总共占用2byte。色彩表现不好,已经被废弃了,在4.4之后使用ARGB_4444会默认使用ARGB_8888替代。
4.ARGB_8888:ARGB四个通道分别占用8bit,总共占用4byte。比较灵活,色彩表现度较好,是推荐使用的模式,也是默认使用的模式。
5.RGBA_F16:一个像素占用8byte,适用于广色域显示。实际开发中暂时还没见到用这个的场景。
如果是系统资源,图片所存放的文件夹的路径不同。图片的缩放比例也是不同的。
如果你把一张图片放在了mdpi,而用户的手机是xhdpi的话,你会发现你的图片实际所占用的内存会比计算值(按照上面的方法做计算)多4倍。所以如果你把上面那张图片放在了mdpi目录下面,这简直就是自残啊:占用内存120M。
这是因为安卓系统会把mdpi的图片尺寸进行扩大以保证显示的尺寸相同。而相反的如果你把图片放到了xhdpi,而你的手机是mdpi的话,你的图片会被压缩成原来的1/4。
具体的比例关系:(NX 表示表示长和宽是mdpi的N倍,总内存就是N*N倍)
图片采样率缩放。
主要用于减少图片的像素点来达到减少内存的效果。主要用到的是BitmapFactory.Options类实现。
inJustDecodeBounds:该属性设置为true时,只加载图片信息到Options中,而不将图片加载到内存中。
outWidth:图片的宽度。
outHeight:图片的高度。
inSampleSize:采样率,如果值大于1,将对图片进行二次采样,宽和高分别按相应的比例缩小。例如设置为2,则宽和高缩小为原来的2分之一,即200*200的图片缩小成100*100,图片占用的内存则为原来的4分之一。inSampleSize应该设置为2的n次方,否则向下取最近的2的n次方数。比如设置inSampleSize=10,则实际inSampleSize向下取2的3次方为8,因此缩小成8分之一。
具体代码如下:
BitmapFactory.Options options = new BitmapFactory.Options();
//只读取图片信息
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.ad, options);
//读取图片宽和高
int imgWidth = options.outWidth;
int imgHeight = options.outHeight;
//读取ImageView宽和高
int ivHeight = imageView.getHeight();
int ivWidth = imageView.getWidth();
//分别计算宽和高的比值
int scaleX = imgWidth / ivWidth;
int scaleY = imgHeight / ivHeight;
int inSampleSize = 1;
//取较大的值作为采样率
inSampleSize = scaleX > scaleY ? scaleX : scaleY;
//防止采样率小于1
if (inSampleSize < 1)
inSampleSize = 1;
Log.i("bitmap", "inSampleSize = " + inSampleSize);
//根据采样率加载图片
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ad, options);
imageView.setImageBitmap(bitmap);
按区域加载
如果不想压缩图拍则需要按区域加载,主要用到的类为BitmapRegionDecoder类实现,用到的参数主要是Rect和BitmapFactory.Options,其中Rect指定加载图片的区域。代码如下:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.ad, options);
//老套路,计算图片的宽和高
int imgWidth = options.outWidth;
int imgHeight = options.outHeight;
InputStream is = getResources().openRawResource(R.raw.ad);
//初始化BitmapRegionDecoder
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
//设置图片加载模式为RGB_565
options.inPreferredConfig = Bitmap.Config.RGB_565;
//加载图片中心位置300px*300px大小的图片
Bitmap bitmap = decoder.decodeRegion(new Rect(imgWidth/2 - 150, imgHeight/2 - 150, imgWidth/2 + 150, imgHeight/2 + 150), options);
imageView.setImageBitmap(bitmap);
本文主要参考如下博客:
https://blog.csdn.net/forevercbb/article/details/81205681