在android 中加载一张图片,如果图片过大就有可能会出现内存溢出,特别是在加载数据过多的时候,像ListView 和GridView等重复列表中,因此在处理Android图片防止内存溢出就显得特别的重要,也是很多面试中经常问到的问题,面试官通常都会让你谈谈如何就行内存优化,那么图片加载优化就是一个非常重要的内容。首先我们来看一张图片加载到内存中所占据的内存大小的计算方法。
*在加载图片是需要的内存是怎么计算的?
一张图片占用内存=图片长度图片宽度单位像素占用的字节数。
其中单位像素占用的字节数是有图片的参数决定的,在android中通过BitmapFactory.Options的inPreferredConfig变量决定的。inPreferredConfig为Bitmap.Config类型:
如下:
Bitmap.Config
1. ALPHA_8
Each pixel is stored as a single translucency (alpha) channel.
This is very useful to efficiently store masks for instance. No color information is stored. With this configuration, each pixel requires 1 byte of memory.
此时图片只有alpha值,没有RGB值,一个像素占用一个字节
This field is deprecated. Because of the poor quality of this configuration, it is advised to use ARGB_8888instead.
这种格式的图片,看起来质量太差,已经不推荐使用。
Each pixel is stored on 2 bytes. The three RGB color channels and the alpha channel (translucency) are stored with a 4 bits precision (16 possible values.) This configuration is mostly useful if the application needs to store translucency information but also needs to save memory. It is recommended to use ARGB_8888 instead of this configuration.
一个像素占用2个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占4个bites,共16bites,即2个字节
Each pixel is stored on 4 bytes. Each channel (RGB and alpha for translucency) is stored with 8 bits of precision (256 possible values.) This configuration is very flexible and offers the best quality. It should be used whenever possible
一个像素占用4个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占8个bites,共32bites,即4个字节
这是一种高质量的图片格式,电脑上普通采用的格式。它也是Android手机上一个BitMap
Each pixel is stored on 2 bytes and only the RGB channels are encoded: red is stored with 5 bits of precision (32 possible values), green is stored with 6 bits of precision (64 possible values) and blue is stored with 5 bits of precision. This configuration can produce slight visual artifacts depending on the configuration of the source. For instance, without dithering, the result might show a greenish tint. To get better results dithering should be applied. This configuration may be useful when using opaque bitmaps that do not require high color fidelity.
一个像素占用2个字节,没有alpha(A)值,即不支持透明和半透明,Red(R)值占5个bites ,Green(G)值占6个bites ,Blue(B)值占5个bites,共16bites,即2个字节.对于没有透明和半透明颜色的图片来说,该格式的图片能够达到比较的呈现效果,相对于ARGB_8888来说也能减少一半的内存开销。因此它是一个不错的选择。另外我们通过android.content.res.Resources来取得一个张图片时,它也是以该格式来构建BitMap的
从Android4.0开始,该选项无效。即使设置为该值,系统任然会采用 ARGB_8888来构造
Bitmap 原理:
每一张图片显示在屏幕上之前都必须先加入内存处理,然后输出到屏幕上。那么我们又需要显示这个图片,又不能在完全确认它加入内存是安全(不会outOfMemory)的的时候怎么办呢,android提供了一个方法,就是读取图片的信息时不让其加入内存,我们读取图片信息后进行一些尺寸上的压缩处理后再次加入内存显示就安全了。(为了避免java.lang.OutOfMemory 的异常,我们需要在真正解析图片之前检查它的尺寸(除非你能确定这个数据源提供了准确无误的图片且不会导致占用过多的内存)。)
为了避免内存泄露,因此在读取图片的时候就要进行压缩,通常的做法就是先获取图片的尺寸,然后再按照尺寸和屏幕尺寸比例进行压缩:如下:
BitmapFactory.Options optins=new BitmapFactory.Options();
options.inJustDecodeBounds=true; //表示不让其加入内存 只是获取图片的一些类型和宽高信息
BitmapFactory.decodeResources(getResources(),R.id.image,options); //解析图片资源的来源
int imgHeight=options.outHeight; //获得图片高度
int imgWidth=options.outWidth; //获得图片宽度
String imageType=options.outMimeType; //图片类型
接下来就来加载一个尺寸缩小的版本到内存中:
为了告诉解码器加载一个缩小版本的图片到内存中,需要在BitmapFactory.options中设置inSampleSize的值。例如,一个分辨率为2048*1536的图片,如果设置inSampleSize=4,那么会产出一个大约512*384大小的bitmap,加载这张图片需要的内存大概为0.75M(512*384*4/1024/1024),如果加载原图,大约会要12M内存(2048*1536*4/1024/1024)(前提是Bitmap的配置是ARGB_8888),下面贴出代码:inSampleSize数字为几表示按照几倍进行压缩。
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
注: 设置inSampleSize为2的幂是因为解码器最终还是会对非2的幂的数据进行向下处理,获取到最靠近2的幂的数,可以参考inSampleSize的文档
接下来就实战一下,首先创建一个项目,LoadImage: Activity代码如下:
public int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
options.inJustDecodeBounds = true;
options.inPreferredConfig= Bitmap.Config.ARGB_8888;
BitmapFactory.decodeResource(getResources(),R.drawable.abc,options);
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
这个代码很简单,上面基本已经讲过,就是按照我们控件要显示的宽高来计算压缩的比例,得到inSampleSize;
接下来的代码更简单:
private Bitmap loadImage() {
BitmapFactory.Options options = new BitmapFactory.Options();
int j = calculateInSampleSize(options, 200, 250);
options.inSampleSize = j;
options.inJustDecodeBounds = false;
int outWidth = options.outWidth;
int outHeight = options.outHeight;
Log.d("MainActivity", "outH:" + outHeight + ":outW:" + outWidth);
return BitmapFactory.decodeResource(getResources(), R.drawable.abc, options);
}
这个最关键的一步就是 options.inJustDecodeBounds = false; 表示让图片真正加入内存了,但这已经按比例压缩了,所以不用担心内存溢出。
最后我们设置显示就行了
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.image_view);
imageView = (ImageView) findViewById(R.id.image_view);
Bitmap bitmap = loadImage();
}
接下来贴出布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:scaleType="center"
android:src="@drawable/myimage"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"/>
LinearLayout>
是不是很简单,如果用android studio 的同学可以直接看到加载的内存大小,你把传入的显示控件要求的宽高设置为不同,其内存需求也是不同的。具体内存可能并不是严格的图片加载内存的大小所需要的那么大,这是因为即使是一个空应用也要消耗内存才能跑起来,不过大致的内存还是很接近图片需要的内存的。
好了,Bitmap最简单的用法就到这里,下一篇文章减少更高级的用法。