android加载多图 LruCache与硬盘缓存融为一体

这两天工作之余,刷了一些博客,了解了android利用缓存的一些东西,想必大家也都有一些相关的经验,新手不了解也没有关系,本博文就给大家介绍一下缓存的使用。
——————————————————我是分割线————————————————
首先简单介绍一下内缓存,图片加载使用内缓存最多的应该是LruCache,我们可以对这个类设置泛型LruCache

mInnerCache = new LruCache<String, Bitmap>(1*1024*1024){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes()*value.getHeight();
            }
        };

构造中的参数是我们需要指定的内存缓存的大小,注意这里我们需要 重写sizeOf方法,它表示了如何计算一个value值得大小,不重写的话sizeOf默认返回1,也就是说没有起到控制bitmap占用内存大小的功能。

然后我们在从某些途径(网络或文件系统)获得了bitmap时,直接mInnerCache.put就可以加入缓存,获取的时候get就行,它其实就是维护了一个特殊的map,对map进行了大小控制得到的功能。

这当然不是我们今天的重点!!!!!

然后我们再来简单看看硬盘缓存。

恩~我需要一个盗链:郭前辈的Android DiskLruCache完全解析,硬盘缓存的最佳方案,其中讲了硬盘缓存的使用方式。

这当然也不是我们的重点!!!

郭前辈也写过融合内存缓存和硬盘缓存的方法,在研究了他得融合方案基础上,我有些自己的想法,于是尝试做一套自己的缓存策略。

郭前辈的缓存策略是:需要获取图片时,先直接从网上下载图片至硬盘缓存中,再将硬盘缓存中的图片二次读取,并加入内存缓存中,下一次想获得该图片,先从内存缓存中找,没有的话开辟异步任务,从硬缓存中找,还没有的话操作回到开始。

我的缓存策略是: 需要获取图片时,从网络上下载至内存,加入到内存缓存,不断的向内存中写入,当内存缓存满时,将需要清理出内存缓存的bitmap缓存到硬盘;再次需要该图片时,先问内存要,没有的话开辟异步任务问硬盘要,要到了就加入内存缓存,没要到就重新从网上下载。

可能读起来有点拗口,仔细理解一下,我觉得这样更符合计算机局部化规则,也更有利于我们节省流量。

现在到了今天的重点了!

要实现本文中的缓存策略无疑是有难度的,难点有二:
1、如何获取LruCache即将清理掉的bitmap;
2、如何将内存中的bitmap写入硬盘。

要解决这两个问题我们得从LruCache的源码入手:

package com.example.volleydemo.util;

import java.util.LinkedHashMap;
import java.util.Map;

public class Lru<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; // put的次数
    private int createCount; // create的次数
    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 Lru(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

    /**
     * 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返回相应的item,或者创建返回相应的item。相应的item会移动到队列的头部,
     * 如果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++; // 丢失
        }

        /*
         * 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. 如果丢失了就试图创建一个item
         */

        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
                // 如果前面存在oldValue,那么撤销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;
        }
    }

    /**
     * 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) { // 返回的先前的value值
                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. 清空cache空间
     */
    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 = map.entrySet().iterator().next();
                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. 删除key相应的cache项,返回相应的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;
    }

    /**
     * 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.
     * 当item被回收或者删掉时调用。改方法当value被回收释放存储空间时被remove调用, 或者替换item值时put调用,默认实现什么都没做。
     * <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}.
     *            true---为释放空间被删除;false---put或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. 当某Item丢失时会调用到,返回计算的相应的value或者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.
     */
    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.
     * 返回用户定义的item的大小,默认返回1代表item的数量,最大size就是最大item值
     * <p>
     * An entry's size must not change while it is in the cache.
     */
    protected int sizeOf(K key, V value) {
        return 1;
    }

    /**
     * Clear the cache, calling {@link #entryRemoved} on each removed entry.
     * 清空cacke
     */
    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. 返回当前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);
    }
}

这是我在网上找到的LruCache的源码,确实挺短的,加上一些有的没的才300+行,我们关注一下这个方法:

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

