关闭

打造完美的ImageLoader——LruCache+DiskLruCache

标签: androidImageLoaderLruCacheDiskLruCache
1960人阅读 评论(1) 收藏 举报
分类:

做android应用少不了要和网络打交道,在我刚开始学android的时候总是处理不好网络图片的加载,尤其是图片乱跳的问题,后来发现了各种缓存图片的方法:本地缓存、软引用、LruCache....

我知道的这三种方法中,第一中和其他两种并不冲突,我们完全可以缓存到本地一份,在缓存到内存中一份。软引用这样方式,第一次使用软引用的时候,感觉做一个完美的内存缓存太容易了,可惜在android2.3以后android加强了对软引用的回收,这种方式基本上算是废了。


LruCache登场

软引用废了,难道就没有替代品了吗? 有,android sdk中google官方添加了LruCache类, 该类使用Lru算法实现内存缓存,关于LruCache的使用,可以google一下,很简单。


本地缓存方案

以前我的本地缓存方案都是自己写流实现的,这种方式完全可以,而且也不是很费劲,现在也可以使用这种方式。但是,有一个更好用的方式:DiskLruCache,广域DiskLruCache,还是google一下就ok。这里说明一下,DiskLruCache并没有在SDK中需要我们自己下载,可以使用我在github上fork的:https://github.com/qibin0506/DiskLruCache  直接将源码copy到项目中即可。


ImageLoader的思路

给定一个url,我们通过这个url,先去内存中取图片,如果内存中不存在,在去本地获取,本地存在的话,再添加到内存中,不存在就去网络中下载,下载完毕后,缓存到本地和内存中。


实现

思路很清晰,那么现在开始跟着思路实现一下代码吧。

public class ImageLoader {
	private static final int SUCCESS = 1;
	private static final int FAILED = 0;
	
	private static int sImageWidth;
	
	private static Context sContext;
	private static ImageLoader sInstance;
	private static ExecutorService sThreadPool = Executors.newCachedThreadPool();
	private LruCache<String, Bitmap> mLruCache;
	private DiskLruCache mDiskCache;
	
	/**
	 * 初始化ImageLoader
	 * @param context
	 * @param width 预想的图片宽度
	 */
	public static void init(Context context, int width) {
		sContext = context;
		sImageWidth = width;
	}

	/**
	 * 单例 获取ImageLoader的实例
	 * @return
	 */
	public synchronized static ImageLoader getInstance() {
		if(null == sInstance) {
			sInstance = new ImageLoader();
		}
		return sInstance;
	}
	
	private ImageLoader() {
		// begin 初始化LruCache
		int maxMemory = (int) Runtime.getRuntime().maxMemory();
		int maxSize = maxMemory / 8;
		mLruCache = new LruCache<String, Bitmap>(maxSize) {
			@Override
			protected int sizeOf(String key, Bitmap value) {
				return value.getByteCount();
			}
		};
		// end
		
		// begin 初始化DiskLruCache
		try {
			mDiskCache = DiskLruCache.open(getCacheDir(), getAppVersion(), 1, 10*1024*1024);
		} catch (Exception e) {
			e.printStackTrace();
		}
		// end
	}
	
