Android 之 Bitmap 解析
bitmap在android中指的是一张图片,可以是png,也可以是jpg等其他图片格式。
一、bitmap的基本加载
bitmap的加载离不开bitmapfactory类,关于bitmap官方介绍creates bitmap objects from various sources, including files, streams, and byte-arrays.查看api,发现和描述的一样,bitmapfactory类提供了四类方法用来加载bitmap:
decodefile 从文件加载
a. 通过intent打开本地图片或照片
b. 在onactivityresult中获取图片uri
c. 根据uri获取图片的路径
d. 根据路径解析bitmap:bitmap bm = bitmapfactory.decodefile(sd_path)decoderesource 以r.drawable.xxx的形式从本地资源中加载
bitmap bm = bitmapfactory.decoderesource(getresources(), r.drawable.aaa);decodestream 从输入流加载
a.开启异步线程去获取网络图片
b.网络返回inputstream
c.解析:bitmap bm = bitmapfactory.decodestream(stream),这是一个耗时操作,要在子线程中执行decodebytearray 从字节数组中加载
接3.a,3.b,
c. 把inputstream转换成byte[]
d. 解析:bitmap bm = bitmapfactory.decodebytearray(mybyte,0,mybyte.length);
注意:decodefile和decoderesource间接调用decodestream方法。
关于图片的基本加载既不是本文的重点,也不是什么难点,所以这里就不贴详细代码了,这里只写几句关键代码和伪代码,【详细代码】可以下载查看
二、高效的加载bitmap
我们在使用bitmap时,经常会遇到内存溢出等情况,这是因为图片太大或者android系统对单个应用施加的内存限制等原因造成的,比如上述方法1加载一张照片时就会报:06-28 10:43:30.777 26007-26036/com.peak.app w/openglrenderer: bitmap too large to be uploaded into a texture (3120x4160, max=4096x4096),而方法2加载一个3+g的照片时会报caused by: java.lang.outofmemoryerror: failed to allocate a 144764940 byte allocation with 16765264 free bytes and 109mb until oom所以,高效的使用bitmap就显得尤为重要,对他效率的优化也是如此。
高效加载bitmap的思想也很简单,就是使用系统提供给我们options类来处理bitmap。翻看bitmap的,发现上述四个加载bitmap的方法都是支持options参数的。
通过bitmapfactory.options按一定的采样率来加载缩小后的图片,然后在imageview中使用缩小的图片这样就会降低内存占用避免【oom】,提高了bitamp加载时的性能。
这其实就是我们常说的图片尺寸压缩。尺寸压缩是压缩图片的像素,一张图片所占内存的大小 图片类型*宽*高,通过改变三个值减小图片所占的内存,防止oom,当然这种方式可能会使图片失真 。
android 色彩模式说明:
alpha_8:每个像素占用1byte内存。argb_4444:每个像素占用2byte内存argb_8888:每个像素占用4byte内存rgb_565:每个像素占用2byte内存
android默认的色彩模式为argb_8888,这个色彩模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。
bitmapfactory.options的inpreferredconfig参数可以 指定decode到内存中,手机中所采用的编码,可选值定义在bitmap.config中。缺省值是argb_8888。
假设一张1024*1024,模式为argb_8888的图片,那么它占有的内存就是:1024*1024*4 = 4mb
1、采样率insamplesize
insamplesize的值必须大于1时才会有效果,且采样率同时作用于宽和高;当insamplesize=1时,采样后的图片为图片的原始大小当insamplesize=2时,采样后的图片的宽高均为原始图片宽高的1/2,这时像素为原始图片的1/(22),占用内存也为原始图片的1/(22);insamplesize的取值应该总为2的整数倍,否则会向下取整,取一个最接近2的整数倍,比如insamplesize=3时,系统会取insamplesize=2
假设一张1024*1024,模式为argb_8888的图片,insamplesize=2,原始占用内存大小是4mb,采样后的图片占用内存大小就是(1024/2) * (1024/2 )* 4 = 1mb
2、获取采样率遵循以下步骤
将bitmapfacpry.options的injustdecodebounds参数设为true并加载图片当injustdecodebounds为true时,执行decodexxx方法时,bitmapfactory只会解析图片的原始宽高信息,并不会真正的加载图片从bitmapfacpry.options取出图片的原始宽高(outwidth,outheight)信息选取合适的采样率将bitmapfacpry.options的insamplesize参数设为false并重新加载图片
经过上面过程加载出来的图片就是采样后的图片,代码如下:
public void decoderesource(view view) { bitmap bm = decodebitmapfromresource(); imageview.setimagebitmap(bm); } private bitmap decodebitmapfromresource(){ bitmapfactory.options options = new bitmapfactory.options(); options.injustdecodebounds = true; bitmapfactory.decoderesource(getresources(), r.drawable.bbbb, options); options.insamplesize = calculatesamplesize(options,300,300); options.injustdecodebounds =false; return bitmapfactory.decoderesource(getresources(),r.drawable.bbbb,options); } // 计算合适的采样率(当然这里还可以自己定义计算规则),reqwidth为期望的图片大小,单位是px private int calculatesamplesize(bitmapfactory.options options,int reqwidth,int reqheight){ log.i("========","calculatesamplesize reqwidth:"+reqwidth+",reqheight:"+reqheight); int width = options.outwidth; int height =options.outheight; log.i("========","calculatesamplesize width:"+width+",height:"+height); int insamplesize = 1; int halfwidth = width/2; int halfheight = height/2; while((halfwidth/insamplesize)>=reqwidth&& (halfheight/insamplesize)>=reqheight){ insamplesize*=2; log.i("========","calculatesamplesize insamplesize:"+insamplesize); } return insamplesize; }
三、使用bitmap时的一些注意事项
1、不用的bitmap及时释放
if (!bmp.isrecycle()) { bmp.recycle();//回收图片所占的内存 bitmap = null; system.gc(); //提醒系统及时回收 }
虽然调用recycle()并不能保证立即释放占用的内存,但是可以加速bitmap的内存的释放。
释放内存以后,就不能再使用该bitmap对象了,如果再次使用,就会抛出异常。所以一定要保证不再使用的时候释放。比如,如果是在某个activity中使用bitmap,就可以在activity的onstop()或者ondestroy()方法中进行回收。
2、捕获异常
因为bitmap非常耗内存,了避免应用在分配bitmap内存的时候出现outofmemory异常以后crash掉,需要特别注意实例化bitmap部分的代码。通常,在实例化bitmap的代码中,一定要对outofmemory异常进行捕获。很多开发者会习惯性的在代码中直接捕获exception。但是对于outofmemoryerror来说,这样做是捕获不到的。因为outofmemoryerror是一种error,而不是exception。
bitmap bitmap = null; try { // 实例化bitmap bitmap = bitmapfactory.decodefile(path); } catch (outofmemoryerror e) { } if (bitmap == null) { return defaultbitmapmap; // 如果实例化失败 返回默认的bitmap对象 }
3、【缓存通用的bitmap对象】
有时候,可能需要在一个activity里多次用到同一张图片。比如一个activity会展示一些用户的头像列表,而如果用户没有设置头像的话,则会显示一个默认头像,而这个头像是位于应用程序本身的资源文件中的。如果有类似上面的场景,就可以对同一bitmap进行缓存。如果不进行缓存,尽管看到的是同一张图片文件,但是使用bitmapfactory类的方法来实例化出来的bitmap,是不同的bitmap对象。缓存可以避免新建多个bitmap对象,避免内存的浪费。在android应用开发过程中所说的缓存有两个级别,一个是硬盘缓存,一个是内存缓存。
4、图片的质量压缩
上述用insamplesize压缩是尺寸压缩,android中还有一种压缩方式叫质量压缩。质量压缩是在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的,经过它压缩的图片文件大小(kb)会有改变,但是导入成bitmap后占得内存是不变的,宽高也不会改变。因为要保持像素不变,所以它就无法无限压缩,到达一个值之后就不会继续变小了。显然这个方法并不适用与缩略图,其实也不适用于想通过压缩图片减少内存的适用,仅仅适用于想在保证图片质量的同时减少文件大小的情况而已
private void compressimage(bitmap image, int reqsize) { bytearrayoutputstream baos = new bytearrayoutputstream(); image.compress(bitmap.compressformat.jpeg, 100, baos);// 质量压缩方法,这里100表示不压缩, int options = 100; while (baos.tobytearray().length / 1024 > reqsize) { // 循环判断压缩后的图片是否大于reqsize,大于则继续压缩 baos.reset();//清空baos image.compress(bitmap.compressformat.jpeg, options, baos);// 这里压缩options%,把压缩后的数据放到baos中 options -= 10; } // 把压缩后的baos放到bytearrayinputstream中 bytearrayinputstream isbm = new bytearrayinputstream(baos.tobytearray()); //decode图片 bitmap bitmap = bitmapfactory.decodestream(isbm, null, null); }
5、android加载大量图片内存溢出解决方案:
尽量不要使用setimagebitmap或setimageresource或bitmapfactory.decoderesource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createbitmap来完成的,需要消耗更多内存,可以通过bitmapfactory.decodestream方法,创建出一个bitmap,再将其设为imageview的 source使用bitmapfactory.options对图片进行压缩(上述第二部分)运用java软引用,进行图片缓存,将需要经常加载的图片放进缓存里,避免反复加载
四、bitmap一些其他用法
1、图片旋转指定角度
// 图片旋转指定角度 private bitmap rotateimage(bitmap image, final int degree) { int width = image.getwidth(); int height = image.getheight(); if (width > height) { matrix matrix = new matrix(); matrix.postrotate(degree); if (image != null && !image.isrecycled()) { bitmap resizedbitmap = bitmap.createbitmap(image, 0, 0, width, height, matrix, true); return resizedbitmap; } else { return null; } } else { return image; } }
2、图片合成
private bitmap createstarbitmap(float grade, int maxgrade) { bitmap empty_star = bitmapfactory.decoderesource(getresources(), r.drawable.empty_star); // 空星 bitmap normal_star = bitmapfactory.decoderesource(getresources(), r.drawable.normal_star); // 实星 bitmap half_star = bitmapfactory.decoderesource(getresources(), r.drawable.half_star); ; // 半星 int star_width = empty_star.getwidth(); int star_height = empty_star.getheight(); bitmap newb = bitmap.createbitmap(star_width * 5, star_height, bitmap.config.argb_8888);// 创建一个底层画布 canvas cv = new canvas(newb); for (int i = 0; i < maxgrade; i++) { if (i < grade && i + 1 > grade) // 画半星 { cv.drawbitmap(half_star, star_width * i, 0, null);// 画图片的位置 } else if (i < grade) // 画实心 { cv.drawbitmap(normal_star, star_width * i, 0, null);// 画图片的位置 } else // 画空心 { cv.drawbitmap(empty_star, star_width * i, 0, null);// 画图片的位置 } } // save all clip cv.save(canvas.all_save_flag);// 保存 // store cv.restore();// 存储 return newb; } activity中调用 bitmap bm = createstarbitmap(3.5f, 5); imageview.setimagebitmap(bm);
上述代码展示的是通过右图的三张图片动态合成评分:
3、图片圆角
public bitmap toroundcorner(bitmap bitmap, int pixels) { bitmap roundcornerbitmap = bitmap.createbitmap(bitmap.getwidth(), bitmap.getheight(), bitmap.config.argb_8888); canvas canvas = new canvas(roundcornerbitmap); int color = 0xff424242;// int color = 0xff424242; paint paint = new paint(); paint.setcolor(color); // 防止锯齿 paint.setantialias(true); rect rect = new rect(0, 0, bitmap.getwidth(), bitmap.getheight()); rectf rectf = new rectf(rect); float roundpx = pixels; // 相当于清屏 canvas.drawargb(0, 0, 0, 0); // 先画了一个带圆角的矩形 canvas.drawroundrect(rectf, roundpx, roundpx, paint); paint.setxfermode(new porterduffxfermode(porterduff.mode.src_in)); // 再把原来的bitmap画到现在的bitmap!!!注意这个理解 canvas.drawbitmap(bitmap, rect, rect, paint); return roundcornerbitmap; }
4、将bitmap转换成drawable
drawable newbitmapdrawable = new bitmapdrawable(bitmap);
还可以从bitmapdrawable中获取bitmap对象
bitmap bitmap = new bitmapdrawable.getbitmap();
5、drawable转换成bitmap
public static bitmap drawabletobitmap(drawable drawable) { bitmap bitmap = bitmap.createbitmap( drawable.getintrinsicwidth(), drawable.getintrinsicheight(), drawable.getopacity() != pixelformat.opaque bitmap.config.argb_8888 : bitmap.config.rgb_565 ); canvas canvas = new canvas(bitmap); drawable.setbounds(0, 0, drawable.getintrinsicwidth(), drawable.getintrinsicheight()); drawable.draw(canvas); return bitmap; }
6、图片的放大和缩小
public bitmap scalematriximage(bitmap oldbitmap, float scalewidth, float scaleheight) { matrix matrix = new matrix(); matrix.postscale(scalewidth,scaleheight);// 放大缩小比例 bitmap scalebitmap = bitmap.createbitmap(oldbitmap, 0, 0, oldbitmap.getwidth(), oldbitmap.getheight(), matrix, true); return scalebitmap; }
7、图片裁剪
public bitmap cutimage(bitmap bitmap, int reqwidth, int reqheight) { bitmap newbitmap = null; if (bitmap.getwidth() > reqwidth && bitmap.getheight() > reqheight) { bitmap = bitmap.createbitmap(bitmap, 0, 0, reqwidth, reqheight); } else { bitmap = bitmap.createbitmap(bitmap, 0, 0, bitmap.getwidth(), bitmap.getheight()); } return bitmap; }
8、图片保存到sd
public void savepic(bitmap bitmap,string path) { file file = new file(path); fileoutputstream fileoutputstream = null; try { file.createnewfile(); fileoutputstream = new fileoutputstream(file); bitmap.compress(bitmap.compressformat.png, 100, fileoutputstream); fileoutputstream.flush(); } catch (ioexception e) { e.printstacktrace(); } finally { try { if (fileoutputstream != null) { fileoutputstream.close(); } } catch (ioexception e) { e.printstacktrace(); } } }