将大图加载到内存中老是使人痛苦,由于咱们常常会在应用的崩溃报告中看到OOM(Out Of Memory)的bug。你们都知道,Android系统的内存有限。咱们必须牢记这一点。android
stackoverflow上有不少关于大图加载的问题,当你的应用程序遇到OOM的时候,你能够选择直接复制粘贴其中的答案来解决这个问题。所以,你彻底能够略过本篇文章,但我想介绍一些加载大图的基础知识及其实际工做的原理。bash
我只想解释图片解码背后的逻辑。我建议你使用Picasso或Glide来加载图片。没有必要从新发明轮子。markdown
这很简单。你只须要使用BitmapFactory来解码你的图片。ide
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.hqimage);
imageView.setImageBitmap(bitmap);
复制代码
看起来一切正常。可是我要告诉你一个问题,让咱们看看这张解码过的图片在内存中实际占据的空间大小。oop
bitmap.getByteCount()方法将返回bitmap的大小。 这张图片在内存中的大小为12262248字节,至关于12.3 MB。是的,你可能会感到困惑。由于这张图片在磁盘上的实际大小约为3.5 MB,而getByteCount()方法返回的值远大于它。缘由以下:spa
存储在磁盘上的图片是被压缩过的(以JPG,PNG或相似的格式存储)。 一旦将图片加载到内存中,它就再也不被压缩,并占用尽量多的图片的全部像素所需的内存空间。code
BitmapFactory能够为咱们提供图片的元数据。咱们可使用这个类来实现第一步。orm
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.mipmap.hqimage, options); 复制代码
咱们将BitmapFactory.Options实例传递给BitmapFactory.decodeSource()方法。options.inJustDecodeBounds = true 是什么意思?这句代码是指咱们不想将图片加载到内存中。咱们只想获取图片的相关信息(宽度,高度等),并使用这些信息来计算缩放比例。对象
咱们运行这段代码并打印图片的信息:图片
options.outHeight : 1126
options.outWidth : 2000
options.bitmap : null
复制代码
它只输出了图片的高度和宽度。
如今咱们须要计算inSampleSize。什么是inSampleSize? inSampleSize是BitmapFactory.Options类的一个属性,用于设置图片的缩放比。
若是咱们有一张尺寸为1000x1000的图片,而且在解码以前设置inSampleSize的值为2, 那么解码以后,咱们将获得一张尺寸为500x500的图片。 若是咱们有一张尺寸为200x400的图片,而且在解码以前设置inSampleSize的值为5, 那么解码以后,咱们将获得一张尺寸为40x80的图片。
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; options.inSampleSize = 3; BitmapFactory.decodeResource(getResources(), R.mipmap.hqimage, options); 复制代码
咱们能够直接这样作吗?不能,由于咱们不知道图片大小是多少。若是它是小图片,而且咱们使其更小,那么咱们的用户看到的就是一些像素而不是图像。有一些图片须要缩放5倍,另外一些图片则须要缩放2倍。咱们不能将缩放比设置为一个常数,因此咱们必须根据图片的大小来计算它的值。
如何计算inSampleSize的值取决于您。个人意思是,你能够根据你的须要编写inSampleSize的计算方法。在android官方文档中,计算结果是2的幂次方。
options.inSampleSize = calculateInSampleSize(options, 500,500); options.inJustDecodeBounds = false; Bitmap smallBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.hqimage, options); 复制代码
这里咱们将inJustDecodeBounds的值设为false,并得到了一个bitmap对象。如今,bitmap.getByteCount()方法返回的图片大小是3.1 MB。这是它在内存中的大小。正如我以前说过的,图片存储在磁盘上时会被压缩。当咱们将它们加载到内存中时它们会占据更大的内存空间。经过上面这种方法,咱们将它在内存中占据的空间大小从12.3 MB减小到了3.1 MB,减小了75%。
咱们还可使用Bitmap的compress方法对磁盘上的图片进行压缩。
咱们来看看在不改变图片质量的状况下图片被压缩后的大小。 100表示与原图保持相同的质量。
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
byte[] bitmapdata = bos.toByteArray();
复制代码
经过计算获得图片在磁盘上的大小为1.6 MB。
咱们把compress方法中的质量参数改成50,并再次计算图片大小。
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, bos);
复制代码
经过计算获得图片在磁盘上的大小为24.4 KB。
注意:在改变compress方法中的质量参数的时候,压缩格式应该是.JPEG。设置为PNG格式的时候,修改是无效的。
下面是一张对比效果图: