安卓缓存-LruCache介绍

在Android中,有一个叫做LruCache类专门用来做缓存处理的,尤其适合图片缓存。
它有一个特点,当缓存的内容达到了预先设定的值的时候,那么近期使用次数最少的内容就会被回收掉。
我们来看看它的源码(注意是在android.support.v4.util下):

package android.support.v4.util;

import java.util.LinkedHashMap;
import java.util.Map;

public class LruCache<K, V> {
    //内部维护了一个LinkedHashMap,为什么要用这个,后面介绍
    private final LinkedHashMap<K, V> map;

    private int size; //已缓存的大小
    private int maxSize; //缓存最大值

    private int putCount; //往集合里put的次数
    private int createCount; //创建的个数
    private int evictionCount;//移除的个数
    private int hitCount;//命中次数
    private int missCount;//丢失的次数

    /**
     * 构造方法
     * @param maxSize 缓存的最大值
     */
    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

    /**
     * 重新设置最大缓存大小
     *
     * @param maxSize
     */
    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }

        synchronized (this) {
            this.maxSize = maxSize;
        }
        trimToSize(maxSize);
    }

    /**
     *  通过key获取相应的value,或者创建返回相应的value。相应的value会移动到队列的头部,
     *  如果item的value没有被cache或者不能被创建,则返回null。
     */
    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            missCount++;
        }

        //如果丢失了就试图创建一个value
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }
        //同步,保证多线程下访问安全
        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // 如果前面存在一个旧的value,那么撤销put() 
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }

    /**
     * 在缓存集合(LinkedHashMap)队首添加value
     *
     * @return the previous value mapped by {@code key}.
     */
    public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            putCount++;
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) { 
                //返回的先前的value
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }

        trimToSize(maxSize);
        return previous;
    }

    /**
     * 调整cache空间
     * 当缓存的大小超过原来设定的maxSize时,遍历map,将多余的项(代码中对应toEvict)剔除掉,直到当
     * 前cache的大小等于或小于限定的大小
     * @param maxSize the maximum size of the cache before returning. May be -1
     *            to evict even 0-sized elements.
     */
    public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize || map.isEmpty()) {
                    break;
                }

                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }

    /**
     * 从缓存中移除掉key对应的value
     *
     * @return the previous value mapped by {@code key}.
     */
    public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V previous;
        synchronized (this) {
            previous = map.remove(key);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, null);
        }

        return previous;
    }

    /**
     * 当item被回收或者删掉时调用。该方法当value被回收释放存储空间时被remove调用,
     * 或者替换item值时put调用,默认实现什么都没做。
     *
     * @param evicted true if the entry is being removed to make space, false
     *     if the removal was caused by a {@link #put} or {@link #remove}.
     * @param newValue the new value for {@code key}, if it exists. If non-null,
     *     this removal was caused by a {@link #put}. Otherwise it was caused by
     *     an eviction or a {@link #remove}.
     */
    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

    /**
     * 当某Item丢失时会调用到,返回计算的相应的value或者null
     * <p>If a value for {@code key} exists in the cache when this method
     * returns, the created value will be released with {@link #entryRemoved}
     * and discarded. This can occur when multiple threads request the same key
     * at the same time (causing multiple values to be created), or when one
     * thread calls {@link #put} while another is creating a value for the same
     * key.
     */
    protected V create(K key) {
        return null;
    }
    /**
     * 计算某个item的大小,如果是Bitmap,这应该是bitmap.getByteCount();
     */
    private int safeSizeOf(K key, V value) {
        int result = sizeOf(key, value);
        if (result < 0) {
            throw new IllegalStateException("Negative size: " + key + "=" + value);
        }
        return result;
    }


    protected int sizeOf(K key, V value) {
        return 1;
    }

    /**
     * 清空缓存
     */
    public final void evictAll() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }

    /**
     * For caches that do not override {@link #sizeOf}, this returns the number
     * of entries in the cache. For all other caches, this returns the sum of
     * the sizes of the entries in this cache.
     */
    public synchronized final int size() {
        return size;
    }

    /**
     * For caches that do not override {@link #sizeOf}, this returns the maximum
     * number of entries in the cache. For all other caches, this returns the
     * maximum sum of the sizes of the entries in this cache.
     */
    public synchronized final int maxSize() {
        return maxSize;
    }

    /**
     * Returns the number of times {@link #get} returned a value that was
     * already present in the cache.
     */
    public synchronized final int hitCount() {
        return hitCount;
    }

    /**
     * 返回get()为null的次数或者创建的次数
     */
    public synchronized final int missCount() {
        return missCount;
    }

    /**
     * 返回创建create()的次数 
     */
    public synchronized final int createCount() {
        return createCount;
    }

    /**
     * 返回put的次数
     */
    public synchronized final int putCount() {
        return putCount;
    }

    /**
     * 返回被回收的数量
     */
    public synchronized final int evictionCount() {
        return evictionCount;
    }

    /**
     * 返回当前cache的副本,从最近最少访问到最多访问
     */
    public synchronized final Map<K, V> snapshot() {
        return new LinkedHashMap<K, V>(map);
    }

    @Override public synchronized final String toString() {
        int accesses = hitCount + missCount;
        int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
        return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
                maxSize, hitCount, missCount, hitPercent);
    }
}

