软件世界网 购物 网址 三丰软件 | 小说 美女秀 图库大全 游戏 笑话 | 下载 开发知识库 新闻 开发 图片素材
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
移动开发 架构设计 编程语言 Web前端 互联网
开发杂谈 系统运维 研发管理 数据库 云计算 Android开发资料
  软件世界网 -> 移动开发 -> Android缓存类LruCache源码分析 -> 正文阅读

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


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  
移动开发 最新文章
深入了解android中的消息机制Handler
Android
Libgdx之BitmapFont字体
AndroidApp发布到应用市场的流程
Android开发找工作之前先看看这些知识点吧
View的事件分发机制解析
简单介绍了解白鹭引擎Egret
Cocos2d
android获取本地图片(二)
动画特效七:碰撞动画
360图书馆 软件开发资料 文字转语音 购物精选 软件下载 美食菜谱 新闻资讯 电影视频 小游戏 Chinese Culture 股票 租车
生肖星座 三丰软件 视频 开发 短信 中国文化 网文精选 搜图网 美图 阅读网 多播 租车 短信 看图 日历 万年历 2018年1日历
2018-1-16 21:18:53
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  软件世界网 --