因为需要得到即将清理出内存缓存的bitmap,就得知道什么时候会出现这种情况,只有一种!就是新项加入到cache中,cache发现大小超了,就去清理门户了。于是我们很容易知道清理门户的方法是trimToSize(maxSize)。我们就来研究一下:

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 = map.entrySet().iterator().next();
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

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

最后的 entryRemoved方法默认空实现,我们先不管它,机制的读者直接瞄准了remove方法,就是你了皮卡丘,map的remove方法自然会为我们返回这个可怜的即将被清理的bitmap,咳咳,也就是 map.remove(key);上面一行的value ,有了key有了value,想要干什么不容易?

于是第一个问题解决了!

那么第二个问题,怎么将一个bitmap写入硬盘,其实也很简单,Bitmap有一个compress方法,用它可以直接进行图片的压缩写入工作。
这里,我们队LruCache进行改造,改造的方法也很简单:

private void trimToSize(int maxSize) {
        while (true) {
            String key;
            Bitmap 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<String, Bitmap> toEvict = map.entrySet().iterator().next();
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();

                //---------把将要移出内缓存的bitmap加入硬盘缓存中
                addToOutterCache(key,value);
                //---------把将要移出内缓存的bitmap加入硬盘缓存中

                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

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

将下面这两个方法直接加入LruCache类中:

    private void addToOutterCache(String key,Bitmap value) {
        try {
            if(outterCache.get(getMd5(key)) != null){
                Log.v("addToOutterCache", "不用写入");
                return;
            }
        } catch (NoSuchAlgorithmException | IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        try {
            Log.v("addToOutterCache", "写入硬盘缓存");
            DiskLruCache.Editor editor = outterCache.edit(getMd5(key));
            OutputStream os=editor.newOutputStream(0);
            value.compress(Bitmap.CompressFormat.PNG, 0, os);
            editor.commit();
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public String getMd5(String str) throws NoSuchAlgorithmException {
        final MessageDigest md5 = MessageDigest.getInstance("MD5");
        byte[] bs = md5.digest(str.getBytes());
        StringBuilder sb = new StringBuilder(40);
        for(byte x:bs) {
            if((x & 0xff)>>4 == 0) {
                sb.append("0").append(Integer.toHexString(x & 0xff));
            } else {
                sb.append(Integer.toHexString(x & 0xff));
            }
        }
        return sb.toString();
    }

好了 我们的LruCache改造工程完成了,也就去去几十行,给新类起个名字叫BitmapLru。

下面贴一下我们工程的代码:

MainActivity还要吗?。。。。

public class MainActivity extends Activity {

    ListView listview;
    CacheAdapter adapter;
    List<String> datalist;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        datalist=new ArrayList<String>();
        for(String s:Urldata.imageUrls){
            datalist.add(s);
        }

        listview=(ListView) findViewById(R.id.listview);
        adapter=new CacheAdapter(this,datalist);
        listview.setAdapter(adapter);

    }
}

CacheAdapter:

public class CacheAdapter extends BaseAdapter {

    LayoutInflater infalter;
    List<String> datalist;

    BitmapLru mInnerCache;
    DiskLruCache mOutterCache;

    BitmapFactory.Options opt;

    public CacheAdapter(Context c, List<String> datalist) {
        super();
        this.infalter = LayoutInflater.from(c);
        this.datalist = datalist;
        opt=new Options();
        opt.inPreferredConfig=Config.RGB_565;
        try {
            mOutterCache = DiskLruCache.open(getDiskCacheDir(c, "bitmap"),
                    1, 1, 5 * 1024 * 1024);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        mInnerCache = new BitmapLru(3*1024*1024,mOutterCache){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes()*value.getHeight();
            }
        };
    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return datalist.size();
    }

    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return datalist.get(position);
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {

            holder = new ViewHolder();
            convertView = infalter.inflate(R.layout.list_item, null);
            holder.icon = (ImageView) convertView.findViewById(R.id.News_icon);
            holder.tittle = (TextView) convertView
                    .findViewById(R.id.News_Tittle);
            holder.content = (TextView) convertView
                    .findViewById(R.id.News_Content);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.icon.setTag(datalist.get(position));  

        holder.icon.setBackgroundResource(R.drawable.ic_launcher);
        holder.tittle.setText("hello " + position);
        holder.content.setText("fine " + position);
        loadImage(holder.icon, datalist.get(position));

        return convertView;
    }

    private void loadImage(ImageView icon, String url) {
        Bitmap image = getFromInnerCache(url);
        if (image == null) {
            new GrobImage(icon).execute(url);
        } else {
            Log.e("loadImage","从内存缓存中获取");
            icon.setImageBitmap(image);
        }

    }

    private Bitmap getFromInnerCache(String url) {
        // TODO Auto-generated method stub
        return mInnerCache.get(url);
    }

    class ViewHolder {
        ImageView icon;
        TextView tittle;
        TextView content;
    }

    class GrobImage extends AsyncTask<String, Void, Bitmap> {
        private String imageUrl;
        private ImageView view;
        public GrobImage(ImageView view){
            this.view=view;
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            // TODO Auto-generated method stub
            FileInputStream fileInputStream = null;
            imageUrl = params[0];
            Bitmap image = null;
            Snapshot snapShot = null;

            try {
                snapShot = mOutterCache.get(getMd5(imageUrl));
                if(snapShot!=null){
                    //从硬盘缓存中得到了数据
                    Log.e("doInBackground", "从硬盘缓存中找到数据");
                    fileInputStream=(FileInputStream) snapShot.getInputStream(0);
                    FileDescriptor fileDescriptor = fileInputStream.getFD();
                    image=BitmapFactory.decodeFileDescriptor(fileDescriptor);
                    fileInputStream.close();
                    if(image!=null){
                        mInnerCache.put(imageUrl, image);
                    }

                }else{
                    //需要在网络中获取
                    Log.e("doInBackground", "网络中获取");
                    URL url=new URL(imageUrl);
                    HttpURLConnection conn=(HttpURLConnection) url.openConnection();

                    image=BitmapFactory.decodeStream(conn.getInputStream(), null, opt);
                    mInnerCache.put(imageUrl, image);
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }  
            return image;
        }

        @Override
        protected void onPostExecute(Bitmap result) {

            if (view != null && result != null) {  
                view.setImageBitmap(result);  
            }
            super.onPostExecute(result);
        }

    }
    public File getDiskCacheDir(Context context, String uniqueName) {  
        String cachePath;  
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())  
                || !Environment.isExternalStorageRemovable()) {  
            cachePath = context.getExternalCacheDir().getPath();  
        } else {  
            cachePath = context.getCacheDir().getPath();  
        }  
        return new File(cachePath + File.separator + uniqueName);  
    }
    public String getMd5(String str) throws NoSuchAlgorithmException {
        final MessageDigest md5 = MessageDigest.getInstance("MD5");
        byte[] bs = md5.digest(str.getBytes());
        StringBuilder sb = new StringBuilder(40);
        for(byte x:bs) {
            if((x & 0xff)>>4 == 0) {
                sb.append("0").append(Integer.toHexString(x & 0xff));
            } else {
                sb.append(Integer.toHexString(x & 0xff));
            }
        }
        return sb.toString();
    }
}

BitmapLru:

public class BitmapLru {
    private final LinkedHashMap<String, Bitmap> map;

    /** Size of this cache in units. Not necessarily the number of elements. */
    private int size; // 已经存储的大小
    private int maxSize; // 规定的最大存储空间

    private int putCount; // put的次数
    private int createCount; // create的次数
    private int evictionCount; // 回收的次数
    private int hitCount; // 命中的次数
    private int missCount; // 丢失的次数

    //---------------------------------------------------------------------
    DiskLruCache outterCache;
    public BitmapLru(int maxSize,DiskLruCache outterCache) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.outterCache=outterCache;
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<String,Bitmap>(0, 0.75f, true);
    }
    //---------------------------------------------------------------------

    /**
     * @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 BitmapLru(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<String,Bitmap>(0, 0.75f, true);
    }

    /**
     * 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返回相应的item,或者创建返回相应的item。相应的item会移动到队列的头部,
     * 如果item的value没有被cache或者不能被创建,则返回null。
     */
    public final Bitmap get(String key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        Bitmap 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. 如果丢失了就试图创建一个item
         */

        Bitmap 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
                // 如果前面存在oldValue,那么撤销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;
        }
    }

    /**
     * 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 Bitmap put(String key, Bitmap value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

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

    /**
     * @param maxSize
     *            the maximum size of the cache before returning. May be -1 to
     *            evict even 0-sized elements. 清空cache空间
     */
    private void trimToSize(int maxSize) {
        while (true) {
            String key;
            Bitmap 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<String, Bitmap> toEvict = map.entrySet().iterator().next();
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();

                //---------把将要移出内缓存的bitmap加入硬盘缓存中
                addToOutterCache(key,value);
                //---------把将要移出内缓存的bitmap加入硬盘缓存中

                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

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

    //-----------------------改写部分------------------------
    private void addToOutterCache(String key,Bitmap value) {
        try {
            if(outterCache.get(getMd5(key)) != null){
                Log.v("addToOutterCache", "不用写入");
                return;
            }
        } catch (NoSuchAlgorithmException | IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        try {
            Log.v("addToOutterCache", "写入硬盘缓存");
            DiskLruCache.Editor editor = outterCache.edit(getMd5(key));
            OutputStream os=editor.newOutputStream(0);
            value.compress(Bitmap.CompressFormat.PNG, 0, os);
            editor.commit();
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public String getMd5(String str) throws NoSuchAlgorithmException {
        final MessageDigest md5 = MessageDigest.getInstance("MD5");
        byte[] bs = md5.digest(str.getBytes());
        StringBuilder sb = new StringBuilder(40);
        for(byte x:bs) {
            if((x & 0xff)>>4 == 0) {
                sb.append("0").append(Integer.toHexString(x & 0xff));
            } else {
                sb.append(Integer.toHexString(x & 0xff));
            }
        }
        return sb.toString();
    }
    //-----------------------改写部分------------------------

    /**
     * Removes the entry for {@code key} if it exists. 删除key相应的cache项,返回相应的value
     * 
     * @return the previous value mapped by {@code key}.
     */
    public final Bitmap remove(String key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        Bitmap 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.
     * 当item被回收或者删掉时调用。改方法当value被回收释放存储空间时被remove调用, 或者替换item值时put调用,默认实现什么都没做。
     * <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}.
     *            true---为释放空间被删除;false---put或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, String key, Bitmap oldValue, Bitmap 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. 当某Item丢失时会调用到,返回计算的相应的value或者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.
     */
    protected Bitmap create(String key) {
        return null;
    }

    private int safeSizeOf(String key, Bitmap 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.
     * 返回用户定义的item的大小,默认返回1代表item的数量,最大size就是最大item值
     * <p>
     * An entry's size must not change while it is in the cache.
     */
    protected int sizeOf(String key, Bitmap value) {
        return 1;
    }

    /**
     * Clear the cache, calling {@link #entryRemoved} on each removed entry.
     * 清空cacke
     */
    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. 返回当前cache的副本,从最近最少访问到最多访问
     */
    public synchronized final Map<String, Bitmap> snapshot() {
        return new LinkedHashMap<String, Bitmap>(map);
    }

    @Override
    public synchronized final java.lang.String toString() {
        int accesses = hitCount + missCount;
        int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
        return java.lang.String.format(
                "LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", maxSize,
                hitCount, missCount, hitPercent);
    }
}

DiskLruCache这个类不贴了,工具类,郭前辈的在我前面提到的博文中也给出来了,还有图片的url,我无耻的使用了郭前辈的相册。

!!!!!最后说一下注意事项!!!!!!!!!:

由于硬盘缓存的操作牵扯到journal文件的读写,所以多线程操作很可能引起冲突,这里我们为什么可以这么做,因为我们使用的是AsyncTask异步任务,AsyncTask在android3.0以后不是并发的,等于是一个大小为1的线程池,所以在AsyncTask中操作不会引起冲突。

这种实现方式也有缺点:没有克服大批量IO的问题(谁克服了?)。我工程中也没做图片的闪烁处理,原理上说就是对异步任务的cancel,不是主要矛盾嘛,嘿嘿~见谅。

可能还有很多代码问题,希望指正,谢谢。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值