1. 程式人生 > >Android lrucache 實現與使用(Android內存優化)

Android lrucache 實現與使用(Android內存優化)

hashmap 獲取 fin pub viewpage map.entry ring zhong 實現

什麽是LruCache?

LruCache實現原理是什麽?

這兩個問題其實可以作為一個問題來回答,知道了什麽是 LruCache,就只然而然的知道 LruCache 的實現原理;Lru的全稱是Least Recently Used ,近期最少使用的!所以我們可以推斷出 LruCache 的實現原理:把近期最少使用的數據從緩存中移除,保留使用最頻繁的數據,那具體代碼要怎麽實現呢,我們進入到源碼中看看。

LruCache源碼分析


public class LruCache<K, V> {
    //緩存 map 集合,為什麽要用LinkedHashMap
    //因為沒錯取了緩存值之後,都要進行排序,以確保
    //下次移除的是最少使用的值
    private final LinkedHashMap<K, V> map;
    //當前緩存的值
    private int size;
    //最大值
    private int maxSize;
    //添加到緩存中的個數
    private int putCount;
    //創建的個數
    private int createCount;
    //被移除的個數
    private int evictionCount;
    //命中個數
    private int hitCount;
    //丟失個數
    private int missCount;

    //實例化 Lru,需要傳入緩存的最大值
    //這個最大值可以是個數,比如對象的個數,也可以是內存的大小
    //比如,最大內存只能緩存5兆
    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);
    }

    //重置最大緩存的值
    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }

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

    //通過 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++;
        }


        //如果沒有,用戶可以去創建
        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;
        }
    }

    //添加緩存,跟上面這個方法的 create 之後的代碼一樣的
    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 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 = null;
                for (Map.Entry<K, V> entry : map.entrySet()) {
                    toEvict = entry;
                }

                if (toEvict == null) {
                    break;
                }
                //移除最後一個,也就是最少使用的緩存
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }

    //手動移除,用戶調用
    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;
    }
    //這裏用戶可以重寫它,實現數據和內存回收操作
    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}


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

        //這個方法要特別註意,跟我們實例化 LruCache 的 maxSize 要呼應,怎麽做到呼應呢,比如 maxSize 的大小為緩存的個數,這裏就是 return 1就 ok,如果是內存的大小,如果5M,這個就不能是個數 了,這是應該是每個緩存 value 的 size 大小,如果是 Bitmap,這應該是 bitmap.getByteCount();
    protected int sizeOf(K key, V value) {
        return 1;
    }

    //清空緩存
    public final void evictAll() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }


    public synchronized final int size() {
        return size;
    }


    public synchronized final int maxSize() {
        return maxSize;
    }


    public synchronized final int hitCount() {
        return hitCount;
    }


    public synchronized final int missCount() {
        return missCount;
    }


    public synchronized final int createCount() {
        return createCount;
    }


    public synchronized final int putCount() {
        return putCount;
    }


    public synchronized final int evictionCount() {
        return evictionCount;
    }


    public synchronized final Map<K, V> snapshot() {
        return new LinkedHashMap<K, V>(map);
    }
}

LruCache 使用

先來看兩張內存使用的圖

技術分享圖片

                             圖-1

技術分享圖片

                            圖-2

以上內存分析圖所分析的是同一個應用的數據,唯一不同的是圖-1沒有使用 LruCache,而圖-2使用了 LruCache;可以非常明顯的看到,圖-1的內存使用明顯偏大,基本上都是在30M左右,而圖-2的內存使用情況基本上在20M左右。這就足足省了將近10M的內存!

ok,下面把實現代碼貼出來

/**
 * Created by gyzhong on 15/4/5.
 */
public class LruPageAdapter extends PagerAdapter {

    private List<String> mData ;
    private LruCache<String,Bitmap> mLruCache ;
    private int mTotalSize = (int) Runtime.getRuntime().totalMemory();
    private ViewPager mViewPager ;

    public LruPageAdapter(ViewPager viewPager ,List<String> data){
        mData = data ;
        mViewPager = viewPager ;
        /*實例化LruCache*/
        mLruCache = new LruCache<String,Bitmap>(mTotalSize/5){

            /*當緩存大於我們設定的最大值時,會調用這個方法,我們可以用來做內存釋放操作*/
            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                super.entryRemoved(evicted, key, oldValue, newValue);
                if (evicted && oldValue != null){
                    oldValue.recycle();
                }
            }
            /*創建 bitmap*/
            @Override
            protected Bitmap create(String key) {
                final int resId = mViewPager.getResources().getIdentifier(key,"drawable",
                        mViewPager.getContext().getPackageName()) ;
                return BitmapFactory.decodeResource(mViewPager.getResources(),resId) ;
            }
            /*獲取每個 value 的大小*/
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        } ;
    }


    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        View view = LayoutInflater.from(container.getContext()).inflate(R.layout.view_pager_item, null) ;
        ImageView imageView = (ImageView) view.findViewById(R.id.id_view_pager_item);
        Bitmap bitmap = mLruCache.get(mData.get(position));
        imageView.setImageBitmap(bitmap);
        container.addView(view);
        return view;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View) object);
    }

    @Override
    public int getCount() {
        return mData.size();
    }



    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }
}

總結

1、LruCache 是基於 Lru算法實現的一種緩存機制;
2、Lru算法的原理是把近期最少使用的數據給移除掉,當然前提是當前數據的量大於設定的最大值。
3、LruCache 沒有真正的釋放內存,只是從 Map中移除掉數據,真正釋放內存還是要用戶手動釋放。

Android lrucache 實現與使用(Android內存優化)