关于Bitmap的一些优化

//10.28

关于bitmap的缓存算法,现在可以用lruCache代替。

另外最近遇到一个问题,公司里的某一个项目,通过多次生成bitmap来调整bitmap的尺寸

虽然不知道目的是什么,但是我试着用option的outWidth和outHeight代替生成bitmap后拿出来的长度和宽度

但是outWidth和outHeight都是图片的原始尺寸,所以反而没有实现最终效果

暂时没有好的想法解决


===============================================

之前接触的一些技术,没用一段时间都忘了,唉,趁现在赶紧保存起来。


由于Android只能识别PNG格式的图片,所以当我们要读取其他格式的图片,或者同时、甚至不断加载图片时,就要用到Bitmap去解码图片、生成缩略图。

但是由于生成Bitmap是直接占用内存的,所以往往加载一些大的图片或者数量变多之后,直接内存溢出。

所以关于Bitmap的优化是必须的。

以下为部分转载:

Android提供了一个用于图像解码的类BitmapFactory,其中提供了一系列方法解析图片对象,包括byte 数组,InputStream ,资源ID,或者指定的文件名。

BitmapFactory.Options 用于指定解码时的一些设置:
1)inBitmap如果设置,当加载内容时该方法将尝试重用这个位图;
2)inDensity使用像素密度来表示位图;
3)inDither如果存在抖动,解码器将尝试解码图像抖动;
4)inPurgeable如果设置为true,则由此产生的位图将分配其像素,以便系统需要回收内存时可以将它们清除;
5)inInputShareable与inPurgeable一起使用,如果inPurgeable为false那该设置将被忽略,如果为true,那么它可以决定位图是否能够共享一个指向数据源的引用,或者是进行一份拷贝;

(第4、5点说得很清楚,要一起使用。inPurgeable我的理解是,设置为true后,生成Bitmap所存储的其像素的内存空间可以在内存不足的情况下被回收,在需要再次调用该Bitmap的像素时[如重新绘制Bitmap等],系统会调用BitmapFactory decoder重新生成这些像素;如果设置为false则不能被回收,在设备不断生成Bitmap时会直接OOM)

对于这个在网上还有一些资料:

1,在Android 2.3以及之后,采用的是并发回收机制,避免在回收内存时的卡顿现象。


2,在Android 2.3.3(API Level 10)以及之前,Bitmap的backing pixel 数据存储在native memory, 与Bitmap本身是分开的,Bitmap本身存储在dalvik heap 中。导致其pixel数据不能判断是否还需要使用,不能及时释放,容易引起OOM错误。 


从Android 3.0(API 11)开始,pixel数据与Bitmap一起存储在Dalvik heap中。


6)inJustDecodeBounds如果设置,那返回的位图将为空,但会保存数据源图像的宽度和高度;

  1. BitmapFactory.Options options = new BitmapFactory.Options();
  2. options.inJustDecodeBounds = true;
  3. Bitmap bmp = BitmapFactory.decodeFile(path, options);
  4. /* 这里返回的bmp是null *

这样就可以只获取该图片的宽度和高度,而不用多占内存。而后面要再生成Bitmap时要把isJustDecodeBounds设置为false


7)inMutable如果设置,解码方法将始终返回一个可变的位图;
8)inPreferQualityOverSpeed如果设置为true,解码器将尝试重建图像以获得更高质量的解码,甚至牺牲解码速度;
9)inPreferredConfig 如果为非空,解码器将尝试解码成这个内部配置;

(一般会用这个设置像素的位数,下面是相关资料:

A:透明度

R:红色

G:绿

B:蓝

Bitmap.Config ARGB_4444:每个像素占四位,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位 

Bitmap.Config ARGB_8888:每个像素占四位,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位

Bitmap.Config RGB_565:每个像素占四位,即R=5,G=6,B=5,没有透明度,那么一个像素点占5+6+5=16位

Bitmap.Config ALPHA_8:每个像素占四位,只有透明度,没有颜色。

一般情况下我们都是使用的ARGB_8888,由此可知它是最占内存的,因为一个像素占32位,8位=1字节,所以一个像素占4字节的内存。假设有一张480x800的图片,如果格式为ARGB_8888,那么将会占用1500KB的内存。

)10)inSampleSize 如果设置的值大于1,解码器将等比缩放图像以节约内存;

(inSampleSize这个值只能取2的次幂,若设置为2时,所获取的图片长宽都为原来的1/2,则图片大小为原来的1/4)
11)inScaled如果设置,当inDensity和inTargetDensity不为0,加载时该位图将被缩放,以匹配inTargetDensity,而不是依靠图形系统缩放每次将它绘制到画布上;

12)inScreenDensity当前正在使用的实际屏幕的像素密度;
13)inTargetDensity这个位图将被画到的目标的像素密度;
14)mCancel用于指示已经调用了这个对象的取消方法的标志;
15)outHeight、outWidth图像的高度和宽度;
16)outMimeType 如果知道,这个字符串将被设置为解码图像的MIME类型


上面红字部分是最近自己用到的一些方法,没用到的等后面自己慢慢理解了。

总之,我们可以用到这些方法来给BItmap进行优化,下面是一些在网上看到的资料:


在加载图片资源时,可采用以下一些方法来避免OOM的问题:


1,在Android 2.3.3以及之前,建议使用Bitmap.recycle()方法,及时释放资源。


2,在Android 3.0开始,可设置BitmapFactory.options.inBitmap值,(从缓存中获取)达到重用Bitmap的目的。如果设置,则inPreferredConfig属性值会被重用的Bitmap该属性值覆盖。


3,通过设置Options.inPreferredConfig值来降低内存消耗:
     默认为ARGB_8888: 每个像素4字节. 共32位。
     Alpha_8: 只保存透明度,共8位,1字节。
     ARGB_4444: 共16位,2字节。
     RGB_565:共16位,2字节。
     如果不需要透明度,可把默认值ARGB_8888改为RGB_565,节约一半内存。


4,通过设置Options.inSampleSize 对大图片进行压缩,可先设置Options.inJustDecodeBounds,获取Bitmap的外围数据,宽和高等。然后计算压缩比例,进行压缩。


5,设置Options.inPurgeable和inInputShareable:让系统能及时回收内存。
      inPurgeable:设置为True,则使用BitmapFactory创建的Bitmap用于存储Pixel的内存空间,在系统内存不足时可以被回收,当应用需要再次访问该Bitmap的Pixel时,系统会再次调用BitmapFactory 的decode方法重新生成Bitmap的Pixel数组。
                        设置为False时,表示不能被回收。
      inInputShareable:设置是否深拷贝,与inPurgeable结合使用,inPurgeable为false时,该参数无意义。 
                                  True:  share  a reference to the input data(inputStream, array,etc) 。 False :a deep copy。


6,使用decodeStream代替其他decodeResource,setImageResource,setImageBitmap等方法来加载图片。
     区别: 
      decodeStream直接读取图片字节码,调用nativeDecodeAsset/nativeDecodeStream来完成decode。无需使用Java空间的一些额外处理过程,节省dalvik内存。但是由于直接读取字节码,没有处理过程,因此不会根据机器的各种分辨率来自动适应,需要在hdpi,mdpi和ldpi中分别配置相应的图片资源,否则在不同分辨率机器上都是同样的大小(像素点数量),显示的实际大小不对。


      decodeResource会在读取完图片数据后,根据机器的分辨率,进行图片的适配处理,导致增大了很多dalvik内存消耗。

       decodeStream调用过程:
             decodeStream(InputStream,Rect,Options) -> nativeDecodeAsset/nativeDecodeStream
       decodeResource调用过程:即finishDecode之后,调用额外的Java层的createBitmap方法,消耗更多dalvik内存。
             decodeResource(Resource,resId,Options)  -> decodeResourceStream (设置Options的inDensity和inTargetDensity参数)  -> decodeStream() (在完成Decode后,进行finishDecode操作)
             finishDecode() -> Bitmap.createScaleBitmap()(根据inDensity和inTargetDensity计算scale) -> Bitmap.createBitmap()

以上方法的组合使用,合理避免OOM错误。


下面贴一下在网上一些项目里看到一段BitmapCache代码,自己修改了一小部分:


import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Hashtable;

import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
/**
 * 防止溢出
 *
 */