	public void load(final String url, final OnImageListener l) {
		final Handler handler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				switch (msg.what) {
				case SUCCESS:
					l.onResult((Bitmap) msg.obj);
					break;
				case FAILED:
					l.onResult(null);
					break;
				}
			}
		};
		
		// 从memory中获取
		Bitmap bmp = getFromLru(url);
		if(null != bmp) {
			System.out.println("--getFromLru");
			Message msg = handler.obtainMessage(SUCCESS, bmp);
			msg.sendToTarget();
			return;
		}
		
		// 如果memory中没有
		// 从disk中获取
		bmp = getFromDisk(url);
		if(null != bmp) {
			System.out.println("---getFromDisk");
			Message msg = handler.obtainMessage(SUCCESS, bmp);
			msg.sendToTarget();
			return;
		}
		
		// 如果disk中没有, 则从网络中下载
		sThreadPool.execute(new Runnable() {
			@Override
			public void run() {
				System.out.println("----getFromNet");
				DefaultHttpClient client = null;
				try {
					client = new DefaultHttpClient();
					HttpGet get = new HttpGet(url);
					HttpResponse response = client.execute(get);
					if(200 == response.getStatusLine().getStatusCode()) {
						InputStream in = response.getEntity().getContent();
						Bitmap bmp = BitmapFactory.decodeStream(in);
						bmp = scaleImage(bmp);
						
						// 缓存到本地
						addToDisk(url, bmp);
						// 缓存到memory
						addToLru(url, bmp);
						Message msg = handler.obtainMessage(SUCCESS, bmp);
						msg.sendToTarget();
					}
				} catch (Exception e) {
					e.printStackTrace();
					handler.sendEmptyMessage(FAILED);
				} finally {
					if(null != client) {
						client.getConnectionManager().shutdown();
					}
				}
			}
		});
	}
	
	// 缩放图片
	private Bitmap scaleImage(Bitmap bmp) {
		int sample = bmp.getWidth() / sImageWidth;
		int height = bmp.getHeight() / sample;
		return ThumbnailUtils.extractThumbnail(bmp, sImageWidth, height);
	}
	
	// 从memory中获取
	private Bitmap getFromLru(String url) {
		return mLruCache.get(Encrypt.md5(url));
	}
	
	// 添加到内存中
	private void addToLru(String url, Bitmap bmp) {
		if(getFromLru(url) == null) {
			System.out.println("++addToLru");
			mLruCache.put(Encrypt.md5(url), bmp);
		}
	}
	
	// 从本地缓存获取
	private Bitmap getFromDisk(String url) {
		Bitmap bmp = null;
		try {
			DiskLruCache.Snapshot snapshot = mDiskCache.get(Encrypt.md5(url));
			InputStream in = snapshot.getInputStream(0);
			bmp = BitmapFactory.decodeStream(in);
			addToLru(url, bmp); // 这里可以断言 内存中肯定没有
			in.close();
		} catch (Exception e) {
		}
		return bmp;
	}
	
	// 添加到本地缓存
	private void addToDisk(String url, Bitmap bmp) throws Exception {
		System.out.println("+++addtoDisk");
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		bmp.compress(CompressFormat.PNG, 100, baos);
		byte[] buf = baos.toByteArray();

		DiskLruCache.Editor editor = mDiskCache.edit(Encrypt.md5(url));
		OutputStream out = editor.newOutputStream(0);
		out.write(buf);
		out.flush();
		editor.commit();
	}
	
	// 获取缓存目录
	private File getCacheDir() {
		File dir = null;
		if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
			dir = new File(sContext.getExternalCacheDir().getPath() + File.separator + "images" + File.separator);
		}else {
			dir = new File(sContext.getCacheDir().getPath() + File.separator + "images" + File.separator);
		}
		return dir;
	}
	
	// 获取软件版本
	private int getAppVersion() {
		PackageInfo pi;
		try {
			pi = sContext.getPackageManager().getPackageInfo(sContext.getPackageName(), 0);
			return pi.versionCode;
		} catch (NameNotFoundException e) {
			e.printStackTrace();
		}
		return 0;
	}
	
	public interface OnImageListener {
		public void onResult(Bitmap bmp);
	}
}

可以看到ImageLoader中有一个init方法,该方法主要是初始化ImageLoader类中的context和我们预想的图片的宽度,为什么没有高度? 高度要根据宽度比自动计算的。

27~32行,获取ImageLoader的实例, 因为ImageLoader是单例的。

构造方法中,36~43行初始化LruCache,47~51行初始化DiskLruCache, DiskLruCache的静态方法open接收4个参数,分别是:缓存的路径,软件的版本号,一个key对应多少文件,最大缓存的大小。

load方法中,71~77行,从memory中尝试获取图片,如果获取到了,则发送消息,并停止执行。81~87行,如果从memory中获取不到,则尝试从本地缓存中获取,如果获取到了,则发送消息,并停止执行。94~110行,从网络下载图片,并添加到本地缓存和memory中。

scaleImage方法,是一个简单的缩放图片功能,使用了ThumbnailUtils.extractThumbnail,如果在低版本中,需要自己去实现。

131~141行,是从memory中获取和添加到memory中。

144~169行,是从本地缓存中获取和添加到本地缓存。

剩下的两个方法是获取缓存路径和软件版本号。


ImageLoader定义好了以后,怎么使用呢? 很简单,调用load方法就行。

首先自定义一个Adapter继承自BaseAdapter,

public class ImageAdapter extends BaseAdapter {
	private Context mContext;
	private String[] mData;

	public ImageAdapter(Context context, String[] data) {
		mContext = context;
		mData = data;
		initImageLoader();
	}
	
	private void initImageLoader() {
		WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
		DisplayMetrics dm = new DisplayMetrics();
		wm.getDefaultDisplay().getMetrics(dm);
		ImageLoader.init(mContext, dm.widthPixels / 3);
	}
	
	@Override
	public int getCount() {
		return mData.length;
	}

	@Override
	public Object getItem(int position) {
		return mData[position];
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		final ViewHolder holder;
		if(null == convertView) {
			convertView = View.inflate(mContext, R.layout.image_item, null);
			holder = new ViewHolder();
			holder.imageView = (ImageView) convertView.findViewById(R.id.iv);
			convertView.setTag(R.id.iv, holder);
		}else {
			holder = (ViewHolder) convertView.getTag(R.id.iv);
		}
		
		holder.imageView.setImageResource(R.drawable.ic_launcher);
		
		ImageLoader imageLoader = ImageLoader.getInstance();
		imageLoader.load(mData[position], new ImageLoader.OnImageListener() {
			@Override
			public void onResult(Bitmap bmp) {
				holder.imageView.setImageBitmap(bmp);
			}
		});
		
		return convertView;
	}
	
