`
heisedeyueya
  • 浏览: 96611 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类

ListView异步加载网络图片之双缓存技术

阅读更多
ListView异步加载网络图片完美版之双缓存技术

   本示例参考学习了一个国外的示例:http://code.google.com/p/android-imagedownloader/,有兴趣的同学下载研究一下。
问题描述:在这一篇博客中将会为大家讲解如何将下载回来的图片进行缓存,为了节约流量,并且提高下一次显示图片的速度,提高用户体验,所以不能够每次调用getView的时候都去从网络下载图片,就必须用到缓存。
缓存的重点问题:如何控制缓存的大小,如果我们一直向缓存中筛数据,而没有对缓存的大小进行控制,那么最终会导致OOM
解决方案:设置两级缓存,第一级用LinkedHashMap<String,Bitmap>保留Bitmap的强引用,但是控制缓存的大小MAX_CAPACITY=10,当继续向该缓存中存数据的时候,将会把一级缓存中的最近最少使用的元素放入二级缓存ConcurrentHashMap<String, SoftReference<Bitmap>>,二级缓存中保留的Bitmap的软引用。
SoftReference:它保存的对象实例,除非JVM即将OutOfMemory,否则不会被GC回收。这个特性使得它特别适合设计对象Cache。对于Cache,我们希望被缓存的对象最好始终常驻内存,但是如果JVM内存吃紧,为了不发生OutOfMemoryError导致系统崩溃,必要的时候也允许JVM回收Cache的内存,待后续合适的时机再把数据重新Load到Cache中。这样可以系统设计得更具弹性。

// 0.75是加载因子为经验值,true则表示按照最近访问量的高低排序,false则表示按照插入顺序排序
private HashMap<String, Bitmap> mFirstLevelCache = new LinkedHashMap<String, Bitmap>(
			MAX_CAPACITY / 2, 0.75f, true) {
		private static final long serialVersionUID = 1L;
		protected boolean removeEldestEntry(Entry<String, Bitmap> eldest) {
			if (size() > MAX_CAPACITY) {// 当超过一级缓存阈值的时候,将老的值从一级缓存搬到二级缓存
				mSecondLevelCache.put(eldest.getKey(),
						new SoftReference<Bitmap>(eldest.getValue()));
				return true;
			}
			return false;
		};
	};	

加载图片:先读缓存,缓存么有就开启异步任务从网络下载
/**
	 * 加载图片,如果缓存中有就直接从缓存中拿,缓存中没有就下载
	 * @param url
	 * @param adapter
	 * @param holder
	 */
public void loadImage(String url, BaseAdapter adapter, ViewHolder holder) {
		resetPurgeTimer();
		Bitmap bitmap = getBitmapFromCache(url);// 从缓存中读取
		if (bitmap == null) {
			holder.mImageView.setImageResource(R.drawable.ic_launcher);//缓存没有设为默认图片
			ImageLoadTask imageLoadTask = new ImageLoadTask();
			imageLoadTask.execute(url, adapter, holder);//执行异步任务
		} else {
			holder.mImageView.setImageBitmap(bitmap);//设为缓存图片
		}

	}
   
读取缓存的代码:
public Bitmap getBitmapFromCache(String url) {
		Bitmap bitmap = null;
		bitmap = getFromFirstLevelCache(url);// 从一级缓存中拿
		if (bitmap != null) {
			return bitmap;
		}
		bitmap = getFromSecondLevelCache(url);//从二级缓存中拿
		return bitmap;
	}
private Bitmap getFromFirstLevelCache(String url) {
		Bitmap bitmap = null;
		synchronized (mFirstLevelCache) {
			bitmap = mFirstLevelCache.get(url);
			if (bitmap != null) {// 将最近访问的元素放到链的头部,提高下一次访问该元素的检索速度(LRU算法)
				mFirstLevelCache.remove(url);
				mFirstLevelCache.put(url, bitmap);
			}
		}
		return bitmap;
	}
private Bitmap getFromSecondLevelCache(String url) {
		Bitmap bitmap = null;
		SoftReference<Bitmap> softReference = mSecondLevelCache.get(url);
		if (softReference != null) {
			bitmap = softReference.get();
			if (bitmap == null) {// 由于内存吃紧,软引用已经被gc回收了
				mSecondLevelCache.remove(url);
			}
		}
		return bitmap;
	}

定期清理缓存
// 定时清理缓存
	private Runnable mClearCache = new Runnable() {
		@Override
		public void run() {
			clear();
		}
	};
	private Handler mPurgeHandler = new Handler();

	// 重置缓存清理的timer
	private void resetPurgeTimer() {
		mPurgeHandler.removeCallbacks(mClearCache);
		mPurgeHandler.postDelayed(mClearCache, DELAY_BEFORE_PURGE);
	}

	/**
	 * 清理缓存
	 */
	private void clear() {
		mFirstLevelCache.clear();
		mSecondLevelCache.clear();
	}

总结:这篇文章主要讲了图片的缓存技巧,拿来主义,学习从别人的代码中吸取精华,代码我也上传了,并且附有详细的注释,这里的缓存都是在内存当中,适合短期有效的缓存,如果是长期有效的图片,我们可以采用文件存储的方式,再设一级文件缓存,有兴趣的同学可以研究一下。感谢您耐心的看完了,送您一杯咖啡

  • 大小: 95.4 KB
分享到:
评论
7 楼 Kendra 2014-01-28  
  贊
6 楼 heisedeyueya 2012-11-28  
安轩之 写道
哥们,拿你的代码下载下来测试。

SoftReference<Bitmap> softReference = mSecondLevelCache.get(url);
if (softReference != null) {
bitmap = softReference.get();
if (bitmap == null) {//
mSecondLevelCache.remove(url);
}
}
return bitmap;

softReference每次都是空,可想而知你的这个代码没有缓存了。

希望改进,嘿嘿。这个源码小弟受教了,还是非常感谢。希望楼主改改,希望下次测试以后发布。

确实代码还有很多需要改进的地方,空了改一下,多谢批评
5 楼 heisedeyueya 2012-11-28  
安轩之 写道
哥们,拿你的代码下载下来测试。

SoftReference<Bitmap> softReference = mSecondLevelCache.get(url);
if (softReference != null) {
bitmap = softReference.get();
if (bitmap == null) {//
mSecondLevelCache.remove(url);
}
}
return bitmap;

softReference每次都是空,可想而知你的这个代码没有缓存了。

希望改进,嘿嘿。这个源码小弟受教了,还是非常感谢。希望楼主改改,希望下次测试以后发布。

这里为空是因为这个map中还没该url的图片缓存,所以接下来就回去晚上下载了
4 楼 heisedeyueya 2012-11-28  
安轩之 写道
不知道楼主测试了没有。如果不适用一级缓存,把一级缓存注释了,2级缓存也没有执行完,直接报错、

java.util.concurrent.RejectedExecutionException: Task android.os.AsyncTask$3@4181f7d8 rejected from java.util.concurrent.ThreadPoolExecutor@414bddd0[Running, pool size = 128, active threads = 126, queued tasks = 10, completed tasks = 26]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:1967)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:782)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1303)
at android.os.AsyncTask.executeOnExecutor(AsyncTask.java:564)
at android.os.AsyncTask.execute(AsyncTask.java:511)
at isun.heisedeyueya.ImageLoader.loadImage(ImageLoader.java:139)
at isun.heisedeyueya.MyAdapter.getView(MyAdapter.java:116)
at android.widget.AbsListView.obtainView(AbsListView.java:2012)
at android.widget.ListView.makeAndAddView(ListView.java:1772)
at android.widget.ListView.fillDown(ListView.java:672)
at android.widget.ListView.fillSpecific(ListView.java:1330)
at android.widget.ListView.layoutChildren(ListView.java:1603)
at android.widget.AbsListView.onLayout(AbsListView.java:1863)

线程池溢出了。

如果我不使用一级缓存,用二级缓存来做,应该怎么改?请赐教。

Q:375290562
谢谢

如果我们一直向缓存中筛数据,而没有对缓存的大小进行控制,那么最终会导致OOM
安轩之 写道
哥们,拿你的代码下载下来测试。

SoftReference<Bitmap> softReference = mSecondLevelCache.get(url);
if (softReference != null) {
bitmap = softReference.get();
if (bitmap == null) {//
mSecondLevelCache.remove(url);
}
}
return bitmap;

softReference每次都是空,可想而知你的这个代码没有缓存了。

希望改进,嘿嘿。这个源码小弟受教了,还是非常感谢。希望楼主改改,希望下次测试以后发布。

可以在外部控制线程的数量,应为AsynTask默认池子的大小是128。
3 楼 安轩之 2012-11-28  
不知道楼主测试了没有。如果不适用一级缓存,把一级缓存注释了,2级缓存也没有执行完,直接报错、

java.util.concurrent.RejectedExecutionException: Task android.os.AsyncTask$3@4181f7d8 rejected from java.util.concurrent.ThreadPoolExecutor@414bddd0[Running, pool size = 128, active threads = 126, queued tasks = 10, completed tasks = 26]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:1967)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:782)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1303)
at android.os.AsyncTask.executeOnExecutor(AsyncTask.java:564)
at android.os.AsyncTask.execute(AsyncTask.java:511)
at isun.heisedeyueya.ImageLoader.loadImage(ImageLoader.java:139)
at isun.heisedeyueya.MyAdapter.getView(MyAdapter.java:116)
at android.widget.AbsListView.obtainView(AbsListView.java:2012)
at android.widget.ListView.makeAndAddView(ListView.java:1772)
at android.widget.ListView.fillDown(ListView.java:672)
at android.widget.ListView.fillSpecific(ListView.java:1330)
at android.widget.ListView.layoutChildren(ListView.java:1603)
at android.widget.AbsListView.onLayout(AbsListView.java:1863)

线程池溢出了。

如果我不使用一级缓存,用二级缓存来做,应该怎么改?请赐教。

Q:375290562
谢谢
2 楼 安轩之 2012-11-28  
哥们,哦哦,搞错了。一级缓存中已经处理了。看错了,不好意思 
1 楼 安轩之 2012-11-28  
哥们,拿你的代码下载下来测试。

SoftReference<Bitmap> softReference = mSecondLevelCache.get(url);
if (softReference != null) {
bitmap = softReference.get();
if (bitmap == null) {//
mSecondLevelCache.remove(url);
}
}
return bitmap;

softReference每次都是空,可想而知你的这个代码没有缓存了。

希望改进,嘿嘿。这个源码小弟受教了,还是非常感谢。希望楼主改改,希望下次测试以后发布。

相关推荐

    listview异步加载图片乱序的解决

    listview异步加载图片乱序的解决:用到的技术LruCache图片缓存、asynctask异步下载图片

    android gridview/listview加载大量图片

    2. 将getView中需要加载的图片添加进堆栈,启动异步线程从栈顶开始加载图片,保证当前显示区域的图片被先显示。 3. 调整Load线程优先级为Thread.NORM_PRIORITY-1,不要和UI线程抢时间片,使滚动更流畅。 该代码用...

    Android应用源码安卓初学者必敲基础项目

    实现了多线程异步加载图片并更新视图,项目虽然涉及的功能并不多,但是作者在注释方面非常用心,注释的很丰富对新手学习ListView、Json、多线程、异步加载、文件缓存等安卓基础知识很有帮助,建议每位初学者都敲一边...

    图片瀑布流

    本demo通过三个listView控件,利用异步记载、缓存、线程池等技术,实现了瀑布流图片的显示效果,希望对网上异步加载图片缓存有需求的朋友有所帮助。

    Android代码-干货集中营

    volley异步加载网络,volley加载图片并缓存 glide加载图片并缓存 增加图片放大、缩小手势 支持图片下载、分享 欢迎界面 侧边栏 下拉刷新 释放刷新 正在刷新 加载更多 图片 图片 图片 all webview ...

    安卓源码包android web应用OCR图像识别listview相关EditText输入框Launcher 桌面45个合集

    [四次元]listview实现图片的异步加载.rar [四次元]listview快速滑动,修改默认的滑动条.rar [四次元]ListView滚动气泡提示.rar [四次元]listview获取网络图片缓存优化.zip [四次元]Location1014.rar [四次元]map_...

    Android应用—— 花界

    异步图片加载 (8). 统计(暂且使用友盟) (9). 广告(暂且使用有米) (10). 在线支付(集成支付宝) (11). 异步图片加载 (12).其他 原文地址: http://www.cnblogs.com/qianxudetianxia/archive/2012/04/05/2433669.html

    android初学者入门项目

    初学者必看 1、LinearLayout Button、RadioGroup...48、异步加载图片的二级缓存技术 49、QQ的好友列表展示效果 50、Fragment + ViewPager实现tab滑动切换 51、能够显示在桌面前面的的歌词效果 52、activity切换特效

    android开发demo集合

    简明、完整、全面的安卓开发demo集合,包含如下...48、异步加载图片的二级缓存技术 49、QQ的好友列表展示效果 50、Fragment + ViewPager实现tab滑动切换 51、能够显示在桌面前面的的歌词效果 52、activity切换特效

    超实用的jQuery代码段

    8.8 异步加载并解析XML文件 8.9 动态加载HTML内容到标签页中 8.10 使用AJAX刷新异步提交表单 8.11 使用AJAX刷新上传图片 8.12 使用AJAX刷新验证PHP会话是否有效 8.13 在AJAX异步调用时显示加载指示器 8.14 在AJAX...

    ASP.NET4高级程序设计第4版 带目录PDF 分卷压缩包 part1

    11.6.4 在异步任务中使用缓存 11.6.5 多异步任务和超时 11.7 总结 第12章 文件和流 12.1 使用文件系统 12.1.1 Directory类和File类 12.1.2 DirectoryInfo类和FileInfo类 12.1.3 DriveInfo类 12.1.4...

    JAVA上百实例源码以及开源项目

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    JAVA上百实例源码以及开源项目源代码

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    ASP.NET4高级程序设计(第4版) 3/3

    此外,《ASP.NET 4高级程序设计(第4版)》专门提供了两章的内容来教你如何用Ajax 技术制作快速响应的页面,以及如何使用微软的ASP.NETAJAX平台。另外,还专门介绍了ASP.NET4 新增的功能,如MVC 和动态数据等。  ...

    asp.net知识库

    ASP.NET 2.0 缓存技术 (原创) asp.net 2.0中的theme主题覆盖问题 asp.net 2.0中利用app_offline.htm功能 .NET 2.0中的字符串比较 小试ASP.NET 2.0的兼容性 为 asp.net 2.0 的菜单控件增加 target 属性 ASP.NET 2.0...

    C#编程经验技巧宝典

    111 &lt;br&gt;0184 如何在ASP.NET中获取文件的扩展名 111 &lt;br&gt;0185 如何在ASP.NET中用URL在页面之间传值 112 &lt;br&gt;0186 如何使用IsPostBack实现ASP.NET页面加载 112 &lt;br&gt;0187 如何利用输出缓存技术缓存...

    Android学习系列教程实例.pdf

    1.3.2. 加载系统内核 ........................ 15 1.3.3. 启动 Init 进程 ......................... 16 1.3.4. 启动 Zygote 进程 ................... 16 1.3.5. 启动 Runtime 进程 ................ 17 1.3.6. ...

Global site tag (gtag.js) - Google Analytics