Android中使用二级缓存、异步加载批量加载图片完整案例

Android中使用二级缓存、异步加载批量加载图片完整案例,第1张

概述一、问题描述Android应用中经常涉及从网络中加载大量图片,为提升加载速度和效率,减少网络流量都会采用二级缓存异步加载机制,所谓二级缓存就是通过先从内存中获取、再从文件中获取,最后才会访问网络。内存缓存

一、问题描述

AndroID应用中经常涉及从网络中加载大量图片,为提升加载速度和效率,减少网络流量都会采用二级缓存和异步加载机制,所谓二级缓存就是通过先从内存中获取、再从文件中获取,最后才会访问网络。内存缓存(一级)本质上是Map集合以key-value对的方式存储图片的url和Bitmap信息,由于内存缓存会造成堆内存泄露, 管理相对复杂一些,可采用第三方组件,对于有经验的可自己编写组件,而文件缓存比较简单通常自己封装一下即可。下面就通过案例看如何实现网络图片加载的优化。

二、案例介绍

案例新闻的列表图片

三、主要核心组件

下面先看看实现一级缓存(内存)、二级缓存(磁盘文件)所编写的组件

1、MemoryCache

在内存中存储图片(一级缓存),采用了1个map来缓存图片代码如下:

public class MemoryCache {  // 最大的缓存数   private static final int MAX_CACHE_CAPACITY = 30;  //用Map软引用的Bitmap对象,保证内存空间足够情况下不会被垃圾回收    private HashMap<String,SoftReference<Bitmap>> mCacheMap =       new linkedHashMap<String,SoftReference<Bitmap>>() {      private static final long serialVersionUID = 1L;//当缓存数量超过规定大小(返回true)会清除最早放入缓存的        protected boolean removeEldestEntry(Map.Entry<String,SoftReference<Bitmap>> eldest){        return size() > MAX_CACHE_CAPACITY;};  };  /**   * 从缓存里取出图片   * @param ID   * @return 如果缓存有,并且该图片没被释放,则返回该图片,否则返回null   */  public Bitmap get(String ID){    if(!mCacheMap.containsKey(ID)) return null;    SoftReference<Bitmap> ref = mCacheMap.get(ID);    return ref.get();  }  /**   * 将图片加入缓存   * @param ID   * @param bitmap   */  public voID put(String ID,Bitmap bitmap){    mCacheMap.put(ID,new SoftReference<Bitmap>(bitmap));  }  /**   * 清除所有缓存   */  public voID clear() {  try {      for(Map.Entry<String,SoftReference<Bitmap>>entry :mCacheMap.entrySet()) {  SoftReference<Bitmap> sr = entry.getValue();      if(null != sr) {        Bitmap bmp = sr.get();        if(null != bmp) bmp.recycle();      }    }    mCacheMap.clear();  } catch (Exception e) {      e.printstacktrace();}  }}@H_403_31@

2、fileCache

在磁盘中缓存图片(二级缓存),代码如下

public class fileCache {   //缓存文件目录   private file mCacheDir;  /**   * 创建缓存文件目录,如果有SD卡,则使用SD,如果没有则使用系统自带缓存目录   * @param context   * @param cacheDir 图片缓存的一级目录   */public fileCache(Context context,file cacheDir,String dir){if(androID.os.Environment.getExternalStorageState().equals、(androID.os.Environment.MEDIA_MOUNTED))      mCacheDir = new file(cacheDir,dir);   else    mCacheDir = context.getCacheDir();// 如何获取系统内置的缓存存储路径   if(!mCacheDir.exists()) mCacheDir.mkdirs();  }  public file getfile(String url){    file f=null;    try {//对url进行编辑,解决中文路径问题      String filename = URLEncoder.encode(url,"utf-8");      f = new file(mCacheDir,filename);    } catch (UnsupportedEnCodingException e) {      e.printstacktrace();    }    return f;  }  public voID clear(){//清除缓存文件    file[] files = mCacheDir.Listfiles();    for(file f:files)f.delete();}}@H_403_31@

3、编写异步加载组件AsyncImageLoader

androID中采用单线程模型即应用运行在UI主线程中,且AndroID又是实时 *** 作系统要求及时响应否则出现ANR错误,因此对于耗时 *** 作要求不能阻塞UI主线程,需要开启一个线程处理(如本应用中的图片加载)并将线程放入队列中,当运行完成后再通知UI主线程进行更改,同时移除任务――这就是异步任务,在androID中实现异步可通过本系列一中所用到的AsyncTask或者使用thread+handler机制,在这里是完全是通过代码编写实现的,这样我们可以更清晰的看到异步通信的实现的本质,代码如下

public class AsyncImageLoader{  private MemoryCache mMemoryCache;//内存缓存  private fileCache mfileCache;//文件缓存  private ExecutorService mExecutorService;//线程池//记录已经加载图片的ImageVIEw  private Map<ImageVIEw,String> mImageVIEws = Collections      .synchronizedMap(new WeakHashMap<ImageVIEw,String>());//保存正在加载图片的url   private List<LoadPhotoTask> mTaskQueue = new ArrayList<LoadPhotoTask>();  /**   * 默认采用一个大小为5的线程池   * @param context   * @param memoryCache 所采用的高速缓存   * @param fileCache 所采用的文件缓存   */  public AsyncImageLoader(Context context,MemoryCache memoryCache,fileCache fileCache) {    mMemoryCache = memoryCache;    mfileCache = fileCache;    mExecutorService = Executors.newFixedThreadPool(5);//建立一个容量为5的固定尺寸的线程池(最大正在运行的线程数量)  }  /**   * 根据url加载相应的图片   * @param url   * @return 先从一级缓存中取图片有则直接返回,如果没有则异步从文件(二级缓存)中取,如果没有再从网络端获取   */  public Bitmap loadBitmap(ImageVIEw imageVIEw,String url) {    //先将ImageVIEw记录到Map中,表示该ui已经执行过图片加载了    mImageVIEws.put(imageVIEw,url);    Bitmap bitmap = mMemoryCache.get(url);//先从一级缓存中获取图片    if(bitmap == null) {      enquequeLoadPhoto(url,imageVIEw);//再从二级缓存和网络中获取    }    return bitmap;  }  /**   * 加入图片下载队列   * @param url   */  private voID enquequeLoadPhoto(String url,ImageVIEw imageVIEw) {    //如果任务已经存在,则不重新添加    if(isTaskExisted(url))      return;    LoadPhotoTask task = new LoadPhotoTask(url,imageVIEw);    synchronized (mTaskQueue) {      mTaskQueue.add(task);//将任务添加到队列中          }    mExecutorService.execute(task);//向线程池中提交任务,如果没有达到上限(5),则运行否则被阻塞  }  /**   * 判断下载队列中是否已经存在该任务   * @param url   * @return   */  private boolean isTaskExisted(String url) {    if(url == null)      return false;    synchronized (mTaskQueue) {      int size = mTaskQueue.size();      for(int i=0; i<size; i++) {        LoadPhotoTask task = mTaskQueue.get(i);        if(task != null && task.getUrl().equals(url))          return true;      }    }    return false;  }  /**   * 从缓存文件或者网络端获取图片   * @param url   */  private Bitmap getBitmapByUrl(String url) {    file f = mfileCache.getfile(url);//获得缓存图片路径    Bitmap b = ImageUtil.decodefile(f);//获得文件的Bitmap信息    if (b != null)//不为空表示获得了缓存的文件      return b;    return ImageUtil.loadBitmapFromWeb(url,f);//同网络获得图片  }  /**   * 判断该ImageVIEw是否已经加载过图片了(可用于判断是否需要进行加载图片)   * @param imageVIEw   * @param url   * @return   */  private boolean imageVIEwReused(ImageVIEw imageVIEw,String url) {    String tag = mImageVIEws.get(imageVIEw);    if (tag == null || !tag.equals(url))      return true;    return false;  }  private voID removeTask(LoadPhotoTask task) {    synchronized (mTaskQueue) {      mTaskQueue.remove(task);    }  }  class LoadPhotoTask implements Runnable {    private String url;    private ImageVIEw imageVIEw;      LoadPhotoTask(String url,ImageVIEw imageVIEw) {      this.url = url;      this.imageVIEw = imageVIEw;    }    @OverrIDe    public voID run() {      if (imageVIEwReused(imageVIEw,url)) {//判断ImageVIEw是否已经被复用        removeTask(this);//如果已经被复用则删除任务        return;      }      Bitmap bmp = getBitmapByUrl(url);//从缓存文件或者网络端获取图片      mMemoryCache.put(url,bmp);// 将图片放入到一级缓存中      if (!imageVIEwReused(imageVIEw,url)) {//若ImageVIEw未加图片则在ui线程中显示图片      Bitmapdisplayer bd = new Bitmapdisplayer(bmp,imageVIEw,url);            Activity a = (Activity) imageVIEw.getContext();      a.runOnUiThread(bd);//在UI线程调用bd组件的run方法,实现为ImageVIEw控件加载图片      }      removeTask(this);//从队列中移除任务    }    public String getUrl() {      return url;    }  }  /**   *    *由UI线程中执行该组件的run方法   */  class Bitmapdisplayer implements Runnable {    private Bitmap bitmap;    private ImageVIEw imageVIEw;    private String url;    public Bitmapdisplayer(Bitmap b,ImageVIEw imageVIEw,String url) {      bitmap = b;      this.imageVIEw = imageVIEw;      this.url = url;    }    public voID run() {      if (imageVIEwReused(imageVIEw,url))        return;      if (bitmap != null)        imageVIEw.setimageBitmap(bitmap);    }  }  /**   * 释放资源   */  public voID destroy() {    mMemoryCache.clear();    mMemoryCache = null;    mImageVIEws.clear();    mImageVIEws = null;    mTaskQueue.clear();    mTaskQueue = null;    mExecutorService.shutdown();    mExecutorService = null;  }}@H_403_31@

编写完成之后,对于异步任务的执行只需调用AsyncImageLoader中的loadBitmap()方法即可非常方便,对于AsyncImageLoader组件的代码最好结合注释好好理解一下,这样对于AndroID中线程之间的异步通信就会有深刻的认识。

4、工具类ImageUtil

public class ImageUtil {  /**   * 从网络获取图片,并缓存在指定的文件中   * @param url 图片url   * @param file 缓存文件   * @return   */  public static Bitmap loadBitmapFromWeb(String url,file file) {    httpURLConnection conn = null;    inputStream is = null;    OutputStream os = null;    try {      Bitmap bitmap = null;      URL imageUrl = new URL(url);      conn = (httpURLConnection) imageUrl.openConnection();      conn.setConnectTimeout(30000);      conn.setReadTimeout(30000);      conn.setInstanceFollowRedirects(true);      is = conn.getinputStream();      os = new fileOutputStream(file);      copyStream(is,os);//将图片缓存到磁盘中      bitmap = decodefile(file);      return bitmap;    } catch (Exception ex) {      ex.printstacktrace();      return null;    } finally {      try {        if(os != null) os.close();        if(is != null) is.close();        if(conn != null) conn.disconnect();      } catch (IOException e) {  }    }  }  public static Bitmap decodefile(file f) {    try {      return BitmapFactory.decodeStream(new fileinputStream(f),null,null);    } catch (Exception e) { }     return null;  }  private static voID copyStream(inputStream is,OutputStream os) {    final int buffer_size = 1024;    try {      byte[] bytes = new byte[buffer_size];      for (;;) {        int count = is.read(bytes,buffer_size);        if (count == -1)          break;        os.write(bytes,count);      }    } catch (Exception ex) {      ex.printstacktrace();    }  }}@H_403_31@

四、测试应用
 组件之间的时序图:

1、编写MainActivity

public class MainActivity extends Activity {   ListVIEw List;  ListVIEwAdapter adapter;  @OverrIDe  public voID onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentVIEw(R.layout.main);    List=(ListVIEw)findVIEwByID(R.ID.List);    adapter=new ListVIEwAdapter(this,mStrings);    List.setAdapter(adapter);  }  public voID onDestroy(){    List.setAdapter(null);    super.onDestroy();    adapter.destroy();  }  private String[] mStrings={ "http://news.jb51.net/Userfiles/x_Image/x_20150606083511_0.jpg","http://news.jb51.net/Userfiles/x_Image/x_20150606082847_0.jpg",…..};@H_403_31@

2、编写适配器

public class ListVIEwAdapter extends BaseAdapter {  private Activity mActivity;  private String[] data;  private static LayoutInflater inflater=null;  private AsyncImageLoader imageLoader;//异步组件  public ListVIEwAdapter(Activity mActivity,String[] d) {    this.mActivity=mActivity;    data=d;    inflater = (LayoutInflater)mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);    MemoryCache mcache=new MemoryCache();//内存缓存 file sdCard = androID.os.Environment.getExternalStorageDirectory();//获得SD卡    file cacheDir = new file(sdCard,"jereh_cache" );//缓存根目录  fileCache fcache=new fileCache(mActivity,cacheDir,"news_img");//文件缓存    imageLoader = new AsyncImageLoader(mActivity,mcache,fcache);  }  public int getCount() {    return data.length;  }  public Object getItem(int position) {    return position;  }  public long getItemID(int position) {    return position;  }  public VIEw getVIEw(int position,VIEw convertVIEw,VIEwGroup parent) {    VIEwHolder vh=null;    if(convertVIEw==null){      convertVIEw = inflater.inflate(R.layout.item,null);      vh=new VIEwHolder();      vh.tvTitle=(TextVIEw)convertVIEw.findVIEwByID(R.ID.text);      vh.ivimg=(ImageVIEw)convertVIEw.findVIEwByID(R.ID.image);      convertVIEw.setTag(vh);        }else{      vh=(VIEwHolder)convertVIEw.getTag();    }    vh.tvTitle.setText("标题信息测试―――― "+position);    vh.ivimg.setTag(data[position]);    //异步加载图片,先从一级缓存、再二级缓存、最后网络获取图片    Bitmap bmp = imageLoader.loadBitmap(vh.ivimg,data[position]);    if(bmp == null) {      vh.ivimg.setimageResource(R.drawable.default_big);    } else {      vh.ivimg.setimageBitmap(bmp);    }    return convertVIEw;  }  private class VIEwHolder{    TextVIEw tvTitle;    ImageVIEw ivimg;  }  public voID destroy() {    imageLoader.destroy();  }}@H_403_31@

想要了解更多内容的小伙伴,可以点击查看源码,亲自运行测试。

总结

以上是内存溢出为你收集整理的Android中使用二级缓存、异步加载批量加载图片完整案例全部内容,希望文章能够帮你解决Android中使用二级缓存、异步加载批量加载图片完整案例所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1142324.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存