Android学习笔记之BitmapFactory.Options实现图片资源的加载...

时间:2021-08-22 17:45:27

PS:小项目总算是做完了...历经20多天...素材,设计,以及实现全由自己完成...心力憔悴啊...该写写博客记录一下学习到的东西了...

学习内容:

1.使用BitmapFactory.Options实现图片资源的加载...防止OOM的产生...

  我们在设计和制作app的时候,有时我们需要去动态的加载图片资源数据,图片的大小将决定读取图片资源所耗费物理内存...Android对内存的要求是非常苛刻的...如果图片的资源过大,那么就会出现OOM,也就是所说的内存溢出...这样程序就不得不被终止掉...因此我们需要避免这种情况的发生...这里我们需要使用到BitmapFactory.Options...

  BitmapFactory.Options其实是一个辅助类,这个类提供了创建Bitmap类的接口,真正完成图像处理的类其实是Bitmap,但是由于这个类的构造函数是私有的,因此是无法在其他的类外进行实例化对象的,因此BitmapFactory.Options充当了这个辅助类,对外暴露接口,这样,我们就可以真正的调用Bitmap中的方法了...

  BitmapFactory.Options通过decodeFile()的方法去获取图片资源的位置.那么获取到了资源位置后,我们就可以对图片进行解码操作,因为我们都知道,想要获取到一个图片,首先要从图片的码流开始,对码流进行解析才会得到相应的图片...那么这个解码的过程就是使用decodeStream()方法进行解码,解码也是可以限制的...BitmapFactory.Options这个类的Options其实就是对解码进行一个限制...

  那么再说一下Options,Options有几个属性,第一个inJustDecodeBounds(这是一个布尔值,true和false两个属性),inJustDecodeBounds=true的时候,那么代表对现在的这张图片进行非完全解码,其实说白了就是不给这个图片资源分配任何的内存,只是获取这个图片的基本信息(比如说:长度和宽度),不分配内存的原因想必大家都知道了,就是防止图片过大的问题,如果图片过大,那么我们获取到图片长度和宽度后,需要对图片进行一个压缩的操作,那么这个压缩就涉及第二个参数,inSampleSize(int 类型,可以是任意值)...这个值的获取取决于我们压缩操作的方式,这个压缩方式是需要我们自己去实现的,最后通过压缩的方式去获取inSampleSize的值,通过这个值指定图片缩放的大小...最后将inJustDecodeBounds=false这样就可以得到缩放后的图片了...如果图片满足正常的范围之内,那么就直接进行显示就可以了...没必要再进行压缩操作...

  还是来一段代码,来方便大家的理解...

 private Bitmap decodeFile(File f){
try {
//解码图像大小,对图片进行缩放...防止图片过大导致内存溢出...
BitmapFactory.Options o = new BitmapFactory.Options();//实例化一个对象... o.inJustDecodeBounds = true;//这个就是Options的第一个属性,设置为true的时候,不会完全的对图片进行解码操作,不会为其分配内存,只是获取图片的基本信息... BitmapFactory.decodeStream(new FileInputStream(f),null,o); //以码流的形式进行解码.... /*
* 下面也就是对图片进行的一个压缩的操作...如果图片过大,最后会根据指定的数值进行缩放...
* 找到正确的刻度值,它应该是2的幂.
* 这里我指定了图片的长度和宽度为70个像素...
*
* */ final int REQUIRED_SIZE=70;
int width_tmp=o.outWidth, height_tmp=o.outHeight;
int scale=1;
while(true){
if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
break;
width_tmp/=2;
height_tmp/=2;
scale*=2;
} BitmapFactory.Options o2 = new BitmapFactory.Options(); //这里定义了一个新的对象...获取的还是同一张图片...
o2.inSampleSize=scale; //对这张图片设置一个缩放值...inJustDecodeBounds不需要进行设置...
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2); //这样通过这个方法就可以产生一个小的图片资源了...
} catch (Exception e) {}
return null;
}

  这里只是粘贴了缩放过程的一个代码,通过这段代码,我们就可以实现大图片缩放为小型图片,防止OOM的发生...这只是一个小的函数,最后我会给出一个源码提供给大家去下载,方便大家去学习...这里还需要说明一些问题...就是这个inSampleSize值的设置.这个值如果设置为2,那么图片就缩放4倍...如果为3,那么缩放的程度就为9倍...以此类推...

  还有这个decodeStream()方法只是一个把数据封装成流的形式对码流进行一个数据传递...真正完成图片绘制的是其内部过程...我们还是来看一下源代码,方便大家的理解...

