软件世界网 购物 网址 三丰软件 | 小说 美女秀 图库大全 游戏 笑话 | 下载 开发知识库 新闻 开发 图片素材
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
移动开发 架构设计 编程语言 Web前端 互联网
开发杂谈 系统运维 研发管理 数据库 云计算 Android开发资料
  软件世界网 -> 移动开发 -> Android缓存类LruCache源码分析 -> 正文阅读
移动开发 最新文章
深入了解android中的消息机制Handler
Android
Libgdx之BitmapFont字体
AndroidApp发布到应用市场的流程
Android开发找工作之前先看看这些知识点吧
View的事件分发机制解析
简单介绍了解白鹭引擎Egret
Cocos2d
android获取本地图片(二)
动画特效七:碰撞动画

[移动开发]Android缓存类LruCache源码分析

  2016-04-03 20:42:07

1.引言


Android开发难免会遇到加载网络图片问题,而加载网络图片难免会遇到多次重复请求加载同一张图片问题,这么一来就导致多次加载网络,不仅浪费资源而且给用户体验感觉加载图片慢。从Android3.1开始,API中多了一个LruCache类,该类是一个使用最近最少使用算法的缓存类。也就是可以把上次下载的图片以键值对的方式保存在缓存中,以便下载加载同一张图片时无须再次从网络上下载,而且加载速度快。

2.LruCache源码详解


这里我把LruCache源码贴出来,几乎每行代码的注释都有,很详细,有意者可以仔细跟着注释阅读源码,以便理解LruCache类的原理。
/**
1.从类名LruCache就知道,该类的作用是一个最近最少使用算法来维护的一个缓存类。
2.该类是一个用于缓存一定数量的值,并且该缓存对象是持有强引用。
3.每当成功get一次值时,该值都会移动到链表的头部,以便标记该值为最近最新的值。
4.当缓存满了时,链表尾部的值会被认为是最近最少使用的值,会被从链表中移除,以便缓存有空间保存新的值。
5.缓存中链表的值发生改变时会调用空方法entryRemoved,开发者可以重写该方法,以便做相应的操作。
6.开发者应该去重写该类中sizeOf方法,该方法返回每个key对应value值得大小,以便该类去维护缓存的大小。
7.该类是一个安全类。同时该类不允许key和value为空。该类在Android3.1之后添加到源码中。
**/
package android.util;

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


public class LruCache<K, V> {
    private final LinkedHashMap<K, V> map;

    /** Size of this cache in units. Not necessarily the number of elements. */
    private int size;//已使用缓存大小
    private int maxSize;//总的缓存大小

    private int putCount;//添加记录次数
    private int createCount;//创建次数
    private int evictionCount;//移除次数
    private int hitCount;//命中次数
    private int missCount;//未命中次数

