大家都知道,android 应用在创建进程时候,会分配一个指定的内存大小,准确的说话是 google原生OS的默认值是16M,如果我们应用“毫不吝啬”将这些大图直接加载到内存中,很快内存就会耗尽,最终出现OOM异常,所以图片的处理对于一个稳定、用户体验友好的应用来说非常重要,在开发过程中把”图片“给优化一番,保证我们项目在线上稳定、流畅运行。
bitmap在sdk中算是元老级的人物了,从api1中就已经有了,可见其重要性。继承关系就不解释了,实现了Parcelable 具备在内存中传递的特性。
bitmap中有两个重要的内部类 CompressFormat 以及 Config;
下面分别介绍一下这两个类
CompressFormat
CompressFormat 是用来设置压缩方式的,是个枚举类,内部提供了三种图片压缩方式类型,
JPEG :表示Bitmap采用JPEG压缩算法进行压缩,压缩后的格式可以是.jpg或者.png,是一种有损压缩方式。
PNG : 表示Bitmap采用PNG压缩算法进行压缩,压缩后的格式可以是.png,是一种无损压缩方式。
WEBP :表示以WebP压缩算法进行图像压缩,压缩后的格式可以是".webp",是一种有损压缩,质量相同的情况下,WebP格式图像的体积要比JPEG格式图像小40%,美中不足的是,WebP格式图像的编码时间“比JPEG格式图像长8倍”, 而且还需要注意,在官方文档中有这样的描述:As of Build.VERSION_CODES.Q
, a value of 100
results in a file in the lossless WEBP format. Otherwise the file will be in the lossy WEBP format. 意为Android10之后如果quality值(压缩质量)为100的话,bitmap压缩采用无损压缩格式,其他都为有损压缩;
这里有的同志会问,这都是压缩格式啊,具体怎么操作压缩呢,Bitmap为我们提供了一个可靠的方法供开发者使用,我们来顺便看看Bitmap都有什么方法,如下:
第一个方法就是compress()方法, 没错就是这么就这方法,一共有三个参数
format :上面已经说明了,表示压缩格式;
quality :压缩质量,取值0-100,0表示最低画质压缩,100表示最高画质压缩,对于PNG压缩格式来说,该参数可以忽略,对于WEBP格式来说,小于100为有损压缩格式,会对画质产生直接影响, 等于100时候采用的是无损压缩格式,画质是不会有改变,但是图片大小得到很好压缩;
stream :将压缩后的图片写到指定的输出流中;
返回值:boolean, 返回true表示成功将bitmap压缩到输出流中,然后可以通过Bitmap.Factory从相应的输入流中解析出来bitmap信息;
从官网介绍可知, 该方法在图片压缩过程中可能消耗较长时间,建议放在子线程中操作,至于为什么大家可以看看源码, 源码中会调用一个nativeCompress 的Native 方法,也就是压缩处理是放在底层处理的;
Config
表示位图像素的存储格式,什么意思呢? 就是bitmap在屏幕上显示的每一像素在内存中存储的格式,会影响Bitmap真实图片的透明度以及图片质量;
Bitmap.Config.ALPHA_8:颜色信息只由透明度组成,占8位;
Bitmap.Config.ARGB_4444:颜色信息由透明度与R(Red),G(Green),B(Blue)四部分组成,每个部分都占4位,总共占16位;
Bitmap.Config.ARGB_8888:颜色信息由透明度与R(Red),G(Green),B(Blue)四部分组成,每个部分都占8位,总共占32位,是Bitmap 默认的颜色存储格式,也是最占空间的一种配置;
Bitmap.Config.RGB_565:颜色信息由R(Red),G(Green),B(Blue)三部分组成,R占5位,G占6位,B占5位,总共占16位;
上面说了 android 系统默认存储位图方式是 ARGB_8888, 4个通道组成,每个通道8位,分表代表透明度和RGB颜色值, 也就是说一个位图像素占用了4个字节(1个byte8个bit位),
同理:采用 Bitmap.Config.RGB_565 存储,单像素占用内存大小仅有2byte,换句话说一张图片采用ARGB_565格式相对于默认的ARGB_8888内存将减少一半,所以通过改变bitmap像素存储方式也是图片内存优化的重要渠道,这个后面会讲到;
bitmap是android中重要的图像处理工具类,通过bitmap可以对图像进行剪切、旋转、缩放等操作,同时还可以指定格式和压缩质量保存图像文件。
一、拿到一个Bitmap对象
查看源码我们知道,Bitmap被final修饰,因此不可以通过new来获得一个bitmap的实例,获得一个Bitmap对象一般都是通过BitmapFactory来获得,主要有以下几方式。
每一个方法都有自己的重载方法,重载方法多出的参数是Options,解释一下这个参数,这个主要是对生成Bitmap对象做一些简单的处理。
inPreferredConfig 指定decode到内存中,手机中所采用的编码,可选值定义在Bitmap.Config中。缺省值是ARGB_8888。
inJustDecodeBounds 如果设置为true,并不会把图像的数据完全解码,亦即decodeXyz()返回值为null,但是Options的outAbc中解出了图像的基本信息。
inSampleSize 设置decode时的缩放比例。当值大于1时会对图像进行压缩,比如设置为4时,返回的图像宽高都将是原始图像的四分之一,像素是原始图像的十六分之一,如果设置的值小于1,则会被当作1来处理。注意,这里设值必须是2的次幂,其他值会被就近取离2的次幂最近的值。
通过option的这些属性,我们就可以获得一张图片的缩略图啦,一个基本思路是先设置inJustDecodeBounds为true,拿到图像的基本信息,比如宽和高,根据基本信息结合缩略图目标宽高,算出inSampleSize ,然后再设置inJustDecodeBounds为false,获得缩略图。
想要获得一个Bitmap对象,可以通过一个图像的完整路径,或者是通过一个字节流或者是通过字节亦或者是通过本地的资源文件,都可以获得一个Bitmap对象。
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
// /storage/emulated/0/DCIM/Camera/123.jpg
String path = Environment.getExternalStorageDirectory()
.getAbsolutePath()
+ File.separator
+ "DCIM"
+ File.separator
+ "Camera"
+ File.separator
+ "123.jpg";
BitmapFactory.Options option = new BitmapFactory.Options();
option.inSampleSize = 1;
//通过完整路径获得Bitmap对象
Bitmap bm = BitmapFactory.decodeFile(path,option);
FileInputStream is = new FileInputStream(new File(path));
//通过一个输入流获得Bitmap对象
Bitmap bm2 = BitmapFactory.decodeStream(is);
//通过本地资源获得一个Bitmap对象
Bitmap bm1 = BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher);
iv.setImageBitmap(bm2);
}
二,保存图像文件
拿到了Bitmap之后可以直接显示出来,也可以先保存到本地等待处理,Bitmap可以保存为jpg,png,gif等格式,保存方式也是非常简单:
Bitmap bp = revitionImageSize(path + "123.jpg"); FileOutputStream out = new FileOutputStream(path + "456.gif"); //0意味着压缩到最小,100意味着压缩后的质量最好,PNG是无损图像,会忽略这个参数 // bp.compress(Bitmap.CompressFormat.JPEG, 100, out); bp.compress(Bitmap.CompressFormat.PNG, 0, out); out.flush(); out.close();
直接调用compress方法,第一个参数表示压缩的方式,有多种,PNG属于无损压缩,JPEG是有损压缩。第二个参数表示压缩质量,取值为0-100,100表示压缩图像质量最高,但同时压缩率低,0表示压缩图像质量最差,同时压缩率高。但是,这个参数是否有效与第一个参数有关,如果第一个参数选择了PNG,及无损压缩,那么第二参数是无效的。第三个参数是你要输出的字节流。