/*
*这是源码的实现过程,说实话,我也看不懂所有的东西...因为自己也是个小菜鸟...
*不过我们可以分析一下...
*/
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
// we don't throw in this case, thus allowing the caller to only check
// the cache, and not force the image to be decoded.
if (is == null) {
return null;
} // we need mark/reset to work properly if (!is.markSupported()) {
is = new BufferedInputStream(is, DECODE_BUFFER_SIZE);
} // so we can call reset() if a given codec gives up after reading up to
// this many bytes. FIXME: need to find out from the codecs what this
// value should be.
is.mark(1024); Bitmap bm;
boolean finish = true;
/*
*我们来看下面这个函数...
*/
if (is instanceof AssetManager.AssetInputStream) {//这块是一个判断的过程,判断的东西就是,我们这个图片资源到底来自于什么地方,如果满足这个if条件,那么这个图片资源属于drawable文件下的资源...
final int asset = ((AssetManager.AssetInputStream) is).getAssetInt();
/*
*这里就是设置缩放的一个过程,其中包含一些参数的设置...
*比如说缩放的参数,以及目标面积的大小设定...
*/
if (opts == null || (opts.inScaled && opts.inBitmap == null)) {
float scale = 1.0f;
int targetDensity = 0;
if (opts != null) {
final int density = opts.inDensity;
targetDensity = opts.inTargetDensity;
if (density != 0 && targetDensity != 0) {
scale = targetDensity / (float) density;
}
}
/*
*下面这个函数的源码,我也看不到了...应该就是一个解码的操作过程...
*/
bm = nativeDecodeAsset(asset, outPadding, opts, true, scale);
if (bm != null && targetDensity != 0) bm.setDensity(targetDensity); finish = false;
} else {
/*
*这块就是直接解码操作,这个else满足的条件是图片资源满足指定的大小,因此我们不需要传递scale参数了...也就是代表不用进行缩放...
*/
bm = nativeDecodeAsset(asset, outPadding, opts);
}
} else { //这里满足的条件想必大家知道,就是图片资源可能来自于其他地方...
// pass some temp storage down to the native code. 1024 is made up,
// but should be large enough to avoid too many small calls back
// into is.read(...) This number is not related to the value passed
// to mark(...) above.
/*
*通过byte流的形式对图片进行获取,这个也很好理解,如果想获取图片资源,那么必须要经过流的形式,对资源数据进行封装...然后获取...
*上面之所以不用流的形式,是因为drawable中的资源有特定的获取方式...
*/
byte [] tempStorage = null;
if (opts != null) tempStorage = opts.inTempStorage;
if (tempStorage == null) tempStorage = new byte[16 * 1024]; /*
*还是同理,设置参数....
*/
if (opts == null || (opts.inScaled && opts.inBitmap == null)) {
float scale = 1.0f;
int targetDensity = 0;
if (opts != null) {
final int density = opts.inDensity;
targetDensity = opts.inTargetDensity;
if (density != 0 && targetDensity != 0) {
scale = targetDensity / (float) density;
}
} bm = nativeDecodeStream(is, tempStorage, outPadding, opts, true, scale);
if (bm != null && targetDensity != 0) bm.setDensity(targetDensity); finish = false;
} else {
bm = nativeDecodeStream(is, tempStorage, outPadding, opts);
}
} if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
//这个方法,将会被最终调用..也就是下面的方法...
return finish ? finishDecode(bm, outPadding, opts) : bm;
}
/*
*这个方法是当完成解码操作后需要调用的方法...
*/
private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) {
if (bm == null || opts == null) {
return bm;
} final int density = opts.inDensity;
if (density == 0) {
return bm;
} bm.setDensity(density);
final int targetDensity = opts.inTargetDensity;
if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
return bm;
}
byte[] np = bm.getNinePatchChunk();
int[] lb = bm.getLayoutBounds();
final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
/*
*这个函数重要部分就是下面,我们可以看到,通过对参数的一些设定..最后会调用 Bitmap.createScaledBitmap方法...
*/
if (opts.inScaled || isNinePatch) {
float scale = targetDensity / (float) density;
if (scale != 1.0f) {
final Bitmap oldBitmap = bm;
bm = Bitmap.createScaledBitmap(oldBitmap,
Math.max(1, (int) (bm.getWidth() * scale + 0.5f)),
Math.max(1, (int) (bm.getHeight() * scale + 0.5f)), true);
if (bm != oldBitmap) oldBitmap.recycle(); if (isNinePatch) {
np = nativeScaleNinePatch(np, scale, outPadding);
bm.setNinePatchChunk(np);
}
if (lb != null) {
int[] newLb = new int[lb.length];
for (int i=0; i<lb.length; i++) {
newLb[i] = (int)((lb[i]*scale)+.5f);
}
bm.setLayoutBounds(newLb);
}
} bm.setDensity(targetDensity);
} return bm;
}

 我们可以不完全弄懂这个源码到底是怎么回事,但是我们通过源码可以知道其中到底是以怎样的过程进行实现的,这才是看源码的目的...如果还是有更深入研究的读者...那么您可以完全弄懂...这里我们可以看到最后的调用时Bitmap.createScaledBitmap方法,接着Bitmap.createScaledBitmap方法将调用Bitmap createBitmap...这个方法才是最终实现把图片显示在画布上的一个最终函数,这个函数通过使用Canvas最后在画布上将图片描画出来...这是这个函数的源码,我就不进行解释了,其实这个源码比上面那个更简单,容易理解,其实就是根据这些参数把图片展示出来,比如说获取图片的高度和宽度,缩放的大小,图片的编码格式,最后通过调用Paint画笔,结合Canvas,这里我们能够发现,最后画出来的画布是一个矩形,那么画出来的图片必然也是一个矩形..最后把画出来的图片进行返回..最后就显示在画布之上了...

 public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height,
