安卓缓存-LruCache介绍
16lz
2021-01-26
在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; }}
更多相关文章
- Android(安卓)Material Design 组件集合Demo,附源码地址 :CimoGall
- thread in android ndk
- Android(安卓)为apk文件签名,增加修改系统时间等权限
- android背景图片更换――经典例子
- 百度地图开发 android App 数字签名(SHA1)获取办法
- 简单基站定位程序
- Android(安卓)thumbnail 缩略图的获取及与原始图片的映射关系
- TextView通过获得dimens.xml中定义的大小来动态设置字体大小
- Android开发本地及网络Mp3音乐播放器(五)实现专辑封面图片