安卓缓存:LruCache 及自己设计异步 + 缓存的图片加载器

阅读 973
收藏 80
2016-10-05
原文链接:blog.csdn.net

LruCache是一套内存缓存的解决方案,算法基于LRU。
LRU:Least Recently Used(近期最少使用)。LruCache基于LRU算法的缓存策略。

  1. LruCache是一个泛型类,其以强引用的方式存储外界的缓存对象。当内存缓存达到设定的最大值时,则将内存缓存中近期最少使用的对象移除,有效的避免了OOM的出现。

    Java的四种引用

一、LruCache的基本使用

LruCache一般使用来缓存图片,下面以缓存Bitmap为例

  1. 建立一个key为String类型,value为Bitmap类型的LruCache

    private LruCache mCache;
    
  2. 初始化LruCache,并指定LruCache的缓存大小

    //获取应用程序最大可用内存
    int maxMemory = (int) Runtime.getRuntime().maxMemory();
    
    //指定缓存大小
    int cacheSize = maxMemory / 8;
    
    mCache = new LruCache(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap value) {
    
            //返回Bitmap的实际大小 (单位应与maxMemory一致)
            return value.getByteCount();
    
        }
    };
    
  3. 添加缓存

    mCache.get(key);
    
  4. 获取缓存

    mCache.put(key,value);
    
  5. 移除某个键的缓存

    mCache.remove(key);
    
  6. 清空缓存

    mCache.evictAll()
    

二、使用LruCache封装图片加载缓存器

根据LruCache,我们可以将它封装在LruCacheImageLoader来对图片进行缓存。设计模式为使用单例模式来设计:

在Logcat中打印异步任务个数:

package com.cxmscb.cxm.cacheproject;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.support.v4.util.LruCache;
import android.util.Log;
import android.widget.ImageView;
import android.widget.ListView;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashSet;
import java.util.Set;

/**
 * Created by cxm on 2016/8/25.
 */
public class LruCacheImageLoader {

    private LruCache mCache;

    // 存储异步任务的集合
    private Set mTaskSet;

    /*ImageLoader的单例*/
    private static LruCacheImageLoader mImageLoader;

    private Context mContext;

    private LruCacheImageLoader(Context context){

        this.mContext = context;
        mTaskSet = new HashSet<>();

        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 8;
        // 初始化LruCache
        mCache = new LruCache(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };
    }


    // 获取LruCacheImageLoader的实例(带同步锁)
    public static LruCacheImageLoader getInstance(Context context){


        if(mImageLoader==null){
            synchronized (LruCacheImageLoader.class){
                if(mImageLoader==null)
                    mImageLoader = new LruCacheImageLoader(context);
            }
        }
        return  mImageLoader;
    }


    // 根据key值获取缓存中的图片
    private Bitmap getBitmapFromMemory(String url){
        return mCache.get(url);
    }




    //将一张图片存储到LruCache中。
    private void putBitmapToMemory(String url, Bitmap bitmap) {
        if (getBitmapFromMemory(url) == null) {
            mCache.put(url, bitmap);
        }
    }


    /*------------以上的LruCache的使用-------------*/

    /*
     * 对外方法:普通地加载图片到imageView中
     */
    public void displayImage(ImageView iv, final String url) {

            //从缓存中取出图片
            Bitmap bitmap = getBitmapFromMemory(url);
            //如果缓存中没有,先设为默认图片
            if (bitmap == null) {
                LruCacheAsyncTask task = new LruCacheAsyncTask(iv);
                task.execute(url);
                mTaskSet.add(task);
            } else {
                //如果缓存中有 直接设置
                iv.setImageBitmap(bitmap);
            }

    }


    /**
     * 对外方法:为listview加载从start到end的所有的Image
     *
     */
    public void loadTagImageViewInListView(int start, int end, String[] tagUrls, ListView mListView) {

        Drawable.ConstantState aConstantState = mContext.getResources().getDrawable(R.drawable.loading).getConstantState();

        for (int i = start; i < end; i++) {
            String url = tagUrls[i];
            ImageView imageView = (ImageView) mListView.findViewWithTag(url);
            // 判断图片是否加载过,以免新建多个asynctask
            if (imageView.getDrawable().getConstantState().equals(aConstantState)) {
                displayImage(imageView, url);
            }
        }
        Log.i("num of asynctask","  "+mTaskSet.size());
    }


    private class LruCacheAsyncTask extends AsyncTask{


        private ImageView imageView;



        public LruCacheAsyncTask(ImageView imageView){
            this.imageView = imageView;
        }

        @Override
        protected Bitmap doInBackground(String... strings) {
            Bitmap bitmap = getBitmapFromUrl(strings[0]);

            // 将bitmap缓存到LruCache中
            if(bitmap!=null){
                putBitmapToMemory(strings[0],bitmap);
            }

            return bitmap;
        }


        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            if (imageView != null && bitmap != null) {
                imageView.setImageBitmap(bitmap);
            }
            mTaskSet.remove(this);
            Log.i("num of asynctask","  "+mTaskSet.size());
        }

        private Bitmap getBitmapFromUrl(String urlPath) {
            Bitmap bitmap = null;
            try {
                URL url = new URL(urlPath);
                URLConnection conn = url.openConnection();
                conn.connect();
                InputStream in;
                in = conn.getInputStream();
                bitmap = BitmapFactory.decodeStream(in);
                // TODO Auto-generated catch block
            } catch (IOException e) {
                e.printStackTrace();
            }
            return bitmap;
        }

    }


    /**
     * 停止所有当前正在运行的任务
     */
    public void cancelAllTask() {

        if (mTaskSet != null) {
            for (LruCacheAsyncTask task : mTaskSet) {
                task.cancel(false);
            }
            mTaskSet.clear();
            Log.i("num of asynctask","  "+mTaskSet.size());
        }
    }


}

一、项目效果图:

首先是ListView上下滑动时不加载图片,停止滑动才开始加载图片

这里写图片描述

在Logcat中查看加载图片的异步任务个数:

滑动时异步任务个数为0,滑动停止时任务数为ListView中的item可见数,然后递减,最后为0,再次滑动/停止时一直为0。主要是利用了下面的优化。

二、对ListView加载图片的优化:

监听ListView滚动状态,只有当其静止的时候才异步加载网络图片数据。(第一次进入也加载图片)

listView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView absListView, int scrollState) {
                if(scrollState==SCROLL_STATE_IDLE){
                   lruCacheImageLoader.loadTagImageViewInListView(mStart,mEnd,urls,listView);
                }else {
                    // 以免产生太多无用的asynctask
                    lruCacheImageLoader.cancelAllTask();
                }
            }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            mStart = firstVisibleItem;
            mEnd = firstVisbleItem + visibleItemCount;
            if(mFirstIn && visibleItemCount > 0){
                lruCacheImageLoader.loadTagImageViewInListView(mStart,mEnd,urls,listView);
                mFirstIn = false;
            }
        }
    });

完整项目地址:Github地址

评论