隔了很久没写博客,现在必须快速脉动回来。今天我还是接着上一个多线程中的异步加载系列中的最后一个使用异步加载实现ListView中的图片缓存及其优化。具体来说这次是一个综合Demo.但是个人觉得里面还算有点价值的就是里面的图片的缓存的实现。因为老实说它确实能在实际的项目中得到很好的应用。主要学习来源于慕课网中的异步加载学习,来自徐宜生大神的灵感。本次也就是对大神所讲知识的一个总结及一些个人的感受吧。

这次是一个综合的Demo,主要里面涉及到的知识主要有:网络编程、异步加载、JSON解析、图片缓存、通用ListAdapter的使用。最后实现一个加载网络数据的图文混排listView的效果。当然这里面涉及到的知识比较多,但是本次的重点就是图片缓存和异步加载,当然类似网络编程中的HttpURLConnection,JSON解析、打造通用适配器等知识将会在后续博客中给出,这里也就是使用我以前自己封装好的,因为为了简化开发。

这次的重点是异步加载和图片缓存,至于异步加载因为在前两个博客中已经写得很清楚了,这次主要是用一下异步加载,看看异步加载在实际项目是怎么使用的。主要是使用异步加载进行耗时网络请求,并且自定义一个监听器用于当获得数据后,立即将获得的数据回调出去。然后重点介绍的就是图片缓存。

说到图片缓存下面将通过以下几个方面认识一下图片缓存:

1、为什么要使用图片缓存?

很简单“消耗流量特别大”  , 这个相信很多人都感同深受吧,因为我们可能都写过一个类似网络请求数据的ListView的图文混排的Demo,但是如果我们直接通过网络请求图片,然后拿到的图片显示在ListView上,当滑动ListView,下次将已经滑过Item,会发现图片重新请求一个网络数据,重新加载一次,也就是滑到哪就请求一次网络,不管是否重复。可想而知这流量消耗太大,估计这样滑一晚上,第二天早上醒来,发现自己的房子都成中国移动的了。还有一个弊端就是每请求一次网络都是一次异步和耗时过程,所以你会发现在滑动ListView会有卡顿情况出现。

2、图片缓存原理是什么?

图片缓存是基于LRU算法来实现的,LRU即Least Recently Used,中文意思是最近最少未使用算法,学过操作系统原理就知道这是操作系统中页面置换算法之一。

说到这,不妨来看看LruCache源码是怎么介绍的。

/*** A cache that holds strong references to a limited number of values. Each time* a value is accessed, it is moved to the head of a queue. When a value is* added to a full cache, the value at the end of that queue is evicted and may* become eligible for garbage collection.** <p>If your cached values hold resources that need to be explicitly released,* override {@link #entryRemoved}.** <p>If a cache miss should be computed on demand for the corresponding keys,* override {@link #create}. This simplifies the calling code, allowing it to* assume a value will always be returned, even when there's a cache miss.** <p>By default, the cache size is measured in the number of entries. Override* {@link #sizeOf} to size the cache in different units. For example, this cache* is limited to 4MiB of bitmaps:* <pre>   {@code*   int cacheSize = 4 * 1024 * 1024; // 4MiB*   LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {*       protected int sizeOf(String key, Bitmap value) {*           return value.getByteCount();*       }*   }}</pre>** <p>This class is thread-safe. Perform multiple cache operations atomically by* synchronizing on the cache: <pre>   {@code*   synchronized (cache) {*     if (cache.get(key) == null) {*         cache.put(key, value);*     }*   }}</pre>** <p>This class does not allow null to be used as a key or value. A return* value of null from {@link #get}, {@link #put} or {@link #remove} is* unambiguous: the key was not in the cache.** <p>This class appeared in Android 3.1 (Honeycomb MR1); it's available as part* of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's* Support Package</a> for earlier releases.*/

LruCache主要原理:缓存是限制了缓存的数目的,也就是说缓存的容量是有限的,可以把缓存的逻辑内存结构想象一个队列,当缓存中一个缓存值被访问后,它将会被置换到队列的队头,当一个缓存值需要加到队尾时,但是此时队列已满了,也即此时缓存空间已满,那么就需要将处于队列队尾一个缓存值出队列,也即是释放队列队尾一部分缓存空间,因为基于LRU算法处于队尾的,肯定最近最少未使用。也就是因为缓存空间是有限的,才会基于这样算法,及时并合适地将一些数据空间释放。

