关于bitmap的一些知识

     最近在做一个弹幕的功能,涉及到弹幕头像、礼物等下载、缓存等。使用了开源的<Android 开源框架Universal-Image-Loader>,做起来比较顺手。原因是该开源框架比较完善,调用者很容易上手,用起来和方便。在此就不说开源框架了,其实自己要写一个开源框架,最主要的是把原理弄懂。先谈一谈这个bitmap吧。有些知识是从网络摘操,因为写的很好。

    bitmap是位图,它将图像定义为由点(像素)组成,每个点可以由多种色彩表示,包括2、4、8、16、24和32位色彩。例如,一幅1024×768分辨率的32位真彩图片,其所占存储字节数为:1024×768×32/(8*1024)=3072KB。

    Android中展示不同格式的图像,在内存中都是一个二进制数据。都是用Bitmap对象存储的。这个内存包括图片的像素数据和本身对象。图像占用内存过多会导致OOM。但是不恰当的使用,使得OOM发生的几率更大。那为什么会发生OOM?相信大家都知道,Android系统分配给每个应用程序的内存是有限的(在Android 3.1以及更高的版本中,可以在AndroidManifest.xml的application标签中增加一个值等于“true”的android:largeHeap属性来通知Dalvik虚拟机应用程序需要使用较大的Java Heap,)。当使用内存超过限定,就会发生。所以,控制好图片内存是非常关键的。经过Android系统的更新,不同版本对Bitmap的存储有些差异。

  在Android2.3.3(API 10)及之前的版本中,Bitmap对象与其像素数据是分开存储。
Bitmap对象存储在Dalvik heap中,而Bitmap对象的像素数据则存储在Native Memory(本地内存)中或者说Derict Memory(直接内存)中。
这使得存储在Native Memory中的像素数据的释放是不可预知的,我们可以调用recycle()方法来对Native Memory中的像素数据进行释放。
前提是你可以清楚的确定Bitmap已不再使用了,如果你调用了Bitmap对象recycle()之后再将Bitmap绘制出来,就会出现"Canvas: trying to use a recycled bitmap"错误。

 在Android3.0(API 11)之后,Bitmap的像素数据和Bitmap对象一起存储在Dalvik heap中,所以我们不用手动调用recycle()来释放Bitmap对象,内存的释放都交给垃圾回收器来做。2.3以后采用并发垃圾回收,会通过垃圾回收算法检查对象是否可以回收(不同的垃圾回收器具有不同的回收算法),如果在回收器没有及时回收图片,或者某个时候创建大量的bitmap,此时就有可能会OOM。

  那么怎么恰当的处理图片减少OOM的几率呢?

  1.在Android2.3以下,使用图片,先判断图片是否有效即是否已经回收,未回收则需用recycle()方法来释放内存。比如在大量图片在加载的listview,gridview中,这可以通过计数器记录该图片引用次数,在未被引用情形下,及时回收图片。如下:

private synchronized boolean hasValidBitmap() {  
        Bitmap bitmap = getBitmap();  
        return bitmap != null && !bitmap.isRecycled();  
    } 
  2.在Android以上,垃圾回收主动回收图片,所以不用考虑回收问题。但是,因为图片的解码要耗大量内存,可以在图片解码做优化处理。怎么处理呢?

 (1)BitmapFactory.Options第一次先获取图片大小,再和要显示的view大小做一个scale,之后再做解码为view大小的图片。这样,如果原图很大,显示的view很小,就不会占用太多内存。如下:

