我们通过眼睛来观察世界,眼睛通过光的反射,折射将世间的万物映射到我们
的眼睛里。至于是在眼睛里生成图片传递给大脑,还是眼睛将映射得来的
这些图片,了解了世界。
图片何其重要,几乎每一个app都需要加载图片,然而并不是每一个app
都很好的加载了图片。
图片是什么,我们先说一下图片的属性:形状,大小,颜色。
这一节里我们讨论的是图片最简单的属性:大小。
每一部安卓手机对于加载图片使用的内存都是有限制的,据我所知,通常
的都是16MB。然而,就像世界上没有一模一样的叶子一样。图片也是多种
多样的,它们大小不一,形状各异。那么如果超过16MB会发生什么错误呢
你的手机够旧,配置够低,那么很容易的就会报Out of memory的异常,也就是一些人讲的OOM异常。
也许你会发现你的图片并没有超过16MB啊,为何还是报错,那是因为手
机在加载图片的时候需要的内存可能会比图片本身大,(这应该是涉及到图
片的复杂程度吧)。
如何避免OOM异常呢,手机内存的限制通过软件是无法改变的,那么我
们只好在图片本身上下文章。图片并不是一成不变的,只要我们能通过一定
的比例缩放图片就不需要那么大的内存了。一句话概括就是我们要尽量不破
坏原图表达的信息的基础上减小加载图片所需要的内存。而BitmapFactory.Options这个类就是为此而生的。
package com.example.loadbigbitmap;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
public class ImageUtil {
/**
*
* @param options
* 根据原图获得的options,通过它可以获得原图的高,宽,类型。因为inJustDecodeBounds设置成true了,
* 所以并不会真的解码生成一个bitmap,也就不存在占用内存的问题了。
* @param reqWidth 要求的宽
* @param reqHeight 要求的高
* @return 返回设置好的inSampleSize。
*/
/*
* 根据原图的高度和宽度和要求的高度和宽度计算出inSampleSize的大小。
* 通过设置inSampleSize可以修改显示该图片需要的内存大小。比如原来需要20M
* ,那么如果inSampleSize=5,则实际显示的时候只需要5M就够了。
*/
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;
// Calculate the largest inSampleSize value that is a power of 2 and
// keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
/**
*
* @param res 使用getResources()即可
* @param resId 图片的id
* @param reqWidth 要求的高度
* @param reqHeight 要求的宽度
* @return 将会返回一个已经根据要求的高度和宽度缩放的bitmap
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res,
int resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
/*
* 如果我们把它设为true,那么BitmapFactory.decodeFile(String path, Options
* opt)并不会真的返回一个Bitmap给你,它仅仅会把它的宽,高取回来给你,这样就不会占用太多的内存,也就不会那么频繁的发生OOM了。
*/
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
// 把inJustDecodeBounds设置为false,这样通过BitmapFactory生成bitmap时就会返回一个真的bitmap而不是null;
options.inJustDecodeBounds = false;
//根据图片的来源确定到底使用哪一种生成BitmapFactory的方法。
return BitmapFactory.decodeResource(res, resId, options);
}
}
MainActivity
package com.example.loadbigbitmap;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.widget.ImageView;
public class MainActivity extends Activity {
/*
* 显示图片
*/
private ImageView image;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intiUI();
/*
* 图片的原始宽度,高度,类型: 1024;768;image/jpeg
*/
Bitmap bitmap = ImageUtil.decodeSampledBitmapFromResource(getResources(), R.drawable.default_bg, 100, 100);
image.setImageBitmap(bitmap);
}
private void intiUI() {
image = (ImageView) findViewById(R.id.image);
}
}
接下来我就会一步一步的从bitmap的生成到BitmapFactory.Options类的
对象的属性的设置。来讲解如何缩放大容量Bitmap。
BitmapFactory有很多方式生成Bitmap。(decodeByteArray(), decodeFile(), decodeResource(), etc.)
这里我们使用decodeResource()方法生成bitmap,其他方法大同小异,可以根据自己的需求修改。
BitmapFactory.decodeResource(getResources(),R.drawable.default_bg, options);
第一个参数是Resources,直接getResources()就行了,第二个参数是图片的id,第三个参数就是BitmapFactory.Options了,它通过:
final BitmapFactory.Options options = new BitmapFactory.Options();
来获取。
通过options.inSampleSize的设置控制显示图片所需要的大小
整篇文章所需要做的就是设置这个值,假设inSampleSize=5.则根据这个options作为参数,加载的图片的内存就会是原来的1/5。
我们应该根据图片原有的高度,宽度,与需要显示的高度宽度的比例来计算出这个inSampleSize的大小,如何计算请参考代码ImageUtil类里的calculateInSampleSize方法。
假设需要显示的高度和宽度已经知道了,如何取得图片原有的高度和宽度呢?
这时候我们就需要先设置options.inJustDecodeBounds = true;设置成true后再执行BitmapFactory.decodeResource(res, resId, options);方法则安卓系统并不会真的去解码生成一个bitmap,而是返回一个NULL,但是这执行完后的options就会拥有图片的高度,宽度,类型等属性了。
有了这个属性,再执行
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
就对inSampleSize 的值设置成功了。
这时候再执行
// 把inJustDecodeBounds设置为false,这样通过BitmapFactory生成bitmap时就会返回一个真的bitmap而不是null;
options.inJustDecodeBounds = false;
//根据图片的来源确定到底使用哪一种生成BitmapFactory的方法。
return BitmapFactory.decodeResource(res, resId, options);
便能得到一个缩小之后的bitmap。且不会再占用很大的内存。