public class BitmapCache {
	static private BitmapCache cache;
	/** 用于Chche内容的存储 */
	private Hashtable<String, BtimapRef> bitmapRefs;
	/** 垃圾Reference的队列(所引用的对象已经被回收,则将该引用存入队列中) */
	private ReferenceQueue<Bitmap> q;

	/**
	 * 继承SoftReference,使得每一个实例都具有可识别的标识。
	 */
	private class BtimapRef extends SoftReference<Bitmap> {
		private String _key = "";

		public BtimapRef(Bitmap bmp, ReferenceQueue<Bitmap> q, String key) {
			super(bmp, q);
			_key = key;
		}
	}

	private BitmapCache() {
		bitmapRefs = new Hashtable<String, BtimapRef>();
		q = new ReferenceQueue<Bitmap>();

	}

	/**
	 * 取得缓存器实例
	 */
	public static BitmapCache getInstance() {
		if (cache == null) {
			cache = new BitmapCache();
		}
		return cache;

	}

	/**
	 * 以软引用的方式对一个Bitmap对象的实例进行引用并保存该引用
	 */
	private void addCacheBitmap(Bitmap bmp, String key) {
		cleanCache();// 清除垃圾引用
		BtimapRef ref = new BtimapRef(bmp, q, key);
		bitmapRefs.put(key, ref);
	}

	/**
	 * 依据所指定的文件名获取图片
	 */
	public Bitmap getBitmap(String filename, String filepath,int itemwidth) {

		Bitmap bitmapImage = null;
		// 缓存中是否有该Bitmap实例的软引用,如果有,从软引用中取得。
		if (bitmapRefs.containsKey(filename)) {
			BtimapRef ref = (BtimapRef) bitmapRefs.get(filename);
			bitmapImage = (Bitmap) ref.get();
		}
		// 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,
		// 并保存对这个新建实例的软引用
		if (bitmapImage == null) {
			BitmapFactory.Options options = new BitmapFactory.Options();
//			options.inSampleSize=4;
//			options.inTempStorage = new byte[16 * 1024];

			// bitmapImage = BitmapFactory.decodeFile(filename, options);
			
			options.inPurgeable=true;
			options.inInputShareable=true;
			options.inPreferredConfig=Bitmap.Config.ARGB_4444;
			options.inJustDecodeBounds=true;
			bitmapImage=BitmapFactory.decodeFile(filepath, options);
			options.inSampleSize=options.outWidth / itemwidth;
			options.inJustDecodeBounds=false;
			
			BufferedInputStream buf;
			try {
				buf = new BufferedInputStream(new FileInputStream(filepath));
				bitmapImage = BitmapFactory.decodeStream(buf,null, options);
				this.addCacheBitmap(bitmapImage, filename);
			} catch (IOException e) {

				e.printStackTrace();
			}

		}

		return bitmapImage;
	}

	private void cleanCache() {
		BtimapRef ref = null;
		while ((ref = (BtimapRef) q.poll()) != null) {
			bitmapRefs.remove(ref._key);
		}
	}

	// 清除Cache内的全部内容
	public void clearCache() {
		cleanCache();
		bitmapRefs.clear();
		System.gc();
		System.runFinalization();
	}

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值