我们来分析下LinkedHashMap:
LinkedHashMap中的get()方法不仅返回所匹配的值,并且在返回前还会将所匹配的key对应的entry调整在列表中的顺序(LinkedHashMap使用双链表来保存数据),让它处于链表的最后。当然,这种情况必须是在LinkedHashMap中accessOrder==true的情况下才生效的,反之就是get()方法不会改变被匹配的key对应的entry在列表中的位置,这样将大大方便了缓存顺序的调整和缓存的移除。这就是为什么要使用LinkedHashMap的原因。

        @Override 
        public V get(Object key) {
        /*
         * This method is overridden to eliminate the need for a polymorphic
         * invocation in superclass at the expense of code duplication.
         */
        if (key == null) {
            HashMapEntry<K, V> e = entryForNullKey;
            if (e == null)
                return null;
            if (accessOrder)
                makeTail((LinkedEntry<K, V>) e);
            return e.value;
        }

        int hash = Collections.secondaryHash(key);
        HashMapEntry<K, V>[] tab = table;
        for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
                e != null; e = e.next) {
            K eKey = e.key;
            if (eKey == key || (e.hash == hash && key.equals(eKey))) {
                if (accessOrder) 
                    makeTail((LinkedEntry<K, V>) e);//调整顺序
                return e.value;
            }
        }
        return null;
    }

好了,LruCache就介绍到这里,下面有个使用例子,大家可以看看:

package com.king.imagechooser.util;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.LruCache;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;

import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * 本地图片加载框架(读取本地图片)
 * Created by King on 2016/3/21.
 */
public class ImageLoader {

    /**
     * 图片缓存
     */
    private LruCache<String, Bitmap> mCaches;

    /**
     * 线程池
     */
    private ExecutorService mThreadPool;
    /**
     * 默认线程数量
     */
    private final static int DEFAULT_THREAD_COUNTS = 1;

    /**
     * 信号量
     */
    private Semaphore mSemaphore = new Semaphore(0);
    private Semaphore mSemaphoreThreadPool;

    private Type mType = Type.LIFO;

    public enum Type {
        FIFO, LIFO
    }

    private LinkedList<Runnable> mTaskQueue;

    private Thread mPoolThread;
    private Handler mPoolThreadHandler;

    private Handler mUIHandler;

    /**
     * 单例
     */
    private static ImageLoader mInstance;

    private ImageLoader(int treadCount, Type type) {
        init(treadCount, type);
    }

    public static ImageLoader getInstance(int treadCount, Type type) {
        if (mInstance == null) {
            synchronized (ImageLoader.class) {
                if (mInstance == null) {
                    mInstance = new ImageLoader(treadCount, type);
                }
            }
        }
        return mInstance;
    }


