欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android 之 Bitmap 解析

程序员文章站 2022-06-02 22:28:53
bitmap在android中指的是一张图片,可以是png,也可以是jpg等其他图片格式。 一、bitmap的基本加载 bitmap的加载离不开bitmapfactory类,关于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);

上述代码展示的是通过右图的三张图片动态合成评分:

Android 之 Bitmap 解析

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

Android 之 Bitmap 解析

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