Bitmap高效加载--采样率缩放

2018-07-01  本文已影响0人  DorisSunny

今日金句
Empty your cup so that it may be failed。
清空杯子,方能装满 ---李小龙

今天分享一个干货, 为了避免加载Bitmap而导致内存泄漏的问题,我觉得高效加载Bitmap的知识还是要pick一下的。接下来我们一起来学习一下如何高效的加载bitmap吧。
首先加载Bitmap,利用sdk提供的BitmapFactory方法可以创建Bitmap。顾名思义,Factory就是建造Factory的工厂啦,看起来使用了工厂模式。主要方法有四种:

  1. BitmapFactory.decodeFile(String pathName, Options opts)
    从文件路径所指向的文件加载Bitmap,opts是加载Bitmap的相关配置,可以通过配置opts从而限制只解析指定大小的Bitmap而不是全部解析。

  2. BitmapFactory. decodeStream(InputStream is, Rect outPadding, Options opts)
    从输入流里面加载一个Bitmap,decodeFile以及decodeResource最后都会调用这个方法。

  3. BitmapFactory.decodeResource(Resources res, int id, Options opts)
    从资源中加载Bitmap。

  4. BitmapFactory.decodeByteArray(byte[] data, int offset, int length, Options opts)
    从字节数组里面加载Bitmap,offset和length指定 开始加载的偏移量以及要读取的字节的长度,从而加载出一个不变的bitmap。
    补充一点:
    BitmapFactory的四个方法都支持采样率加载,但是decodestream特殊一点。因为对于FileInputStream,用采样率的缩放方式会存在问题,因为FileInputStream是一种有序的文件流,而两次的decodeStream调用会影响文件流的位置属性,从而第二次decodestream返回的是null。那么如果解决的FileInputStream的压缩呢。如下:

//通过文件流获取到文件描述符,然后通过BitmapFactory.decodedFileDescriptor方法加载一张缩放后的图片
  BitmapFactory.Options options = new BitmapFactory.Options();
  options.inJustDecodeBounds = true;
  BitmapFactory.decodeFileDescriptor(fd, null, options);
  
   int inSampleSize = 1;
   if (imgWidth > desiredWidth || imgHeight > desiredHeight) {
        int halfWidth = imgWidth / 2;
        int halfHeight = imgHeight / 2;

        while ((halfWidth / inSampleSize) >= desiredWidth &&
                (halfHeight / inSampleSize) >= desiredHeight) {
            inSampleSize *= 2;
        }
    }

    options.inJustDecodeBounds = false;
    options.inSampleSize= inSampleSize;
    Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);

实现高效加载Bitmap的核心原理是什么?
通过利用BitmapFactory.Options对图片进行采样缩放从而加载出所需的尺寸,而不是全部加载出来,因为如果加载出来的尺寸很大,但是显示的Imageview实际上不需要也显示不了原始尺寸,那么全部加载就会造成资源浪费,降低内存的占用。

那么如何使用BitmapFactory.Options呢?
下图是Options的结构,从图片可以看出其实Options可以用来配置很多采样率(inSampleSize), inDensity(像素密度)等。 我们想要控制加载出来的图片的大小,需要用到的就是inSampleSize这个属性啦。

捕获.PNG

inSampleSize: 采样率我们来看看官方对它的定义:

/**
* If set to a value > 1, requests the decoder to subsample the original
* image, returning a smaller image to save memory. The sample size is
* the number of pixels in either dimension that correspond to a single
* pixel in the decoded bitmap. For example, inSampleSize == 4 returns
* an image that is 1/4 the width/height of the original, and 1/16 the
* number of pixels. Any value <= 1 is treated the same as 1. Note: the
* decoder uses a final value based on powers of 2, any other value will
* be rounded down to the nearest power of 2.
*/
 public int inSampleSize;

