Android技术知识Android开发经验谈Android Tips

LruCache缓存使用

2018-09-03  本文已影响19人  金馆长说

1.为什么要缓存?

在android项目中经常需要请求网络图片,这个时候如果每张网络图片显示都要去获取一次的话,是非常耗费性能的。从网络中成功获取一张图片需要发起请求、等待响应、接收数据、解析数据、在做显示。这一系列操作我们都需要放到子线程中去执行,确认程序不会出现ANR问题。

对于缓存来说,无法就是添加缓存、删除缓存、获取缓存这三种操作。先判断是否有缓存,没有就进行缓存操作,当缓存满了之后我们就可以把最久没使用的缓存给清除掉,已达到不超出缓存边界的问题。

2.二种缓存方式

为了应对这种性能问题,我们有可以选择对图片进行缓存,在android中主要有二种方式来缓存,一种是内存缓存、磁盘缓存。内存缓存是最快的因为它是直接从内存中读取数据,磁盘缓存稍慢因为它要做IO流操作,但是它可以持久化数据,各有各的好处,所以我们可以结合情况二种方式都用上。

3.LruCache

结合以上出现的问题android在3.1的时候推出了一个LRU(Least Recently Used)最近期最少使用算法,它的核心思想就是,优先清除那些最近最少使用的对象。LRU体现有二种一个是内存中的LruCache和一个针对磁盘的DisLruCache。它们的核心都是LRU思想。

4.使用

这里初始化了一个LruCache类,构造函数我们传入了一个Int类型的 cacheSize参数,这个参数代表缓存的最大边界是当前显示的1/8的容量。

sizeOf
这个方法是计算每个缓存对象大小的,内部通过这个方法叠加大小看是否超过了,我们初始化构造函数中的大小。

  private LruCache<String, Bitmap> mCache;
  private Bitmap bitmap;
  private LruCache<String, Bitmap> mCache;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_glide);
        img = findViewById(R.id.iamge);
        initLRUCache();
        findViewById(R.id.but).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Bitmap bitmap = getCacheImage("bitmap");
                if (bitmap == null) {
                    requestImage();
                } else {
                    Log.i("jinwei", "获取缓存");
                    img.setImageBitmap(bitmap);
                }
            }
        });

    private void initLRUCache() {
        long maxMemory = Runtime.getRuntime().maxMemory();
        int cacheSize = (int) (maxMemory / 8);
        mCache = new LruCache<String, Bitmap>(cacheSize) {

            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };
    }

  

    android.os.Handler handler = new android.os.Handler() {
        @Override
        public void dispatchMessage(Message msg) {
            super.dispatchMessage(msg);
            img.setImageBitmap(bitmap);
        }
    };

    private void httpUrlConnectionImage() throws MalformedURLException {
        Log.i("jinwei", "request Image");
        URL url = new URL("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1535715922665&di=b706c8b7a4f907a07b1f5062073a4e2a&imgtype=0&src=http%3A%2F%2Fwww.baimg.com%2Fuploadfile%2F2015%2F0413%2FBTW_2015041347455.jpg");
        try {
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setReadTimeout(5000);
            connection.setConnectTimeout(5000);
            InputStream inputStream = connection.getInputStream();
            bitmap = BitmapFactory.decodeStream(inputStream);
            addCache("bitmap", bitmap);
            handler.sendEmptyMessage(2);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void initLRUCache() {
        long maxMemory = Runtime.getRuntime().maxMemory();
        int cacheSize = (int) (maxMemory / 8);
        mCache = new LruCache<String, Bitmap>(cacheSize) {

            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };
    }

    /**
     * 缓存取bitmap
     *
     * @param key key
     * @return 缓存bm
     */
    private Bitmap getCacheImage(String key) {
        Bitmap mb = mCache.get(key);
        return mb;
    }

    /**
     * 移除缓存
     *
     * @param key
     */
    private void removeCache(String key) {
        mCache.remove(key);
    }

    /**
     * 添加一个缓存
     *
     * @param key
     * @param bitmap
     */
    private void addCache(String key, Bitmap bitmap) {
        if (getCacheImage(key) == null) {
            mCache.put(key, bitmap);
        }
    }

    private void requestImage() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    httpUrlConnectionImage();
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

原理分析

可以看到构造里面做的操作,初始化可一个LinkedHashMap,所有LRU的核心就是通过hasmap来存储缓存对象的,maxSize保存了传入预设的大小值。

    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;

 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);
    }

put方法

首先判断了key-value是否为null,为Null会抛出一个异常,所以LRU不支持key-value为null值。Lru的put方法是线程安全的,safeSizeOf就是会调用我们之前实现的sizeOf方法。

/**
     * 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) {
                size -= safeSizeOf(key, previous);
            }
        }

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

        trimToSize(maxSize);
        return previous;
    }

     private int safeSizeOf(K key, V value) {
        int result = sizeOf(key, value);
        if (result < 0) {
            throw new IllegalStateException("Negative size: " + key + "=" + value);
        }
        return result;
    }

trimToSize

注释说明的检查最近最少使用的的对象进行清除,当然是要缓存超出边界了才会进行清除。size <= maxSize这个判断可以看出来。这个方法在put方法中调用,意思就是每次Put的时候都会检查当然的缓存容量,如果超出就会清除最近最少使用的对象。

 /**
     * Remove the eldest entries until the total of remaining entries is at or
     * below the requested size.
     *
     * @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) {
                    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

这个方法比较容易理解,mapValue = map.get(key);如果mapValue不为NUll就直接返回我们缓存的对象。

/**
     * 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.
     */
    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;
        }
    }

remove

remove直接会key 对应的对象清除掉,响应缓存容量也会减少。

  /**
     * 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;
    }
上一篇下一篇

猜你喜欢

热点阅读