图片加载和它的内存们

在Android设备中,内存的分配是有限的,每个APP分配一定的内存空间,当内存使用达到一定的阈值,就会触发GC,当内存超过最大内存,就会OOM然后就凉凉。因此,内存是相当珍贵的,一个流畅的APP需要做好内存优化。而图片,尤其是大图,是特别消耗内存的,江湖人称“内存杀手”。

一张图片到底占用多少内存

在计算图片占用内存之前,需要厘清两个概念。

  1. 图片占用内存大小

    图片占用内存大小不等同于图片占用磁盘空间大小,图片在存储的时候会经过图片压缩算法进行压缩,然后保存成jpg、png等格式。因此一般情况下,图片占用磁盘空间大小都要小于图片占用内存大小。就像一张充气凳子,不用的时候放气折叠起来放着,使用的时候充气恢复原来的样子。

    图片占用内存的大小 = 宽*高*单个像素占用的字节数

    宽高指的是图片加载到内存中的宽高,从不同的drawable文件夹加载的图片在不同分辨率的手机上显示会对图片的宽高进行相应的缩放,这是屏幕适配的基本知识,这里不进行深入。

  2. 单个像素占用的字节数

    一个像素占用多大内存空间是由图片加载的模式决定的,Android中默认使用ARGB_8888的模式加载图片,即一个像素点占用2byte。这里简单介绍一下各个模式:

    • ALPHA_8:像素点只有alpha通道,1个像素点占用1byte。这个模式下只有透明度,一般用来做遮罩层。
    • RGB_565:R通道占用5bit,G通道占用6bit,B通道占用5bit,总共占用2byte。这个模式没有ALPHA通道,对于色彩要求不高的情况,可以采用RGB_565,Glide默认使用这个模式。
    • ARGB_4444:ARGB四个通道分别占用4bit,总共占用2byte。色彩表现不好,已经被废弃了,在4.4之后使用ARGB_4444会默认使用ARGB_8888替代。
    • ARGB_8888:ARGB四个通道分别占用8bit,总共占用4byte。比较灵活,色彩表现度较好,是推荐使用的模式,也是默认使用的模式。
    • RGBA_F16:一个像素占用8byte,适用于广色域显示。实际开发中暂时还没见到用这个的场景。

在理解上述概念的前提下,基本上对于一张图片占用多大内存可以心中有数。这里尝试加载一张图片进行验证,图片的信息如下图,是一张690*12287的大图。

图片信息

按照上述方法计算结果为:占用内存的大小 = 690 * 12287 * 4 = 33972120。

drawable_size.png

通过Android Profiler导出内存进行验证,和计算结果是吻合的。吻合是吻合,但是大约占了34M的内存,显示效果如下。

显示效果

花了34M显示出这个效果,心疼的抱住了胖胖的自己。

拿大图怎么办

那么遇到大图怎么办呢。分成两种情况,一种是我就想把这么大图片设置到比较小的ImageView上,比如把200px*200px的图片加载到100px*100px的ImageView上,从上图可以看出来,大图设置到小的控件上,一样看不清楚,因此只需要把图片缩放成100*100的就可以了。另一种是上面这种超过屏幕大小的大图,ImageView没办法设置那么大,但是又想看高清大图,就可以采用局部加载的方式。

图片采样率缩放

图片采样率缩放,是通过对图片进行一定比例的缩放,减少图片的像素点,进而减少图片占用内存的大小。主要通过BitmapFactory.Options类实现。

BitmapFactory.Options主要属性如下:

  • inJustDecodeBounds:该属性设置为true时,只加载图片信息到Options中,而不将图片加载到内存中。
  • outWidth:图片的宽度。
  • outHeight:图片的高度。
  • inSampleSize:采样率,如果值大于1,将对图片进行二次采样,宽和高分别按相应的比例缩小。例如设置为2,则宽和高缩小为原来的2分之一,即200*200的图片缩小成100*100,图片占用的内存则为原来的4分之一。inSampleSize应该设置为2的n次方,否则向下取最近的2的n次方数。比如设置inSampleSize=10,则实际inSampleSize向下取2的3次方为8,因此缩小成8分之一。

具体实现代码如下:

            BitmapFactory.Options options = new BitmapFactory.Options();
            //只读取图片信息
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeResource(getResources(), R.drawable.ad, options);
            //读取图片宽和高
            int imgWidth = options.outWidth;
            int imgHeight = options.outHeight;
            //读取ImageView宽和高
            int ivHeight = imageView.getHeight();
            int ivWidth = imageView.getWidth();
            //分别计算宽和高的比值
            int scaleX = imgWidth / ivWidth;
            int scaleY = imgHeight / ivHeight;
            int inSampleSize = 1;
            //取较大的值作为采样率
            inSampleSize = scaleX > scaleY ? scaleX : scaleY;
            //防止采样率小于1
            if (inSampleSize < 1)
                inSampleSize = 1;
            Log.i("bitmap", "inSampleSize = " + inSampleSize);
            //根据采样率加载图片
            options.inJustDecodeBounds = false;
            options.inSampleSize = inSampleSize;
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ad, options);
            imageView.setImageBitmap(bitmap);

这个方式加载出来的效果和上面的一样,就不多放一张图片了。而这时候图片会占用多少内存呢?这里计算图片内存的时候需要注意,当宽/采样率不能整除的情况下,采用进一法计算,比如该图片宽690,计算出来inSampleSize为10,根据前面的描述可以知道实际inSampleSize为8,690/8=86.25,因此最后宽为87。高为12287/8=1535.875,进一法得1536。因此图片占用内存大小为87*1536*4=534528。

同样dump出内存进行验证。

采样后的大小

按区域加载

按区域加载通过BitmapRegionDecoder类实现,用到的参数主要是Rect和BitmapFactory.Options,其中Rect指定加载图片的区域。

实现代码如下:

                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeResource(getResources(), R.drawable.ad, options);
                //老套路,计算图片的宽和高
                int imgWidth = options.outWidth;
                int imgHeight = options.outHeight;
                InputStream is = getResources().openRawResource(R.raw.ad);
                //初始化BitmapRegionDecoder
                BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
                //设置图片加载模式为RGB_565
                options.inPreferredConfig = Bitmap.Config.RGB_565;
                //加载图片中心位置300px*300px大小的图片
                Bitmap bitmap = decoder.decodeRegion(new Rect(imgWidth/2 - 150, imgHeight/2 - 150, imgWidth/2 + 150, imgHeight/2 + 150), options);
                imageView.setImageBitmap(bitmap);

显示效果如下:

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

推荐阅读更多精彩内容