如果inSampleSize的值大于1的话,将对原始的图片进行二次抽样返回一个更小的图片从而来节省内存占用,采样率的值是任何一个维度上的像素数量对应于解码后的bitmap的一个像素。比如inSampleSize == 4, 那么图片是原来的1/4.像素值原来的1/16,即解码前的4个像素相当于解码后的一个像素。 < 1 时被认为采样率就是1.不管你设置多少的采样率,最后的值总是2的指数。任何其他不是2的指数的值,都会取最接近的2的指数的采样率。
从上面的定义可以看出,采样率和图像宽高以及像素值成反比,具体的公式是宽=1/采样率 * 宽 , 高 = 1/采样率 * 高. 而图片的缩放比率是 1/(inSampleSize的2次方). 而占用的大小等于 解码后的宽 * 解码后的高 * 1个像素占用的内存大小 比如1024 * 1024

对于不同的目标尺寸的ImageView我们需要衡量不一样的采样率,长宽相等的采样率比较好定义 比如ImageView的尺寸大小是100 * 100,而原始大小200 * 200 那么采样率设置成2就可以了,如果图片大小 200 * 300,如果设置成采样率3,那么实际大小就变成比图片小,这样就会拉伸。

你一定在想难道每一个尺寸的图片都我们自己去计算采样率那岂不是要累死代码狗,不过呢还好强大的sdk都可以解决你的“一切”烦恼,有可以直接让你计算出采样率的方法哦。我们只需要封装一下就适用于任何的ImageView的尺寸的缩放啦。

步骤如下:

  1. BitmapFactory.Options的 inJustDecodeBounds参数设置为true并加载图片。
  2. 从BitmapFactory.Options取出图片原始的宽高信息,他们对应于outWidth和outHeight。
  3. 根据采样率的规则并结合目标View的所需大小计算出采样率计算出采样率inSampleSize。
  4. 将BitmapFactory.Options 的inJustDecodeBounds参数为false,然后重新加载图片。

最后加载的图片就是按照采样率缩放之后的图片,一起来理解一下上面四个步骤首先为什么要设置inJustDecodeBounds参数为true而最后一步又要设置为false呢。
首先一起来看看源码的定义,其实顾名思义也可以看出这个的意思就是只加载图片的bound,在这里就理解边界吧就是宽高,而不加载图片,这样就可以在获取采样率之前避免加载图片造成资源浪费。 定义如下:

      /**
         * If set to true, the decoder will return null (no bitmap), but
         * the <code>out...</code> fields will still be set, allowing the caller to
         * query the bitmap without having to allocate the memory for its pixels.
         */  
        public boolean inJustDecodeBounds;

从上面的定义可以看出如果isJustDecodeBounds设置为true的话,那么解码器将会return一个空的bitmap,并且会给out...之类的属性进行赋值,允许用户可以查询bitmap信息而不需要加载图片。这样我们就通过outWidth以及outHeight拿到原始图片的宽高了。 不学不知道,学了之后才知道这个属性原来作用这么大。
举个栗子:

  BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_background, options);
        int imgWidth = options.outWidth; //要加载的图片的宽
        int imgHeight = options.outHeight;//要加载的图片的高
        Log.i("doris", "原始图片:" + imgWidth + ":" + imgHeight);
        int desiredWidth = 50;
        int desiredHeight = 50;
        int inSampleSize = 1;
        if (imgWidth > desiredWidth || imgHeight > desiredHeight) {
            int halfWidth = imgWidth / 2;
            int halfHeight = imgHeight / 2;

            while ((halfWidth / inSampleSize) >= desiredWidth &&
                    (halfHeight / inSampleSize) >= desiredHeight) {
                inSampleSize *= 2;
            }
        }

        options.inJustDecodeBounds = false;
        options.inSampleSize= inSampleSize;
        BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_background, options);
        Log.i("doris", "解压后图片:" + options.inSampleSize + options.outWidth + "::" + options.outHeight);

今天学习的知识先总结出个小点,高效加载方法1采样率缩放。之后会更新缓存策略以及压缩策略哦。一起成长,加油。

上一篇下一篇

猜你喜欢

热点阅读