当前位置: 移动技术网 > IT编程>移动开发>Android > Android 之 Bitmap 解析

Android 之 Bitmap 解析

2018年12月10日  | 移动技术网IT编程  | 我要评论

张欧影,食饼筒,福州财务公司

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();
}
  }
 }

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网