LruCache类是线程安全的,它支持多个缓存操作自动通过异步来实现,并且这个类不允许用空值去作为key或者value,并且注意LruCache的key不是保存在缓存中的。

LurCache类出现在Android3.1版本。

3、LruCache如何创建:

LruCache实际在操作上很类似于Map的操作,初学者实际上就可以把它当做一个Map,因为它是key-value成对的,并且有put(),get()方法非常类似Map

个人觉得使用图片缓存使用率很高,为了下次方便使用,索性直接将它封装成一个工具类。

package com.mikyou.utils;import android.graphics.Bitmap;
import android.util.LruCache;public class LruCacheUtils {//创建Cache缓存,第一个泛型表示缓存的标识key,第二个泛型表示需要缓存的对象private LruCache<String, Bitmap> mCaches;public LruCacheUtils() {int maxMemory=(int) Runtime.getRuntime().maxMemory();//获取最大的应用运行时的最大内存//通过获得最大的运行时候的内存,合理分配缓存的内存空间大小int cacheSize=maxMemory/4;//取最大运行内存的1/4;mCaches=new LruCache<String, Bitmap>(cacheSize){@Overrideprotected int sizeOf(String key, Bitmap value) {//加载正确的内存大小return value.getByteCount();//在每次存入缓存的时候调用}};}//将图片保存在LruCache中public void addBitmapToCache(String url,Bitmap bitmap){if (getBitmapFromCache(url)==null) {//判断当前的Url对应的Bitmap是否在Lru缓存中,如果不在缓存中,就把当前url对应的Bitmap对象加入Lru缓存mCaches.put(url, bitmap);}}//将图片从LruCache中读取出来public  Bitmap getBitmapFromCache(String url){Bitmap bitmap=mCaches.get(url);//实际上LruCache就是一个Map,底层是通过HashMap来实现的return bitmap;}
}

通过以上知识的讲解,相信已经对LruCache有了一定的了解了,那么接下来我们就开始我们的Demo吧。

1、首先、我们既然是加载网络数据,所以得解决网络数据来源问题,主要来自于慕课网的一个课程列表的API的地址,返回的数据是JSON格式的数据。

地址是:http://www.imooc.com/api/teacher?type=4&num=60。可以先用浏览器来测试一下数据,测试结果如下:

注意:大家可能看到这里面中文全部都乱码了,这是因为Unicode编码,我会在代码中使用一个工具类将这些转化成中文。

2、数据解决后,那么接着就是布局,布局很简单,主布局就是一个ListView,listItem布局也很简单。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent" ><ListViewandroid:id="@+id/listview"android:layout_width="match_parent"android:layout_height="wrap_content"android:divider="#22000000"android:dividerHeight="0.2dp" ></ListView></RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content" android:padding="5dp"><ImageView android:id="@+id/c_img"android:layout_width="140dp"android:layout_height="90dp"android:src="@drawable/left_img"/>
<TextView android:id="@+id/c_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Android百度地图之导航"android:textSize="16sp"android:layout_marginLeft="5dp"android:layout_toRightOf="@id/c_img"android:layout_marginTop="10dp"/>
<TextViewandroid:id="@+id/c_learner" android:layout_width="wrap_content"android:layout_height="wrap_content"android:drawableLeft="@drawable/learner"android:text="7897"android:textColor="#a9b7b7"android:layout_alignBottom="@id/c_img"android:layout_alignLeft="@id/c_name"android:drawablePadding="5dp"android:layout_marginBottom="5dp"/>
</RelativeLayout>

3、自己封装的HttpURLConnection网络请求框架,返回的是整个JSON数据

