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

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

原文地址:https://www.cnblogs.com/RGogoing/p/4740283.html