	class ViewHolder {
		ImageView imageView;
	}
}
标准的BaseAdapter,需要注意到是第45行,主要是为了解决图片跳动的问题。


最后看一下效果吧:



图片借用了郭神的, 实在是懒得自己去找了。 效果还不错,挺顺畅的。

0
1
查看评论

Android使用LruCache,DiskLruCache结合线程池打造类似(ImageLoader)图片加载器

虽然进行移动开发已经好几年了(主要是android和ios)  也时常到csdn瞻仰牛人的技术文章 但由于比较懒所以很少写博客 这段时间比较充裕 所以来csdn时间就多了点 让我写这篇文章的机缘是 http://blog.csdn.net/lmj623565791/article...
  • a859569967m
  • a859569967m
  • 2016-01-16 20:36
  • 149

Android-Universal-Image-Loader学习笔记(二)--LruDiscCache

LruDiscCache
  • chunqiuwei
  • chunqiuwei
  • 2014-06-30 17:03
  • 4468

Android 异步加载图片-LruCache和SD卡或手机缓存-三级缓存原理加载图片

转载请注明出处http://blog.csdn.net/xiaanming/article/details/9825113 异步加载图片的例子,网上也比较多,大部分用了HashMap> imageCache ,但是现在已经不再推荐使用这种方式了,因为从 Android 2...
  • lsyz0021
  • lsyz0021
  • 2016-04-28 01:16
  • 1050

【LruCache和DiskLruCache结合】图片缓存机制

【LruCache和DiskLruCache结合】图片缓存机制 版权声明:本文为博主原创文章,未经博主允许不得转载。 本文是对网络上几篇文章的综合, Android照片墙完整版,完美结合LruCache和DiskLruCache - 郭霖的专栏 - 博客频道 - C...
  • u010375364
  • u010375364
  • 2016-07-07 00:25
  • 454

Android 开源框架Universal-Image-Loader完全解析(二)--- 图片缓存策略详解

本篇文章继续为大家介绍Universal-Image-Loader这个开源的图片加载框架,介绍的是图片缓存策略方面的,如果大家对这个开源框架的使用还不了解,大家可以看看我之前写的一篇文章Android 开源框架Universal-Image-Loader完全解析(一)--- 基本介绍及使用,我们一般...
  • xiaanming
  • xiaanming
  • 2014-06-05 11:00
  • 67516

ImageLoader使用的DiskLruCache硬盘缓存算法

转载自:简书的排版 最近在研究ImageLoader的源码,发现一个硬盘缓存比较通用的类,这个类不属于谷歌官方却受官方亲睐,基本硬盘缓存都可以利用这个类来实现。 我们先来说一下缓存记录文件journal文件:journal文件作用:记录缓存的文件的行为:删除、读取、正在编辑等状态。 “`...
  • u010782846
  • u010782846
  • 2017-09-20 14:14
  • 136

ImageLoader硬盘缓存解析

概述 我要说的就是鼎鼎大名的Universal Image Loader,UIL是非常强大的一款图片加载框架,它不仅支持本地图片加载也支持网络图片加载还支持Android自身的drawable文件夹,asset文件夹里面的图片文件加载,也支持视频文件的缩略图加载。可以说是一款非常全面而强大的图...
  • wangdong20
  • wangdong20
  • 2016-03-11 16:16
  • 1593

打造完美的ImageLoader——LruCache+DiskLruCache

做android应用少不了要和网络打交道,在我刚开始学android的时候总是处理不好网络图片的加载,尤其是图片乱跳的问题,后来发现了各种缓存图片的方法:本地缓存、软引用、LruCache.... 我知道的这三种方法中,第一中和其他两种并不冲突,我们完全可以缓存到本地一份,在缓存到内存中一份。软引...
  • qibin0506
  • qibin0506
  • 2014-12-01 11:07
  • 1960

LruCache和DiskLruCache实现二级缓存的自定义ImageLoader

  • 2016-03-15 15:26
  • 6.44MB
  • 下载

打造我们心中永恒的m500

最经典的关于M500的帖子,转自http://bbs.tompda.com/thread-1691618-1-1.html郑重申明一下,我的帖子,我自己编辑整理制作的资料,只要不以盈利为目的,且主要目的是为了更多的胖友能共享知识,欢迎转载,但转载请保留文章或者资料原本的完整性. 无论是转载还是引用...
  • JefferyLee
  • JefferyLee
  • 2007-11-19 16:10
  • 6559
    个人资料
    • 访问:661187次
    • 积分:6959
    • 等级:
    • 排名:第3940名
    • 原创:80篇
    • 转载:0篇
    • 译文:2篇
    • 评论:629条
    文章分类
    博客专栏
    友情链接

    鸿洋_

    Aggie的博客

    梁肖技术中心

    极客导航

    最新评论