Matrix m, boolean filter) { checkXYSign(x, y);
checkWidthHeight(width, height);
if (x + width > source.getWidth()) {
throw new IllegalArgumentException("x + width must be <= bitmap.width()");
}
if (y + height > source.getHeight()) {
throw new IllegalArgumentException("y + height must be <= bitmap.height()");
} // check if we can just return our argument unchanged
if (!source.isMutable() && x == 0 && y == 0 && width == source.getWidth() &&
height == source.getHeight() && (m == null || m.isIdentity())) {
return source;
} int neww = width;
int newh = height;
Canvas canvas = new Canvas();
Bitmap bitmap;
Paint paint; Rect srcR = new Rect(x, y, x + width, y + height);
RectF dstR = new RectF(0, 0, width, height); Config newConfig = Config.ARGB_8888;
final Config config = source.getConfig();
// GIF files generate null configs, assume ARGB_8888
if (config != null) {
switch (config) {
case RGB_565:
newConfig = Config.RGB_565;
break;
case ALPHA_8:
newConfig = Config.ALPHA_8;
break;
//noinspection deprecation
case ARGB_4444:
case ARGB_8888:
default:
newConfig = Config.ARGB_8888;
break;
}
} if (m == null || m.isIdentity()) {
bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha());
paint = null; // not needed
} else {
final boolean transformed = !m.rectStaysRect(); RectF deviceR = new RectF();
m.mapRect(deviceR, dstR); neww = Math.round(deviceR.width());
newh = Math.round(deviceR.height()); bitmap = createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig,
transformed || source.hasAlpha()); canvas.translate(-deviceR.left, -deviceR.top);
canvas.concat(m); paint = new Paint();
paint.setFilterBitmap(filter);
if (transformed) {
paint.setAntiAlias(true);
}
} // The new bitmap was created from a known bitmap source so assume that
// they use the same density
bitmap.mDensity = source.mDensity; canvas.setBitmap(bitmap);
canvas.drawBitmap(source, srcR, dstR, paint);
canvas.setBitmap(null); return bitmap;
}

  这样就实现了通过对图片大小的缩放,避免OOM的发生...最后放上面一个源码....提供大家下载....只不过这个源码是通过从服务器上下载图片,通过解析XML文件,然后对数据进行获取..然后对图片进行缩放,最后以ListView的形式进行显示...图片的缩放只隶属于一个小的模块....

源码位置:http://files.cnblogs.com/files/RGogoing/daimarufeng.zip