关于BitmapFactory.decode

最近使用picasso遇到一个问题,picasso load在某些机子上load本地图片load不出来java.io.IOException: Cannot reset picasso官方也没有解决这个问题,只好自己写一个加载本地资源的库,也遇到了不少坑

Decode内存占用过大

这个问题还是由于自己对于BitmapFactory decode了解的不够仔细,先贴出原始代码吧


    /**
     * 根据imageview宽高 decode 图片
     */
    public static Bitmap decodeStream(InputStream is, ImageView imageView) {
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;

        //获取imageview想要显示的宽和高
        ImageSize imageViewSize = getImageViewSize(imageView);
        opts.inSampleSize = ImageUtils.calculateInSampleSize(opts,
                imageViewSize.getWidth(), imageViewSize.getHeight());

        opts.inJustDecodeBounds = false;

        return BitmapFactory.decodeStream(is, null, opts);
    }

这段代码貌似乍看没什么问题,考虑到了imageview加载过大图片的问题(ImageUtils.calculateInSampleSize),也用了inJustDecodeBounds属性,在native decode 不生成bitmap,获取bitmap宽和高,根据宽高压缩图片。

运行的时候发现,内存一个劲的上升,查了半天才发现,少写了一句decode...


    /**
     * 根据imageview宽高 decode 图片
     */
    public static Bitmap decodeStream(InputStream is, ImageView imageView) {
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;
        //这行代码,如果没有这行代码,native就不会decode流,其实很简单
        //低下你调用什么decode在inJustDecodeBounds = true;下面你就要写一样的方法先在native decode bitmap获取宽高
        BitmapFactory.decodeStream(is, null, opts);

        //获取imageview想要显示的宽和高
        ImageSize imageViewSize = getImageViewSize(imageView);
        opts.inSampleSize = ImageUtils.calculateInSampleSize(opts,
                imageViewSize.getWidth(), imageViewSize.getHeight());

        opts.inJustDecodeBounds = false;

        return BitmapFactory.decodeStream(is, null, opts);
    }

Android: SkImageDecoder:: Factory returned null

解决了上面一个问题后,图片还是load不出来...也没有什么异常或者crash的日志,只看到logcat 里面解析图片的时候会报Android: SkImageDecoder:: Factory returned null 查了下说是 “第一次取图片尺寸的时候is这个InputStream被使用过了,再真正取图片的时候又使用了这个InputStream,此时流的起始位置已经被移动过了,需要调用is.reset()来重置,然后再decodeStream(imgInputStream, null, options)就没问题了。 ”
于是乎就将代码改为


    /**
     * 根据imageview宽高 decode 图片
     */
    public static Bitmap decodeStream(InputStream is, ImageView imageView) {
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;

        BitmapFactory.decodeStream(is, null, opts);
        is.reset();//加入reset
       
        ImageSize imageViewSize = getImageViewSize(imageView);
        opts.inSampleSize = ImageUtils.calculateInSampleSize(opts,
                imageViewSize.getWidth(), imageViewSize.getHeight());

        opts.inJustDecodeBounds = false;

        return BitmapFactory.decodeStream(is, null, opts);
    }

java.io.IOException

加入reset之后又遇到IOException 查了些也没具体说为什么就改成下面的方法了,果然好了

 /**
     * 根据imageview宽高 decode 图片
     */
    public static Bitmap decodeStream(InputStream is, ImageView imageView) {
        Bitmap bitmap = null;
        BufferedInputStream buffer = null;

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

        try {
            int buffersize = 16 * 1024;
            buffer = new BufferedInputStream(is, buffersize);
            buffer.mark(16 * 1024);
            BitmapFactory.decodeStream(buffer, null, opts);
            buffer.reset();

            //获取imageview想要显示的宽和高
            ImageSize imageViewSize = getImageViewSize(imageView);
            opts.inSampleSize = ImageUtils.calculateInSampleSize(opts,
                    imageViewSize.getWidth(), imageViewSize.getHeight());

            opts.inJustDecodeBounds = false;
            bitmap = BitmapFactory.decodeStream(buffer, null, opts);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            ImageUtils.closeQuietly(buffer);
            ImageUtils.closeQuietly(is);
        }
        return bitmap;
    }

但是,第二天使用oppo R7的时候还是报哪个错误,load不出来图。逐改成,确实解决了问题,都能顺利的decode图正常显示了。

/**
     * 根据imageview宽高 decode 图片
     */
    public static Bitmap decodeStream(InputStream is, ImageView imageView) {
        Bitmap bitmap = null;
        BufferedInputStream bis = null;
        ByteArrayOutputStream baos = null;

        try {
            BitmapFactory.Options opts = new BitmapFactory.Options();
            opts.inJustDecodeBounds = true;

            bis = new BufferedInputStream(is);
            baos = new ByteArrayOutputStream();

            byte buffer[] = new byte[1024];
            int len;
            while ((len = bis.read(buffer, 0, buffer.length)) > 0) {
                baos.write(buffer, 0, len);
            }

            byte[] imageData = baos.toByteArray();

            BitmapFactory.decodeByteArray(imageData, 0, imageData.length, opts);

            //获取imageview想要显示的宽和高
            ImageSize imageViewSize = getImageViewSize(imageView);
            opts.inSampleSize = ImageUtils.calculateInSampleSize(opts,
                    imageViewSize.getWidth(), imageViewSize.getHeight());

            opts.inJustDecodeBounds = false;
            bitmap = BitmapFactory.decodeByteArray(imageData, 0, imageData.length, opts);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IOUtils.close(bis);
            IOUtils.close(baos);
            IOUtils.close(is);
        }
        return bitmap;
    }

网上搜了好多也没找到具体原因,只能看源代码了:

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);  

于是看明白了,第一次取图片尺寸的时候is这个InputStream被使用过了,再真正取图片的时候又使用了这个InputStream,此时流的起始位置已经被移动过了,需要调用is.reset()来重置,然后再decodeStream(imgInputStream, null, options)就没问题了。 但是注意一个问题,is.mark(1024)是SDK中写死的,如果图片的大小超过1024字节,第一次decode取尺寸之后调用is.reset()会抛出IOException

http://stackoverflow.com/questions/10240042/ioexception-cannot-load-file

你可能感兴趣的:(关于BitmapFactory.decode)