package com.mikyou.utils;import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;import org.json.JSONObject;import android.R.interpolator;public class MikyouHttpUrlConnectionUtils {private static StringBuffer buffer;public static String  getData(String urlString,String apiKeyValue,List<String> stringList){buffer=new StringBuffer();String jsonOrXmlString=null;if (stringList!=null) {for (int i = 0; i <stringList.size(); i++) {urlString+=stringList.get(i);}}try {System.out.println("URL---->"+urlString);URL url=new URL(urlString);HttpURLConnection conn=(HttpURLConnection) url.openConnection();conn.setRequestMethod("GET");if (apiKeyValue!=null) {conn.setRequestProperty("apikey", apiKeyValue);}conn.setConnectTimeout(8000);conn.setReadTimeout(8000);conn.connect();if (conn.getResponseCode()==200) {InputStream is=conn.getInputStream();BufferedReader reader=new BufferedReader(new InputStreamReader(is, "UTF-8"));while ((jsonOrXmlString=reader.readLine())!=null) {buffer.append(jsonOrXmlString+"\n");}reader.close();is.close();}} catch (Exception e) {e.printStackTrace();}String string=UnicodeUtils.decodeUnicode(buffer.toString());//使用UnicodeUtils工具类将Unicode编码转换成UTF-8显示的中文return string;}
}

4、封装课程对象的javaBean类对象即每个Item为一个对象

package com.mikyou.bean;import java.io.Serializable;import android.R.id;public class Course   implements Serializable{private  String cName;private  String  cImgURl;private String cDescriptor;private String cLearner;public String getcName() {return cName;}public void setcName(String cName) {this.cName = cName;}public String getcImgURl() {return cImgURl;}public void setcImgURl(String cImgURl) {this.cImgURl = cImgURl;}public String getcDescriptor() {return cDescriptor;}public void setcDescriptor(String cDescriptor) {this.cDescriptor = cDescriptor;}public String getcLearner() {return cLearner;}public void setcLearner(String cLearner) {this.cLearner = cLearner;}}

5、通用适配器实现的子类

package com.mikyou.adapter;import java.util.List;import com.lidroid.xutils.BitmapUtils;
import com.mikyou.async.ImageLoader;
import com.mikyou.bean.Course;
import com.mikyou.cache.R;
import com.mikyou.tools.ViewHolder;
import android.content.Context;
import android.widget.ImageView;public class MyListAdapter extends CommonAdapter<Course>{
private   ImageLoader loader;public MyListAdapter(Context context, List<Course> listBeans, int layoutId) {super(context, listBeans, layoutId);loader=new ImageLoader();}@Overridepublic void convert(ViewHolder holder, Course course) {holder.setText(R.id.c_name, course.getcName()).setText(R.id.c_learner, course.getcLearner());ImageView iv= holder.getView(R.id.c_img);iv.setTag(course.getcImgURl());//首先、需要将相应的url和相应的iv绑定在一起,为了防止图片和请求URL不对应loader.showImageByAsyncTask(iv, course.getcImgURl());}}

7、核心实现代码:

package com.mikyou.async;import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;import com.mikyou.bean.Course;
import com.mikyou.utils.LruCacheUtils;import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;public class ImageLoader {private ImageView iv;private String url;private LruCacheUtils mCacheUtils;public ImageLoader() {mCacheUtils=new LruCacheUtils();}/*** @author mikyou* 实现的主要思路:* 首先、加载图片的时候,先去LruCache缓存中根据传入的url作为key去取相应的Bitmap对象* ,如果缓存中存在相应的key对应的value,那么就直接取出key对应缓存中的Bitmap对象* 并设置给ImageView,如果缓存中没有,那么就需要通过异步加载请求网络中的数据和图片信息,* 然后通过监听器中的asyncImgListener回调方法将网络请求得到的Bitmap对象,首先得通过iv.getTag()* 比较url如果对应就将该Bitmap对象设置给iv,并且还需要将这个Bitmap对象和相应的url以key-value形式* 通过put方法,加入LruCache缓存中。* */public void showImageByAsyncTask(final ImageView iv,final String url){//首先,从缓存中读取图片,如果有就直接使用缓存,如果没有就直接加载网络图片Bitmap bitmap=mCacheUtils.getBitmapFromCache(url);Log.d("url", url);if (bitmap==null) {//表示缓存中没有,就去访问网络下载图片,并记住将下载到的图片放入缓存中ImageAsyncTask imageAsyncTask=new ImageAsyncTask();imageAsyncTask.execute(url);imageAsyncTask.setOnImgAsyncTaskListener(new OnAsyncListener() {@Overridepublic void asyncListener(List<Course> mCourseList) {}@Overridepublic void asyncImgListener(Bitmap bitmap) {//图片请求网络数据的回调方法if (iv.getTag().equals(url)) {//判断url和iv是否对应iv.setImageBitmap(bitmap);Log.d("addLru", "网络加载并加入缓存--->"+url);mCacheUtils.addBitmapToCache(url, bitmap);//由于是网络请求得到的数据,所以缓存中肯定没有,所以还需要将该Bitmap对象加入到缓存中}}});}else{//否则就直接从缓存中获取iv.setImageBitmap(mCacheUtils.getBitmapFromCache(url));//直接读取缓存中的Bitmap对象Log.d("getLru", "url读出缓存--->"+url);}}//HttpURLConnection网络请求方式来得到网络图片输入流,并且将输入流转换成一个Bitmap对象public Bitmap getBitmapFromURL(String  url){Bitmap bitmap = null;try {URL mURL=new URL(url);HttpURLConnection conn=(HttpURLConnection) mURL.openConnection();bitmap = BitmapFactory.decodeStream(conn.getInputStream());conn.disconnect();} catch (Exception e) {e.printStackTrace();} return bitmap;}}

8、异步加载类实现,这里主要有两个:一个是请求整个网络的JSON数据,另一个就是请求加载网络图片,并且自定义一个监听器接口。

package com.mikyou.async;import java.util.ArrayList;
import java.util.List;import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;import com.mikyou.bean.Course;
import com.mikyou.utils.MikyouHttpUrlConnectionUtils;import android.os.AsyncTask;
import android.util.Log;public class MikyouAsyncTask extends AsyncTask<String, Void, String>{private List<Course> mCourseList;private OnAsyncListener listener;//自定义监听器接口对象引用@Overrideprotected void onPreExecute() {mCourseList=new ArrayList<Course>();super.onPreExecute();}@Overrideprotected String doInBackground(String... params) {String data=MikyouHttpUrlConnectionUtils.getData(params[0], null, null);//网络请求JSON数据return data;}@Overrideprotected void onPostExecute(String result) {//解析JSON数据Log.d("info", result);try {JSONObject object=new JSONObject(result);JSONArray array=object.getJSONArray("data");for (int i = 0; i < array.length(); i++) {Course mCourse=new Course();JSONObject object2=array.getJSONObject(i);mCourse.setcName(object2.getString("name"));mCourse.setcImgURl(object2.getString("picSmall"));mCourse.setcLearner(object2.getInt("learner")+"");mCourse.setcDescriptor(object2.getString("description"));mCourseList.add(mCourse);}if (listener!=null) {//判断是否注册了监听器listener.asyncListener(mCourseList);//通过监听器中的回调方法将异步加载得到的数据后经过解析、封装的对象集合回调出去}} catch (JSONException e) {e.printStackTrace();}super.onPostExecute(result);}public void setOnAsyncTaskListener(OnAsyncListener listener){//公布一个注册监听器的方法this.listener=listener;}
}

ImgAsyncTask异步加载类:

package com.mikyou.async;import java.net.HttpURLConnection;
import java.net.URL;import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.Image;
import android.os.AsyncTask;
import android.text.GetChars;
import android.util.LruCache;
import android.widget.ImageView;public class ImageAsyncTask extends AsyncTask<String, Void, Bitmap>{private OnAsyncListener listener;@Overrideprotected Bitmap doInBackground(String... params) {return getBitmapFromURL(params[0]);}@Overrideprotected void onPostExecute(Bitmap result) {if (listener!=null) {listener.asyncImgListener(result);}super.onPostExecute(result);}public Bitmap getBitmapFromURL(String  url){Bitmap bitmap = null;try {URL mURL=new URL(url);HttpURLConnection conn=(HttpURLConnection) mURL.openConnection();bitmap = BitmapFactory.decodeStream(conn.getInputStream());conn.disconnect();} catch (Exception e) {e.printStackTrace();} return bitmap;}public void setOnImgAsyncTaskListener(OnAsyncListener listener){this.listener=listener;}
}

自定义监听器:

监听器接口:

package com.mikyou.async;import java.util.List;import com.mikyou.bean.Course;import android.graphics.Bitmap;public interface OnAsyncListener {
public void asyncListener(List<Course> mCourseList);
public void asyncImgListener(Bitmap bitmap);
}

运行结果:

没有加入图片缓存的运行结果会发现无论什么时候滑动都会请求网络,会发现图片加载有个延迟时间:

加入图片缓存后的运行结果会发现,非常流畅,并且直接读缓存的图片时没有图片加载的延迟

最后,图片缓存LruCache实际上运用很流行,并且运用在很多流行网络框架中,我们都知道很流行的Xutils框架,其中就有一个BitmapUtils,它里面实现缓存原理也就是基于LruCache来实现的。
Demo下载链接

浅谈Android中的异步加载之ListView中图片的缓存及优化三相关推荐

  1. android 实现异步加载图片,Android中ImageView异步加载图片类

    本源码是从网络找到经修改以方便直接调用感觉用着还可以 首先在项目中添加一个专门加载图片的类AsyncImageLoaderpackage com.demo.core; import java.io.I ...

  2. Android异步加载全解析之引入二级缓存

    Android异步加载全解析之引入二级缓存 为啥要二级缓存 前面我们有了一级缓存,为啥还要二级缓存呢?说白了,这就和电脑是一样的,我们电脑有内存和硬盘,内存读取速度快,所以CPU直接读取内存中的数据, ...

  3. C#中PictureBox异步加载图片

    C#中PictureBox异步加载图片 ??yy 2017-11-05 23:30:00  443  收藏 版权 void Button1Click(object sender, EventArgs ...

  4. 浅谈Android系统进程间通信(IPC)机制Binder中的Server和Client获得Service Manager接口之路

    原文地址: http://blog.csdn.net/luoshengyang/article/details/6627260 在前面一篇文章浅谈Service Manager成为Android进程间 ...

  5. Android利用Volley异步加载(JSON和图片)完整示例

    android的json解析部分都在包org.json下,主要有以下几个类: JSONObject:可以看作是一个json对象,这是系统中有关JSON定义的基本单元,其包含一对儿(Key/Value) ...

  6. 新手教程:不写JS,在MIP页中实现异步加载数据

    从需求谈起:在 MIP 页中异步加载数据 MIP(移动网页加速器) 的 加速原理 除了靠谱的 MIP-Cache CDN 加速外,最值得一提的就是组件系统.所有 JS 交互都需要使用 MIP 组件实现 ...

  7. android Gallery实现异步加载网络图片

    之前在网上找了很多都没有这方面的资料,大概的效果是当Gallery滑动时不下载图片,当Gallery滑动停止时加载当前页面图片,自己花了一点时间大概的实现了,如果各位有更好的意见欢迎说出来大家一起学习 ...

  8. listview中getview异步加载网络图片

    前言:本以为异步加载挺简单,因为网上代码多,但真想要做好,还真不那么简单,从看代码到弄懂再到自己写,实在是有太多的东西需要学了,用了两天的时间,终于弄出来了,因为用到回调函数,所以理解起来可能难度有点 ...

  9. Android利用universal-image-loader异步加载大量图片完整示例

    MainActivity如下: package cc.testlistview; import java.util.ArrayList; import com.example.testlistview ...

最新文章

  1. 最近对python颇有兴趣
  2. python ide如何运行_如何在Ubuntu上安装IDLE Python IDE
  3. php任何读取外键数据,在表中设置外键实现的是哪一类数据完整性
  4. MAC修改python和pip版本
  5. JAVA项目中出现部分中文乱码问题
  6. Storm的acker确认机制
  7. 【SQL编程】Greenplum 实现树结构+自定义函数+避免函数重复调用+ function cannot execute on a QE slice 问题处理(优化过程全记录)
  8. Java中的CopyOnWrite
  9. [转]Velocity脚本摘要
  10. .ai域名注册已经极具投资价值进入火爆期
  11. live2dviewer android,live2dviewerex安卓版
  12. dev用不了_惊艳!小姐姐用动画图解 Git 的 10 大命令,这也太秀了吧!
  13. java切面获取异常日志_spring aop 配置切面,记录系统异常存入log日志
  14. php sql判断l列的存在,thinkphp 模块不存在:404
  15. 中文数字转换为阿拉伯数字
  16. day00 【后台】Readme
  17. Hadoop权威指南-读书笔记
  18. 鲲鹏开发者技术峰会·福州圆满落幕!
  19. 面试篇-Spring 拦截器和过滤器的区别?
  20. 南京大学计算机学院杨老师,南京大学计算机系名师风采_跨考网

热门文章

  1. Python3爬取前程无忧数据分析工作并存储到MySQL
  2. C# 利用 Spire.PDF 实现.pdf转图片
  3. ace 官网地址以及相关的下载地址--防止自己忘记
  4. v-charts 设置柱状图每个柱子颜色
  5. 小米是否真的可以干翻华为?
  6. Fluent保存的h5文件无法用Tecplot打开的问题
  7. 沟通的艺术——情绪:感觉、思考和沟通
  8. 地形建模(二)--TIN拉伸成模型并贴纹理
  9. 无盘工作站给服务器ip地址协议,如何给无盘工作站安装TCP IP协议
  10. 工具传送门(持续更新)