    /**
     * 初始化
     *
     * @param threadCount
     * @param type
     */
    public void init(int threadCount, Type type) {

        mPoolThread = new Thread() {
            @Override
            public void run() {

                Looper.prepare();
                mPoolThreadHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        //从线程池取一个任务去执行
                        mThreadPool.execute(getTaskThread());
                        try {
                            //最多执行threadCount个线程,利用信号量来控制
                            mSemaphoreThreadPool.acquire();
                        } catch (Exception e) {
                            Log.e("King", e.getMessage());
                        }
                    }
                };
                //释放信号量
                mSemaphore.release();
                Looper.loop();
            }
        };
        mPoolThread.start();
        //获取最大可用内存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheMemory = maxMemory / 4;
        //初始化LruCache
        mCaches = new LruCache<String, Bitmap>(cacheMemory) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight();
            }
        };
        //创建线程池
        mThreadPool = Executors.newFixedThreadPool(threadCount);
        mTaskQueue = new LinkedList<Runnable>();
        mType = type;

        mSemaphoreThreadPool = new Semaphore(threadCount);

    }

    /**
     * 显示图片到ImageView
     */
    public void loadImage(final String path, final ImageView imageView) {
        imageView.setTag(path);
        if (mUIHandler == null) {
            mUIHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    //获取图片,为ImageView回调设置图片
                    ImageHolder holder = (ImageHolder) msg.obj;
                    Bitmap bm = holder.bitmap;
                    ImageView iv = holder.imageView;
                    String path = holder.path;
                    if (iv.getTag().toString().equals(path)) {
                        iv.setImageBitmap(bm);
                    }
                }
            };
        }

        Bitmap bitmap = getBitmapFromCache(path);

        if (bitmap != null) {
            refreshBitmap(path, imageView, bitmap);
        } else {
            addTaskThread(new Runnable() {
                @Override
                public void run() {
                    //获取imageView大小
                    ImageSize imageSize = getImageViewSize(imageView);
                    //压缩图片
                    Bitmap bitmap = decodeBitmapFromPath(path, imageSize.width, imageSize.height);
                    //将图片加入缓存
                    addBitmapToCache(path, bitmap);
                    //发送消息,更新视图
                    refreshBitmap(path, imageView, bitmap);
                    //释放信号量,保证剩余线程继续执行
                    mSemaphoreThreadPool.release();
                }
            });
        }
    }

    private void refreshBitmap(String path, ImageView imageView, Bitmap bitmap) {
        ImageHolder holder = new ImageHolder();
        holder.bitmap = bitmap;
        holder.imageView = imageView;
        holder.path = path;
        Message msg = Message.obtain();
        msg.obj = holder;
        mUIHandler.sendMessage(msg);
    }
    /**
     * 获取ImageView的实际大小
     */
    protected ImageSize getImageViewSize(ImageView imageView) {
        ImageSize size = new ImageSize();

        DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();

        LayoutParams lp = imageView.getLayoutParams();
        int width = imageView.getWidth();

        if (width <= 0) {
            width = lp.width; //获取ImageView在layout中声明的宽度
        }
        if (width <= 0) {
            width = getImageViewFieldValue(imageView, "mMaxWidth");//获取ImageView在layout中的最大宽度
        }
        if (width <= 0) {
            width = displayMetrics.widthPixels;
        }
        int height = imageView.getHeight();
        if (height <= 0) {
            height = lp.height; //获取ImageView在layout中声明的高度
        }
        if (height <= 0) {
            height = getImageViewFieldValue(imageView, "mMaxHeight"); //获取ImageView在layout中的最大高度
        }
        if (height <= 0) {
            height = displayMetrics.heightPixels;
        }

        size.width = width;
        size.height = height;
        return size;
    }

    //通过反射获取ImageView的属性(为了兼容低版本Api)
    private static int getImageViewFieldValue(Object object, String fieldName) {
        int value = 0;
        try {
            Field field = ImageView.class.getDeclaredField(fieldName);
            field.setAccessible(true);

            int fieldValue = field.getInt(object);
            if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
                value = fieldValue;
            }
        } catch (NoSuchFieldException e) {
            Log.e("King", e.getMessage());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return value;
    }

    /**
     * 压缩图片
     *
     * @param path
     * @param width
     * @param height
     * @return
     */
    protected Bitmap decodeBitmapFromPath(String path, int width, int height) {
        try {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(path, options);
            options.inSampleSize = calculateInSampleSize(options, width, height);
            options.inJustDecodeBounds = false;
            return BitmapFactory.decodeFile(path, options);
        } catch (Throwable tr) {
            Log.e("King", tr.getMessage());
        }
        return null;
    }

    // 计算图片的缩放值
    public static int calculateInSampleSize(BitmapFactory.Options options,
                                            int reqWidth, int reqHeight) {
        try {
            final int height = options.outHeight;
            final int width = options.outWidth;
            int inSampleSize = 1;

            if (height > reqHeight || width > reqWidth) {
                final int heightRatio = Math.round((float) height
                        / (float) reqHeight);
                final int widthRatio = Math.round((float) width
                        / (float) reqWidth);
                inSampleSize = Math.max(heightRatio, widthRatio);
            }
            return inSampleSize;
        } catch (Throwable e) {
            Log.e("King", e.getMessage());
        }
        return 1;
    }
    /**
     * 将图片添加到缓存
     *
     * @param path
     * @param bm
     */
    private void addBitmapToCache(String path, Bitmap bm) {
        if (getBitmapFromCache(path) == null) {
            if (bm != null) {
                mCaches.put(path, bm);
            }
        }

    }
    //添加线程到集合中,方便管理
    private synchronized void addTaskThread(Runnable runnable) {
        try {
            mTaskQueue.add(runnable);
            if (mPoolThreadHandler == null) {
                //请求信号量,保证Handler为空时再请求,从而避免重复请求
                mSemaphore.acquire();
            }
            mPoolThreadHandler.sendEmptyMessage(0x110);
        } catch (Throwable tr) {
            Log.e("King", tr.getMessage(), tr);
        }
    }
    /**
     * 获取线程
     *
     * @return
     */
    private Runnable getTaskThread() {
        if (mType == Type.FIFO) {

            return mTaskQueue.removeFirst();
        } else if (mType == Type.LIFO) {
            return mTaskQueue.removeLast();
        }
        return null;

    }
    /**
     * 从缓存中获取图片
     */
    public Bitmap getBitmapFromCache(String key) {
        return mCaches.get(key);
    }

    private class ImageHolder {
        Bitmap bitmap;
        ImageView imageView;
        String path;
    }
    private class ImageSize {
        int width;
        int height;
    }

}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猫King

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值