美文网首页Android笔记
Android图片缓存(软引用,LRU)

Android图片缓存(软引用,LRU)

作者: Young_Allen | 来源:发表于2018-11-02 09:26 被阅读30次

    每天一篇系列:
    强化知识体系,查漏补缺。
    欢迎指正,共同学习!

    软引用比较常见的使用是在图片缓存:

    1.创建软引用HashMap作为缓存

    private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
    

    2.向缓存中添加新Bitmap

    public void addBitmapToCache(String path) {
            // 强引用的Bitmap对象,这里bitmap是局部变量,该方法执行完毕后bitmap就会释放
            Bitmap bitmap = BitmapFactory.decodeFile(path);
            // 软引用的Bitmap对象
            SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
            // 添加该对象到Map中使其缓存
            imageCache.put(path, softBitmap);
    }
    

    3.从缓存中读取Bitmap

    public Bitmap getBitmapByPath(String path) {
            // 从缓存中取软引用的Bitmap对象
            SoftReference<Bitmap> softBitmap = imageCache.get(path);
            // 判断是否存在软引用
            if (softBitmap == null) {
                return null;
            }
            // 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空
            Bitmap bitmap = softBitmap.get();
            if(bitmap==null){
                return null;
            }
           return bitmap;
    }
    

    在图片缓存的处理中除了软引用的优化外,主要涉及的是LRU算法策略,这里需要涉及到图片的三级缓存问题:

    三级缓存的设计:
    首先url作为缓存的关键字和bitmap对象关联起来,先从内存LruCache中查询是否存在缓存,如果没有再从DiskLruCache查询是否存在缓存,如果仍然没有缓存,则开启异步任务AsyncTask去网络下载。

    LRU(Least Recently Used)算法的核心是当缓存满了的时候,回收近期使用最少的缓存对象。采用LRU算法的缓存有两种:LruCache和DiskLruCache,分别用于实现内存缓存和硬盘缓存,其核心思想都是LRU缓存算法。

    1.LruCache的介绍

    public class LruCache<K, V> {
        private final LinkedHashMap<K, V> map;
    ...
        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是个泛型类。
    主要算法原理是把最近使用的对象用强引用存储在LinkedHashMap中。当缓存满时,把最近最少使用的对象从内存中移除,并提供了get和put方法来完成缓存的获取和添加操作。
    LruCache的核心思想很好理解,就是要维护一个缓存对象列表,其中对象列表的排列方式是按照访问顺序实现的,即一直没访问的对象,将放在队尾,即将被淘汰。而最近访问的对象将放在队头,最后被淘汰。这个缓存对象的列表就是LinkedHashMap,LinkedHashMap最近访问的最后输出的特性正好满足的LRU缓存算法的思想。
    https://www.jianshu.com/p/b49a111147ee


    可以看看LruCache的put()方法:
        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) {
                    size -= safeSizeOf(key, previous);
                }
            }
    
            if (previous != null) {
                entryRemoved(false, key, previous, value);
            }
    
            trimToSize(maxSize);
            return previous;
        }
    

    其中trimToSize会在内存满时回收近期使用较少的缓存对象。

        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) {
                        break;
                    }
                    //链表的头节点,即近期最少使用的对象
                    Map.Entry<K, V> toEvict = map.eldest();
                    if (toEvict == null) {
                        break;
                    }
    
                    key = toEvict.getKey();
                    value = toEvict.getValue();
                    map.remove(key);
                    size -= safeSizeOf(key, value);
                    evictionCount++;
                }
    
                entryRemoved(true, key, value, null);
            }
        }
    

    剩下的就是如何get缓存了:

        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);
            if (createdValue == null) {
                return null;
            }
    
            synchronized (this) {
                createCount++;
                mapValue = map.put(key, createdValue);
    
                if (mapValue != null) {
                    // There was a conflict so undo that last 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;
            }
        }
    

    get函数里面给开发者预留了一个可以继承的creat实现,当缓存不存在时,可以根据开发者自实现的缓存补充到LinkedHashMap中,并且对key值冲突的情况作了安全处理。
    从LruCache的源码实现中希望可以深入到LinkedHashMap等集合的使用场合,synchronized同步的意义、泛型类的使用,父类调用sizeof是否使用了某种设计模式等更广层面的思考。

    2.DiskLruCache介绍

    public final class DiskLruCache implements Closeable {
    ...
        private final LinkedHashMap<String, Entry> lruEntries
                = new LinkedHashMap<String, Entry>(0, 0.75f, true);
    ...
        private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
                60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
    ...
    }
    

    不同于LruCache,LruCache是将数据缓存到内存中去,而DiskLruCache是外部缓存,例如可以将网络下载的图片永久的缓存到手机外部存储中去。同样DiskLruCache内部也利用了LinkedHashMap特性,还利用了ExecutorService来优化线程池。
    DiskLruCache被声明未final,表明这个类不能被继承。
    (当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法)
    (使用final修饰方法的原因是把方法锁定,以防任何继承类修改它的含义)
    (对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象)。
    其中LinkedHashMap的使用与LRU方式没有差别。通过一个文件记录缓存读取记录,从而记录缓存事件。

        public synchronized Snapshot get(String key) throws IOException {
            checkNotClosed();
            validateKey(key);
            Entry entry = lruEntries.get(key);
            if (entry == null) {
                return null;
            }
    
            if (!entry.readable) {
                return null;
            }
    
            /*
             * Open all streams eagerly to guarantee that we see a single published
             * snapshot. If we opened streams lazily then the streams could come
             * from different edits.
             */
            InputStream[] ins = new InputStream[valueCount];
            try {
                for (int i = 0; i < valueCount; i++) {
                    ins[i] = new FileInputStream(entry.getCleanFile(i));
                }
            } catch (FileNotFoundException e) {
                // a file must have been deleted manually!
                return null;
            }
    
            redundantOpCount++;
            journalWriter.append(READ + ' ' + key + '\n');
            if (journalRebuildRequired()) {
                executorService.submit(cleanupCallable);
            }
    
            return new Snapshot(key, entry.sequenceNumber, ins);
        }
    

    相关文章

      网友评论

        本文标题:Android图片缓存(软引用,LRU)

        本文链接:https://www.haomeiwen.com/subject/ogwwaftx.html