public static Bitmap decodeStream(InputStream is, Config pixelFormat, int outWidth, int outHeight, int maxWidth, int maxHeight) {
		byte[] decode_buf = _local_buf.get();
		if (decode_buf == null) {
			decode_buf = new byte[64 * 1024];
			_local_buf.set(decode_buf);
		}

		try {
			BitmapFactory.Options opts = new BitmapFactory.Options();
			opts.inTempStorage = decode_buf;
			opts.inPreferredConfig = pixelFormat;

			opts.inJustDecodeBounds = true; //先获取图片大小
			is.mark(64 * 1024);
			BitmapFactory.decodeStream(is, null, opts);//第一次解码获取图片打下
			try {
				is.reset();
			} catch (IOException e) {
				e.printStackTrace();
				is.close();
				return null;
			}
			if ((outWidth > 0 && outHeight >= 0) || (outWidth >= 0 && outHeight > 0) ||
				(maxWidth > 0 && maxHeight >= 0) || (maxWidth >= 0 && maxHeight > 0)) {
				opts.inScaled = true;
				opts.inTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
				int scale_x;
				int scale_y;
				if (maxWidth > 0 || maxHeight > 0) {
					if (outHeight == 0 && outWidth == 0) {
						outWidth = Math.min(opts.outWidth, maxWidth);
						outHeight = Math.min(opts.outHeight, maxHeight);
					} else {
						if (outWidth > 0 && maxWidth > 0) {
							outWidth = Math.min(outWidth, maxWidth);
						}
						if (outHeight > 0 && maxHeight > 0) {
							outHeight = Math.min(outHeight, maxHeight);
						}
					}
				}
				if (outWidth == 0) {
					scale_x = scale_y = opts.inTargetDensity * opts.outHeight / outHeight;
				} else if (outHeight == 0) {
					scale_x = scale_y = opts.inTargetDensity * opts.outWidth / outWidth;
				} else {
					scale_x = opts.inTargetDensity * opts.outWidth / outWidth;
					scale_y = opts.inTargetDensity * opts.outHeight / outHeight;
				}
				opts.inDensity = Math.min(scale_x, scale_y);
				if (opts.inDensity == opts.inTargetDensity) {
					opts.inScaled = false;
					outWidth = opts.outWidth;
					outHeight = opts.outHeight;
				} else {
					outWidth = XulUtils.roundToInt((float) opts.outWidth * opts.inTargetDensity / opts.inDensity);
					outHeight = XulUtils.roundToInt((float) opts.outHeight * opts.inTargetDensity / opts.inDensity);
				}
			} else {
				outWidth = opts.outWidth;
				outHeight = opts.outHeight;
			}

				Log.i(TAG, " width " + opts.outWidth + " height " + opts.outHeight + "  ==> width " + outWidth + " height " + outHeight);
			

			opts.inJustDecodeBounds = false;
			opts.inPurgeable = false;
			opts.inMutable = true;
			if (!opts.inScaled ) {
				opts.inBitmap = createBitmapFromRecycledBitmaps(opts.outWidth, opts.outHeight, pixelFormat);//可以设置图片重用,
			} else {
				++_newCount;
			}
			opts.inSampleSize = 1;
			Bitmap bm = BitmapFactory.decodeStream(is, null, opts);//第二次解码图片,ops带scale
			return bm;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				is.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
   以上代码注意第一次设置inJustDecodeBounds为true,获取图片尺寸。再进行解码获取图片。

  另外BitmapFactory.Options.inBitmap的这个字段,假如这个字段被设置了,在解码Bitmap的时候,就会去重用inBitmap设置的Bitmap,减少内存的分配和释放,提高了应用的性能(该参数不是用来减少内存占用,而是用来内存重用,避免大内存块的申请与释放。)。因为内存的释放(GC)可能影响所有线程绘制。

   什么条件会被重用?

   Android 4.4之前,设置的Bitmap只有同等大小的图片所占用的内存才能被重用。 即宽高相等,且inSampleSize为1。 而4.4之后你可以重用任何bitmap的内存区域,只要这块内存比将要分配内存的bitmap大就可以。例如给inBitmap赋值的图片大小为100-100,那么新申请的bitmap必须也为100-100才能够被重用。从SDK 19开始,新申请的bitmap大小必须小于或者等于已经赋值过的bitmap大小。解码新申请的bitmap与旧的bitmap必须有相同的解码格式。那么就不能支持4444与565格式的bitmap了,不过可以通过创建一个包含多种典型可重用bitmap的对象池,这样后续的bitmap创建都能够找到合适的“模板”去进行重用。

  在实际的项目中,有些时候设备在设置重用图片后,会导致图片出现花屏的现象,即解码失败导致。这样的情况,就不要开启重用图片的功能。

 


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值