    /**
     * @param maxSize for caches that do not override {@link #sizeOf}, this is
     *     the maximum number of entries in the cache. For all other caches,
     *     this is the maximum sum of the sizes of the entries in this cache.
     */
    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);//创建链表缓存,该链表缓存是整个LruCache类的重点。
    }

    /**
     * Sets the size of the cache.
     * @param maxSize The new maximum size.
     *重新设置缓存大小
     * @hide
     */
    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }

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

    /**
     * Returns the value for {@code key} if it exists in the cache or can be
     * created by {@code #create}. If a value was returned, it is moved to the
     * head of the queue. This returns null if a value is not cached and cannot
     * be created.
     * 根据key值获得缓存中对应的数据,该方法是线程安全的。
     */
    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++;//标记未命中的次数
        }

        /*
         * Attempt to create a value. This may take a long time, and the map
         * may be different when create() returns. If a conflicting value was
         * added to the map while create() was working, we leave that value in
         * the map and release the created value.
         */
        //未命中走以下流程
        V createdValue = create(key);//创建新的数据,默认返回null,创建的时候可以重写该方法,以便未命中的时候创建一个新的数据添加到缓存链表中。
        if (createdValue == null) {
            return null;//未命中时默认到这里结束。
        }

        //以下代码是在未命中时创建新数据添加到链表缓存中。
        synchronized (this) {
            createCount++;//标记创建新数据的次数
            mapValue = map.put(key, createdValue);
            //如果链表中已有该key对应的值,则最后取消添加新创建的值。很多人可能感到奇怪,此时链表中应该不存在key对应的值啊?其实这里是为了防止多线程操作导致数据不同步而添加的安全代码。不懂得自己体会去吧!
            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                //标记已经使用了的内存大小。
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            //链表中发生数据变化时(调用了put,get方法)调用该方法。该方法默认是个空,开发者可以重写该方法在链表中数据变化时做相应的操作。
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            //重新整理当前链表维护的内存。
            trimToSize(maxSize);
            return createdValue;
        }
    }

    /**
     * Caches {@code value} for {@code key}. The value is moved to the head of
     * the queue.
     *
     * @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) {//链表中已存在key对应的值则替换原来的值
                size -= safeSizeOf(key, previous);//标记已使用的内存大小
            }
        }

        if (previous != null) {
            //链表中数据发生交换时调用该方法。
            entryRemoved(false, key, previous, value);
        }
        //整理链表维护的内存大小
        trimToSize(maxSize);
        return previous;
    }

    /**
     * @param maxSize the maximum size of the cache before returning. May be -1
     *     to evict even 0-sized elements.
     * 管理链表中内存的大小
     */
    private 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) {
                    break;
                }

                // BEGIN LAYOUTLIB CHANGE
                // get the last item in the linked list.
                // This is not efficient, the goal here is to minimize the changes
                // compared to the platform version.
                Map.Entry<K, V> toEvict = null;
                //for循环得到链表末尾的数据,然后移除它。
                for (Map.Entry<K, V> entry : map.entrySet()) {
                    toEvict = entry;
                }
                // END LAYOUTLIB CHANGE

                if (toEvict == null) {
                    break;
                }
                //移除链表末尾的一个数据,该数据就是最近最少使用到的数据。
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);//移除数据
                size -= safeSizeOf(key, value);//重写计算已使用的内存大小
                evictionCount++;//标记移除数据的次数
            }
            //开发者可以重新改方法,当移除数据时做相应的操作。
            entryRemoved(true, key, value, null);
        }
    }

    /**
     * Removes the entry for {@code key} if it exists.
     *
     * @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;
    }

    /**
     * Called for entries that have been evicted or removed. This method is
     * invoked when a value is evicted to make space, removed by a call to
     * {@link #remove}, or replaced by a call to {@link #put}. The default
     * implementation does nothing.
     *
     * <p>The method is called without synchronization: other threads may
     * access the cache while this method is executing.
     *
     * @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) {}

    /**
     * Called after a cache miss to compute a value for the corresponding key.
     * Returns the computed value or null if no value can be computed. The
     * default implementation returns null.
     *
     * <p>The method is called without synchronization: other threads may
     * access the cache while this method is executing.
     *
     * <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.
     */
    //该方法默认返回空,开发者可以重新该方法以便在get链表中的数据失败时创建新的数据添加到链表中。
    protected V create(K key) {
        return null;
    }

    //该方法是重写计算链表中已经使用了的内存大小
    private int safeSizeOf(K key, V value) {
        int result = sizeOf(key, value);
        if (result < 0) {
            throw new IllegalStateException("Negative size: " + key + "=" + value);
        }
        return result;
    }

    /**
     * Returns the size of the entry for {@code key} and {@code value} in
     * user-defined units.  The default implementation returns 1 so that size
     * is the number of entries and max size is the maximum number of entries.
     *
     * <p>An entry's size must not change while it is in the cache.
     */
    //该方法默认返回1,开发者必须重写该方法,用来计算每个key对应value值得大小,以便维护链表中缓存的大小。
    protected int sizeOf(K key, V value) {
        return 1;
    }

    /**
     * Clear the cache, calling {@link #entryRemoved} on each removed entry.
     */
    //移除链表中所有的数据
    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;
    }

    /**
     * Returns the number of times {@link #get} returned null or required a new
     * value to be created.
     * 得到未命中的次数
     */
    public synchronized final int missCount() {
        return missCount;
    }

    /**
     * Returns the number of times {@link #create(Object)} returned a value.
     * 得到创建新数据的次数
     */
    public synchronized final int createCount() {
        return createCount;
    }

    /**
     * Returns the number of times {@link #put} was called.
     * 得到添加新数据次数
     */
    public synchronized final int putCount() {
        return putCount;
    }

    /**
     * Returns the number of values that have been evicted.
     * 得到移除数据的次数
     */
    public synchronized final int evictionCount() {
        return evictionCount;
    }

    /**
     * Returns a copy of the current contents of the cache, ordered from least
     * recently accessed to most recently accessed.
     * 得到链表对象
     */
    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);
    }
}

LruCache图片使用示例


一下代码是图片缓存类ImageCache示例代码:
import android.graphics.Bitmap;
import android.util.LruCache;

/**
 * Created by xjp on 2016/4/2.
 */
public class ImageCache {
    private LruCache mLruCache;
    private static ImageCache instance = new ImageCache();

    public ImageCache() {
        //得到当前应用总的内存大小
        int appTotalCache = (int) Runtime.getRuntime().totalMemory();
        //取当前应用内存的1/8作为缓存大小
        int maxSize = appTotalCache / 8;
        mLruCache = new LruCache<String, Bitmap>(maxSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //此处返回每个Bitmap对象的大小。值得注意:此处返回值并不是图片的张数,
                // 且返回值的单位应该和maxSize的单位一样。也就是maxSize单位是B,那么此处返回值单位也是B
                return value.getByteCount();
            }

            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                //如果缓存中的值被移除,则去回收Bitmap内存。
                if (evicted) {
                    if (oldValue != null && !oldValue.isRecycled()) {
                        oldValue.recycle();
                    }
                }
            }
        };
    }

    public static ImageCache getInstance() {
        return instance;
    }

    //从缓存中读取数据
    public Bitmap getBitmapFromCache(String key) {
        if (mLruCache != null) {
            mLruCache.get(key);
        }
        return null;
    }

    //保存数据到缓存中
    public void putBitmapToCache(String key, Bitmap value) {
        if (mLruCache != null) {
            if (mLruCache.get(key) == null) {
                mLruCache.put(key, value);
            }
        }
    }

    //清除缓存中所有数据
    public void clearCache() {
        if (mLruCache != null) {
            mLruCache.evictAll();
        }
    }
}

总结:以后就可以直接用ImageCache类来作为图片缓存了。
上一篇文章      下一篇文章      查看所有文章
2016-04-03 20:41:06  
360图书馆 论文大全 母婴/育儿 软件开发资料 网页快照 文字转语音 购物精选 软件 美食菜谱 新闻中心 电影下载 小游戏 Chinese Culture
生肖星座解梦 人民的名义 人民的名义在线看 三沣玩客 拍拍 视频 开发 Android开发 站长 古典小说 网文精选 搜图网 天下美图
中国文化英文 多播视频 装修知识库
2017-4-29 9:42:43
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  软件世界网 --