缓存Bitmap Caching Bitmaps

加载一个bitmap到你的UI中是很简单的,然而当你要同时加载一大批图片就会变得很复杂了。在很多情况下(比如ListView,GridView或者ViewPager),屏幕上的图片总数和即将滚动到屏幕的图片会根本没有限制。

可用内存空间在这种将屏幕外的子view回收的组件下会一直下降,这是在假设你没有保持任何有用的长引用的情况下。这样做很好,但是为了保持流畅和快速加载UI,你需要避免在这些图片每次回到屏幕时都去不断地处理。


这个教程会教你如何使用内存和磁盘缓存处理bitmap,在加载很多bitmap的时候,提升你的UI组件的响应速度和流畅性。


使用内存缓存



内存缓存可以在耗费宝贵的内存的情况下快速的读取bitmap。LruCache类(同样适用API级别4之前使用Support类库),特别适合用来缓存bitmap,它会保持对最近使用的对象的强引用,这是在强引用的LinkedHashMap中实现的,并且它会在将耗尽限定大小内存前取消最进最少使用的资源。

注意:在过去,一个流行的实现内存缓存的方法是SoftReference或WeakReference bitmap的缓存,然而,这并不推荐。Android 2.3(API 级别9)起,垃圾收集器会在收集soft/weak 引用 变得更加激进,这样回使得它们变得基本上不起作用。另外,在Android 3.0(API 级别11)之前,备份bitmap的数据是存储在一个native内存中,它不是用一种可以预测的方式释放的,会潜在地造成应用程序很快的耗尽它的内存限制并崩溃。

为了选择一个合适的LruCache大小,有很多因素需要被考虑到,例如:

·你剩下的activity或程序会有宽裕的内存使用吗?

·有多少图片会一下展示在屏幕上?有多少图片需要准备显示在屏幕上?

·设置的屏幕尺寸和密度是多少?一个特变高密度pm(xhdpi)的设备,比如Galaxy Nexus 会比其它设置像Nexus S(hdpi)需要一个大的缓存去在内存中支持同样多的图片.

·bimap的尺寸和配置如何,那么有多少内存会被占用?

·图片的访问频率是怎么样的?是不是有些会被访问的比其他的频繁些呢?如果这样,你可能想保证这些图片一直在内存中,甚至是创建多个不同的LruCache对象来处理不同组的bitmap。

·你可以平衡质量和数量吗?有时候,存储大量的低质量bitmap,在后台任务中静默下载高质量版本的图片会很有效果

没有一个特定的尺寸和公式适合所有的程序,这决定于你分析你的内存情况,提出一个合适的方案。缓存太小会造成额外的开销,没有任何益处,缓存太大,同样会造成java.lang.OutOfMemory异常,让你的程序工作在很少的内存下。

 下面是一个设置bitmap的LruCache示例。

private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    //获取最大可以虚拟机内存,超过这个数量会抛出OutOfMemory异常
   //LruCache存储单位是kb,它的构造方法含有一个int参数
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // Use 1/8th of the available memory for this memory cache.
   // 使用1/8的可用内存来做缓存
    final int cacheSize = maxMemory / 8;

    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
	//这个缓存的大小是用kb来衡量的,而不是按个数
            return bitmap.getByteCount() / 1024;
        }
    };
    ...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}

注意:在这个例子中,1/8的程序内存被分配到我们的缓存中。在普通/高像素的设备中,这个值至少是4MB(32/8).一个全屏的GridView,用图片填满一个800x480分辨率的设备会使用到大概1.5MB(800*480*4字节),那么,这个内存至少可以缓存2.5页图片。



当加载bitmap到ImageView中是,LruCache会先被检查一下。如果一个键值被找到,那它会马上用来去更新ImageView,否则,一个后台线程会被生成去处理这个图片。

 

public void loadBitmap( int resId,ImageView imageView){	

	final String imageKey = String.valueOf(resId);

	final Bitmap bitmap = getBitmapFromMemCache(imageKey);
	if(bitmap!=null){
 	   mImageView.setImageBitmap(bitmap);
 	}else{
   	mImageView.setImageResource(R.drawable.imge_placeholder);
	BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
	task.execute(resId);
	}
}


