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

今日金句
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采样率缩放。之后会更新缓存策略以及压缩策略哦。一起成长,加油。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容