在我们编写 Android 程序的时候,几乎永远逃避不了图片压缩的难题。除了应用图标之外,我们所要显示的图片基本上只有两个来源:
不管是网上下载下来的也好,还是从系统图片库中读取的图片,都有一个相同的特点:像素一帮较高。同时我们都知道,Android 系统分配给我们每个应用的内存是有限的,由于解析、加载一张图片,需要占用的内存大小,是远大于图片自身大小的。所以,这时程序就可能因为占用了过多的内存,从而出现OOM 现象。那么什么是 OOM 呢?
OOM 即 OutOfMemory 异常,也就是我们所说的 内存溢出 ,其一般表现为应用闪退等现象。那么我们该如何下手去解决呢?
首先我们发现,我们所加载的这些图片的分辨率,要比我们手机屏幕高得多,更有甚者,我们在一个拇指大的控件上,去加载一个 4k 大图是完全没有必要的,也就是说,如果我们能让每个控件上都去显示相应大小的图片,那么这个问题也就迎刃而解了
那么,要怎样才能达到图片与控件的对号入座?这时我们就引进了图片压缩的方案:
那么就让我们开始吧:
我们都知道,Android 向我们提供了 BitmapFactory 这个类,在这个类中有着诸如:decodeResource() decodeFile() decodeStream() 等:
其中:
其他的方法这里就不多说了,因为在源码中我们可有i看到,几乎所有的方法,最后都会将图片解析为流的形式,最后调用 decodeStream() 方法,实例化出我们的 Bitmap 对象。
虽然这些方法对我们是再熟悉不过的了,但对于某些初学者而言,却经常忽略了一个重要的内部类 :BitmapFactory.Options ,然而他确实我们图片压缩必不可少的,为什么需要这个参数呢?Options 的对象用于确定需要生成的 Bitmap 即目标图片的参数。
他的用法很简单,我们先 new 一个 BitmapFactory.Options 对象。再去调用含有 Options参数的方法,如
调用完之后我们发现,除了方法放回给我们一个实例化出来的 Bitmap 图片之外,这个 Options 对象中长度、宽度、类型等等属性,也都被设置成了了我们图片的相应属性。所以,我们很容易想到:通过将 Options 对象传入,来获得图片的原始尺寸,为后期的压缩做准备,说干就干,我们将 Options 对象,和 Resources中一张 4k 图片的id 一块传入上诉方法中,来尝试获得它的尺寸,结果我们发现:程序 OOM 崩溃了!
为什么会发生这种情况?首先我们想想我们为什么要获得这个Options 对象?时为了获得图片的尺寸大小;那我们为什么要获得原图尺寸大小?是为了按照原图尺寸和控件尺寸的比例,将其压缩为适合显示的大小?那我们又为什么要去压缩它为合适的大小呢?是因为如果按照原大小去调用相应的 decode...()方法解析图片,会导致内存占有率过高触发OOM 异常,进而导致程序崩溃啊!没想到的是:结果我们为了获得 Options 而调用了相应的 decode...() 方法,的确 Options 是复制了,但由于该方法适用于生成图片,也就是 Bitmap 对象的。所以程序也在解析这张超大图的过程中OOM 崩溃了
那么难道就没方法了吗?
有的,我之前说过:Option 内部有着众多参数,其中有一个叫做: inJustDecodeBounds 。这个参数默认值为false 。但如果我们先把这个参数设置为 true 时,该方法便不在会去生成相应的 Bitmap ,而仅仅是去测量图片的各种属性,如长度、宽度、类型等等,然后放回一个 null 。所以,我们很容易想到:可以先通过将 inJustDecodeBounds 的值设为 true ,再去调用相应的相应的 decode...()方法,最后再将inJustDecodeBounds 的值改回 false。这种做法有两个好处:
但这恰恰是被很多人所忽略的一点。
好了,现在给出具体的实现:
大家可能发现,这里只将 inJustDecodeBounds 设为true却没有改回false ,这是因为获得 Options 只是图片压缩的第一步,我们在后续方法中将会进行修改
我们继续看 Options 的构成。我们发现,其中有个名为 inSampleSize 的数据成员,他就是关键所在,那么他有着什么意义呢?
这里我给大家举个例子,比如我这有张 4000*1000 像素的图片:
这不仅仅是长宽都变为原来四分之一或者五分之一这么简单,而是其图片大小,直接变为原图的 1/(n^2)!也就是说:
那么下面我就给出这个方法的具体实现:
我们发现,这里我先计算出了,原图尺寸与目标大小大比例,在三目运算符中,将inSamplesize 赋值为较大的一个。为什么不用小的那一个呢?这里我就卖个关子,大家可以在评论区中发表自己的想法
经过前面的两个步骤,想必大家已经能勾勒处这最后一步的做法了,思路非常简单:
具体实现如下
非常棒,我们赶紧看看效果:
太棒了,几乎和原图效果一摸一样,但软件运行的流畅性确大大提高了!但是,这真的就完美了吗?
最求完美的我们可能会有个想法:如果调用我们方法的人,或者说特殊时候的我们。不想用这个已经写好的 decodeBitmapById方法,而是像自己通过前两个方法:calculateOptionsById calculateInSamplesizeByOptions 来实现图片压缩功能,这是问题就出现了:
所以,我们需要在优化一下:
首先,在calculateOptionsById中,默认将 options.inJustDecodeBounds 设置为true:
其次,在 calculateInSamplesizeByOptions最后,默认将 options.inJustDecodeBounds设置为false:
为什么不在该方法后面,对 options.inSampleSize进行赋值呢?这主要是防止,有时我们可能只想得到计算相应比例来做其他操作,而不想改变原有属性,所以是否赋值,就交给用户去选择吧
好了,到这里为止,历时有关图片压缩的所有坑坑洼洼都已经总结好了,我们从头理以边思路:
漫漫开发之路,我们只是其中的一小部分……只有不断的学习、进阶,才是我们的出路!才跟得上时代的进步!
今年年初我花一个月的时间收录整理了一套知识体系,如果有想法深入的系统化的去学习的,可以私信我【安卓】,我会把我收录整理的资料都送给大家,帮助大家更快的进阶。