BitmapFactory.Options详解

 

在通过BitmapFactory.decodeFile(String path)方法将突破转成Bitmap时,遇到大一些的图片,我们经常会遇到OOM(Out Of Memory)的问题。怎么避免它呢? 这就用到了我们上面提到的BitmapFactory.Options这个类。

 

BitmapFactory.Options这个类,有一个字段叫做 inJustDecodeBounds 。SDK中对这个成员的说明是这样的: If set to true, the decoder will return null (no bitmap), but the out… 也就是说,如果我们把它设为true,那么BitmapFactory.decodeFile(String path, Options opt)并不会真的返回一个Bitmap给你,它仅仅会把它的宽,高取回来给你,这样就不会占用太多的内存,也就不会那么频繁的发生OOM了。

 

示例代码如下:

  1. BitmapFactory.Options options = new BitmapFactory.Options();
  2. options.inJustDecodeBounds = true;
  3. Bitmap bmp = BitmapFactory.decodeFile(path, options);

复制代码

这段代码之后,options.outWidth 和 options.outHeight就是我们想要的宽和高了。

有了宽,高的信息,我们怎样在图片不变形的情况下获取到图片指定大小的缩略图呢? 比如我们需要在图片不变形的前提下得到宽度为200的缩略图。 那么我们需要先计算一下缩放之后,图片的高度是多少 

  1. int height = options.outHeight * 200 / options.outWidth;
  2. options.outWidth = 200;
  3. options.outHeight = height; 
  4. options.inJustDecodeBounds = false;
  5. Bitmap bmp = BitmapFactory.decodeFile(path, options);
  6. image.setImageBitmap(bmp);

复制代码

这样虽然我们可以得到我们期望大小的ImageView 但是在执行BitmapFactory.decodeFile(path, options);时,并没有节约内存。要想节约内存,还需要用到BitmapFactory.Options这个类里的 inSampleSize 这个成员变量。 我们可以根据图片实际的宽高和我们期望的宽高来计算得到这个值。

  1. inSampleSize = options.outWidth / 200;

另外,为了节约内存我们还可以使用下面的几个字段:

  1. options.inPreferredConfig = Bitmap.Config.ARGB_4444;    // 默认是Bitmap.Config.ARGB_8888
  2. options.inPurgeable = true;
  3. options.inInputShareable = true;

 

BitmapFactory.Options.inSampleSize

设置恰当的inSampleSize可以使BitmapFactory分配更少的空间以消除该错误。inSampleSize的具体含义请参考SDK文档。例如:

BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 4;
Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);

设置恰当的inSampleSize是解决该问题的关键之一。BitmapFactory.Options提供了另一个成员inJustDecodeBounds。

BitmapFactory.Options opts = new BitmapFactory.Options();

opts.inJustDecodeBounds = true;

Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);

设置inJustDecodeBounds为true后,decodeFile并不分配空间,但可计算出原始图片的长度和宽度,即opts.width和opts.height。有了这两个参数,再通过一定的算法,即可得到一个恰当的inSampleSize。

查看Android源码,我们得知,为了得到恰当的inSampleSize,Android提供了一种动态计算的方法。

public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
    int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
    int roundedSize;
    if (initialSize <= 8) {
        roundedSize = 1;
        while (roundedSize < initialSize) {
            roundedSize <<= 1;
        }
    } else {
        roundedSize = (initialSize + 7) / 8 * 8;
    }
    return roundedSize;
}

private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
    double w = options.outWidth;
    double h = options.outHeight;
    int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
    int upperBound = (minSideLength == -1) ? 128 : (int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));
    if (upperBound < lowerBound) {
        // return the larger one when there is no overlapping zone.
        return lowerBound;
    }
    if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
        return 1;
    } else if (minSideLength == -1) {
        return lowerBound;
    } else {
        return upperBound;
    }
} 

使用该算法,就可动态计算出图片的inSampleSize。

BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imageFile, opts);
opts.inSampleSize = computeSampleSize(opts, -1, 128*128);  
opts.inJustDecodeBounds = false;
try {
   bmp = BitmapFactory.decodeFile(imageFile, opts);
   imageView.setImageBitmap(bmp);
} catch (OutOfMemoryError err) {
}

源代码如下:

 

    public static Bitmap createImageThumbnail(String filePath){
         Bitmap bitmap = null;
         BitmapFactory.Options opts = new BitmapFactory.Options();
         opts.inJustDecodeBounds = true;
         BitmapFactory.decodeFile(filePath, opts);

         opts.inSampleSize = computeSampleSize(opts, -1, 128*128);
         opts.inJustDecodeBounds = false;

         try {
             bitmap = BitmapFactory.decodeFile(filePath, opts);
         }catch (Exception e) {
            // TODO: handle exception
        }
        return bitmap;
    }

    public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
        int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
        int roundedSize;
        if (initialSize <= 8) {
            roundedSize = 1;
            while (roundedSize < initialSize) {
                roundedSize <<= 1;
            }
        } else {
            roundedSize = (initialSize + 7) / 8 * 8;
        }
        return roundedSize;
    }

    private static int computeInitialSampleSize(BitmapFactory.Options options,int minSideLength, int maxNumOfPixels) {
        double w = options.outWidth;
        double h = options.outHeight;
        int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
        int upperBound = (minSideLength == -1) ? 128 :(int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));
        if (upperBound < lowerBound) {
            // return the larger one when there is no overlapping zone.
            return lowerBound;
        }
        if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
            return 1;
        } else if (minSideLength == -1) {
            return lowerBound;
        } else {
            return upperBound;
        }
    }

 