BitmapWorkerTask 同样需要更新,用来添加键值到内存缓存中。
class BitmapWorkerTask extends AsyncTask<Integer,Void,Bitmap>{
  ...
  //后台解析图片
   @override
   protected Bitmap doInBackGround(Integer... parms){
    final Bitmap bitmap = decodeSampledBitmapFromResource(getResouces(),param[0],100,100));
    addBitmapToMemoryCache(String.valueOf(params[0]),bitmap);
     return bitmap;
  }
...
}



使用磁盘缓存 

内存缓存是一个有效提高访问最近看过的bitmap的方法,然而,你不能依赖所有的图片都可以在内存中使用。类似GridView的组件都会附带大量的信息,可以很容易的填满所有的内存缓存。你的程序可能被其它任务中断,比如电话,并且,在后台的时候,它可能会被杀死,内存缓存会被销毁。一旦用户重新使用,你的程序就必须再次处理这些图片。 磁盘缓存可以用在这些例子中,通过持久化处理bitmap,来帮助减少加载内存缓存中没有的图片的时间。当然,从磁盘读取图片会比在内存中加载图片要慢,并且应该在后台线程中处理,这是因为磁盘的读取时间也是无法预计的。 注意:ContentProvider应该是一个很适合存储频繁访问的缓存图片的地方,例如,一个图片浏览程序。 这个示例程序中使用了一个实现了DiskLruCache的类 ,这是从 Android source中提取出来的。这是更新的示例代码,它在已存在的内存缓存中添加了磁盘缓存。

private DiskLruCache mDisLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private static final int DISK_CACHE_SIZE = 1024*1024*10;//10MB
private static final String disk_CACHE_SUBDIR = "thumbnails";

@Override
protected void onCreate(Bundle savedInstanceState){
 ...
 // 初始化内存缓存
...
 //在后台线程中初始化磁盘缓存
  File cacheDir = getdiskCacheDir(this,DISK_CACHE_SUBDIR);
  new InitDiskCacheTask().execute(cacheDir);
  ...

}

class InitDiskCacheTask extends AsyncTask<File,Void,Void>{
  @Override
	protected Void doInBackGround(File... params){
	 synchronized(mdiskCahceLock){
         	File cacheDir = params[0];
 		mDiskLruCache = DiskLruCache.open(cacheDir,DISK_CACHE_SIZE);
		mDiskCacheStarting = false;  //初始化完成
		mDiskCacheLock.notifyAll(); // 缓存所有等待的线程
     }
	return null;
   }
}

class BitmapWorkerTask extends AsyncTask<Integer,Void,Bitmap>{

  ...
// 后台解码图片

	@Override
	protected Bitmap doInBackground(Integer... params){
		final String imageKey = String.valueOf(params[0]);
		

	//后台检查磁盘缓存
	Bitmap bitmap = getBitmapFromDiskCache(imageKey);
	
	if(bitmap==null){//没有在磁盘缓存中找到
		//普通方式处理
		final Bitmap bitmap = decodeSampledBitmapFromResource(getResources(),params[0],100,100);
	
	}
 	//添加bitmap到缓存中
	addBitmapToCache(imageKey,bitmap);
 	return bitmap;

}

public void addBitmapToCache(String key,Bitmap bitmap){
	// 像以前一样添加到内存缓存中
	if(getBitmapFromMemCache==null){
		mMemoryCache.put(key,bitmap);
	}
	//同时添加中磁盘缓存中
	synchronized(mdiskCacheLock){
		if(mDiskLruCache != null&& mDiskLruCache.get(key) == null){
			mDiskLruCache.put(key,bitmap);
		}
	}
}

public Bitmap getBitmapFromDiskCache(String key){
	synchronized(mDiskCacheLock){
	//等待磁盘缓存从后台线程中开始
	while(mDiskCacheStarting){
	 try{
		mDiskCacheLock.wait();
	}catch(InterruptedException e){}
	}
	if(mDiskLruCache != null){
		return mDiskLruCache.get(key);
	}
	}
	return null;	
	}

//创建一个唯一的子目录在指定的程序缓存目录中。尝试使用外部空间,但是如果没有挂载,那么
//就恢复使用内部空间
	public static File getDiskCacheDir(Context context,String uniqueName){
	  //检查媒体是否挂载或存储空间是否内置,如果是,那么尝试使用外部存储目录
	//否则使用内存缓存目录
	final Sting cachePath = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())||!isExternalStorageRemovable()?getExternalCacheDir(context).getPath():context.getCacheDir().getPath();

 return new File(cachePath+File.separator +uniqueName);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值