项目实战代码:

/**
     * 不full load图片,先根据一定的算法缩小再load
     * 
     * @param path
     * @return
     */
    public static Bitmap loadBitmapFromFile(String path, int thumbWidth, int thumbHeight) {
        Bitmap bmp = null;
        
        BitmapFactory.Options opts = new BitmapFactory.Options();
        /*
         * 设置inJustDecodeBounds为true后,decodeFile并不分配空间,但可计算出原始图片的长度和宽度,即opts.width和opts.height。
         * 有了这两个参数,再通过一定的算法,即可得到一个恰当的inSampleSize
         */
        opts.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, opts);
        
        opts.inSampleSize = calculateInSampleSize(opts, thumbWidth, thumbHeight);
        //这里一定要将其设置回false,因为之前我们将其设置成了true     
        opts.inJustDecodeBounds = false;
        
        try {
            bmp = BitmapFactory.decodeFile(path, opts);
        } catch (OutOfMemoryError e) {
            Logger.e(TAG, e.getMessage());
        }
        
        return bmp;
    }
    
    //计算图片的缩放值
    public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }

        return inSampleSize;
    }
    
    
    /**
     * 不full load图片,先根据一定的算法缩小再load
     * 
     * @param path
     * @return
     */
    public static Bitmap loadBitmapFromFile(String path) {
        Bitmap bmp = null;
        
        BitmapFactory.Options opts = new BitmapFactory.Options();
        /*
         * 设置inJustDecodeBounds为true后,decodeFile并不分配空间,但可计算出原始图片的长度和宽度,即opts.width和opts.height。
         * 有了这两个参数,再通过一定的算法,即可得到一个恰当的inSampleSize
         */
        opts.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, opts);
        
        opts.inSampleSize = computeSampleSize(opts, -1, 512 * 512);
        //这里一定要将其设置回false,因为之前我们将其设置成了true     
        opts.inJustDecodeBounds = false;
        
        try {
            bmp = BitmapFactory.decodeFile(path, opts);
        } catch (OutOfMemoryError e) {
            Logger.e(TAG, e.getMessage());
        }
        
        return bmp;
    }
    
    public static Bitmap loadBitmapFromUrl(Context ctx, String url) {
        File path =  ctx.getCacheDir();
        String hashedURLString = hashURLString(url);
        
        String finalPath = new File(path, hashedURLString).getAbsolutePath();
        
        boolean ok = FileUtils.saveUrl(finalPath, url);
        if (ok) {
            return loadBitmapFromFile(finalPath);
        }
        
        return null;
    }    
    /**
     * 将图片先放到临时空间中,再读取图片。full load
     * 
     * @param path
     * @return
     */
    public static Bitmap loadBitmapFromFileX(String path) {
        BitmapFactory.Options bfOptions = new BitmapFactory.Options();
        bfOptions.inDither = false;
        bfOptions.inPurgeable = true;
        bfOptions.inTempStorage = new byte[12 * 1024];
        File file = new File(path);
        FileInputStream fs = null;

        Bitmap bmp = null;

        try {
            fs = new FileInputStream(file);
            bmp = BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (OutOfMemoryError e) {
            e.printStackTrace();
        } finally {
            try {
                if (fs != null) {
                    fs.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return bmp;
    }
    
    public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
        int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);

        int roundedSize;
        if (initialSize <= 8) {
            roundedSize = 1;
            while (roundedSize < initialSize) {
                roundedSize <<= 1;
            }
        } else {
            roundedSize = (initialSize + 7) / 8 * 8;
        }

        return roundedSize;
    }

    private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
        double w = options.outWidth;
        double h = options.outHeight;

        int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
        int upperBound = (minSideLength == -1) ? 128 : (int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));

        if (upperBound < lowerBound) {
            return lowerBound;
        }

        if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
            return 1;
        } else if (minSideLength == -1) {
            return lowerBound;
        } else {
            return upperBound;
        }
    }
    
    private static String hashURLString(String urlString) {
        try {
            // Create MD5 Hash
            MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
            digest.update(urlString.getBytes());
            byte messageDigest[] = digest.digest();
            
            // Create Hex String
            StringBuffer hexString = new StringBuffer();
            for (int i=0; i<messageDigest.length; i++)
                hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
            return hexString.toString();
            
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        
        //fall back to old method
        return urlString.replaceAll("[^A-Za-z0-9]", "#");
    }

 

 

posted @ 2014-03-06 17:31  Yang2  阅读(1876)  评论(0编辑  收藏  举报