使用LruCache缓存,轻松解决图片过多造成的OOM

本文转载于我果:Sunzxyong
原文连接:http://blog.csdn.net/u010687392/article/details/46985653
Android中一般情况下采取的缓存策略是使用二级缓存,即内存缓存+硬盘缓存—>LruCache+DiskLruCache,二级缓存可以满足大部分的需求了,另外还有个三级缓存(内存缓存+硬盘缓存+网络缓存),其中DiskLruCache就是硬盘缓存,下篇再讲吧!
1、那么LruCache到底是什么呢?
查了下官方资料,是这样定义的:
LruCache 是对限定数量的缓存对象持有强引用的缓存,每一次缓存对象被访问,都会被移动到队列的头部。当有对象要被添加到已经达到数量上限的 LruCache 中,队列尾部的对象将会被移除,而且可能会被垃圾回收器回收。LruCache 中的 Lru 指的是“Least Recently Used-近期最少使用算法”。这就意味着,LruCache 是一个能够判断哪个缓存对象是近期最少使用的缓存对象,从而把最少使用的移至队尾,而近期使用的则留在队列前面了。举个例子:比如我们有a、b、c、d、e五个元素,而a、c、d、e都被访问过,唯有b元素没有访问,则b元素就成为了近期最少使用元素了,就移至在队尾了。

从上面的叙述中我们总结可以知道LruCache核心思想就两点:

、LruCache使用的是近期最少使用算法,近期使用最少的将会移至到队尾,而近期刚刚使用的则会移至到头部。

、LruCache缓存的大小是限定的(限定的大小由我们定义),当LruCache存储空间满了就会移除队尾的而为新的对象的加入腾出控件。

2、我们还是从源码入手学学LruCache到底怎么使用吧:
我们先看看LruCache类中的变量有什么:

显示发现一堆int类型的变量,还有一个最重要的LinkedHashMap<K,V>这个队列,通俗的讲LinkedHashMap<K,V>就是一个双向链表存储结构。
各个变量的意思为:
size - LruCache中已经存储的大小
maxSize - 我们定义的LruCache缓存最大的空间
putCount - put的次数(为LruCache添加缓存对象的次数)
createCount - create的次数
evictionCount - 回收的次数
hitCount - 命中的次数
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);  
    } 

发现需要传入一个int类型的值,顾名思义,这就是我们定义的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;  
   }  

通过该方法我们可以知道LruCache中是通过<Key,Value>形式存储缓存数据的。意思就是我们把一个Value存储到LruCache中,并设置对应键值为key。然后判断key和value都不能为空,否则就抛异常了。之后把该Value移至队列的头部。
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;  
    }  
} 

该方法就是得到对应key缓存的Value,假如该Value存在,返回Value并且移至该Value至队列的头部,这也证实了最近最先使用的将会移至队列的头部。假如Value不存在则返回null。
remove()方法:

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;  
}  
该方法就是从LruCache缓存中移除对应key的Value值。
sizeof()方法:一般需要重写的:
[java] view plain copy print?
protected int sizeOf(K key, V value) {  
       return 1;  
   }  
重写它计算不同的Value的大小。一般我们会这样重写:
[java] view plain copy print?
mLruCache = new LruCache<String, Bitmap>(cacheSize) {  
            @Override  
            protected int sizeOf(String key, Bitmap bitmap) {  
                if(bitmap!=null){  
                    return bitmap.getByteCount();  
                }  
                return 0;  
            }  
        };  

好了,总结一下使用LruCache的原理:比如像ImageView中加载一张图片时候,首先会在LruCache的缓存中检查是否有对应的key值(get( key)),如果有就返回对应的Bitmap,从而更新ImageView,如果没有则重新开启一个异步线程来重新加载这张图片。
来看看用LruCache缓存Bitmap的例子:

public class MyLruCache extends AppCompatActivity{  
    private LruCache<String,Bitmap> mLruCache;  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        //得到应用程序最大可用内存  
        int maxCache = (int) Runtime.getRuntime().maxMemory();  
        int cacheSize = maxCache / 8;//设置图片缓存大小为应用程序总内存的1/8  
        mLruCache = new LruCache<String, Bitmap>(cacheSize) {  
            @Override  
            protected int sizeOf(String key, Bitmap bitmap) {  
                if(bitmap!=null){  
                    return bitmap.getByteCount();  
                }  
                return 0;  
            }  
        };  
    }  
    /** 
     * 添加Bitmap到LruCache中 
     * 
     * @param key 
     * @param bitmap 
     */  
    public void putBitmapToLruCache(String key, Bitmap bitmap) {  
        if (getBitmapFromLruCache(key) == null) {  
            mLruCache.put(key, bitmap);  
        }  
    }  

    /** 
     * @param key 
     * @return 从LruCache缓存中获取一张Bitmap,没有则会返回null 
     */  
    public Bitmap getBitmapFromLruCache(String key) {  
        return mLruCache.get(key);  
    }  
}  

下面通过一个实例来看看怎么用:
先看看效果吧:
这里写图片描述
这里写图片描述
贴下主要代码:
MainActivity:

public class MainActivity extends ActionBarActivity {  
    private GridView mGridView;  
    private List<String> datas;  
    private Toolbar mToolbar;  
    private GridViewAdapter mAdapter;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        Log.v("zxy", "cache:" + getCacheDir().getPath());  
        Log.v("zxy", "Excache:" + getExternalCacheDir().getPath());  
        mToolbar = (Toolbar) findViewById(R.id.toolbar);  
        mToolbar.setTitleTextColor(Color.WHITE);  
        mToolbar.setNavigationIcon(R.mipmap.icon);  
        setSupportActionBar(mToolbar);  
        initDatas();  

        mGridView = (GridView) findViewById(R.id.gridView);  
        mAdapter = new GridViewAdapter(this, mGridView, datas);  
        mGridView.setAdapter(mAdapter);  
        mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {  
            @Override  
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {  
                Toast.makeText(MainActivity.this, "position=" + position + ",id=" + id, Toast.LENGTH_SHORT).show();  
            }  
        });  

    }  

    public void initDatas() {  
        datas = new ArrayList<>();  
        for (int i = 0; i < 55; i++) {  
            datas.add(URLDatasTools.imageUrls[i]);  
        }  
    }  

    @Override  
    protected void onDestroy() {  
        super.onDestroy();  
        mAdapter.cancelAllDownloadTask();//取消所有下载任务  
    }  
}  

GridViewAdapter:

public class GridViewAdapter extends BaseAdapter implements AbsListView.OnScrollListener {  
    private List<DownloadTask> mDownloadTaskList;//所有下载异步线程的集合  
    private Context mContext;  
    private GridView mGridView;  
    private List<String> datas;  
    private LruCache<String, Bitmap> mLruCache;  
    private int mFirstVisibleItem;//当前页显示的第一个item的位置position  
    private int mVisibleItemCount;//当前页共显示了多少个item  
    private boolean isFirstRunning = true;  

    public GridViewAdapter(Context context, GridView mGridView, List<String> datas) {  
        this.mContext = context;  
        this.datas = datas;  
        this.mGridView = mGridView;  
        this.mGridView.setOnScrollListener(this);  
        mDownloadTaskList = new ArrayList<>();  
        initCache();  
    }  

    private void initCache() {  
        //得到应用程序最大可用内存  
        int maxCache = (int) Runtime.getRuntime().maxMemory();  
        int cacheSize = maxCache / 8;//设置图片缓存大小为应用程序总内存的1/8  
        mLruCache = new LruCache<String, Bitmap>(cacheSize) {  
            @Override  
            protected int sizeOf(String key, Bitmap bitmap) {  
                if (bitmap != null) {  
                    return bitmap.getByteCount();  
                }  
                return 0;  
            }  
        };  
    }  

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

    @Override  
    public Object getItem(int position) {  
        return datas.get(position);  
    }  

    @Override  
    public long getItemId(int position) {  
        return position;  
    }  

    @Override  
    public View getView(int position, View convertView, ViewGroup parent) {  
        convertView = LayoutInflater.from(mContext).inflate(R.layout.layout_item, parent, false);  
        ImageView mImageView = (ImageView) convertView.findViewById(R.id.imageView);  
        TextView mTextView = (TextView) convertView.findViewById(R.id.textView);  
        String url = datas.get(position);  
        mImageView.setTag(String2MD5Tools.hashKeyForDisk(url));//设置一个Tag为md5(url),保证图片不错乱显示  
        mTextView.setText("第" + position + "项");  
        setImageViewForBitmap(mImageView, url);  
        return convertView;  

    }  

    /** 
     * 给ImageView设置Bitmap 
     * 
     * @param imageView 
     * @param url 
     */  
    private void setImageViewForBitmap(ImageView imageView, String url) {  
        String key = String2MD5Tools.hashKeyForDisk(url);//对url进行md5编码  
        Bitmap bitmap = getBitmapFromLruCache(key);  
        if (bitmap != null) {  
            //如果缓存中存在,那么就设置缓存中的bitmap  
            imageView.setImageBitmap(bitmap);  
        } else {  
            //不存在就设置个默认的背景色  
            imageView.setBackgroundResource(R.color.color_five);  
        }  
    }  

    /** 
     * 添加Bitmap到LruCache中 
     * 
     * @param key 
     * @param bitmap 
     */  
    public void putBitmapToLruCache(String key, Bitmap bitmap) {  
        if (getBitmapFromLruCache(key) == null) {  
            mLruCache.put(key, bitmap);  
        }  
    }  

    /** 
     * @param key 
     * @return 从LruCache缓存中获取一张Bitmap,没有则会返回null 
     */  
    public Bitmap getBitmapFromLruCache(String key) {  
        return mLruCache.get(key);  
    }  

    @Override  
    public void onScrollStateChanged(AbsListView view, int scrollState) {  
        if (scrollState == SCROLL_STATE_IDLE) {//GridView为静止状态时,让它去下载图片  
            loadBitmap(mFirstVisibleItem, mVisibleItemCount);  
        } else {  
            //滚动时候取消所有下载任务  
            cancelAllDownloadTask();  
        }  
    }  

    @Override  
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {  
        mFirstVisibleItem = firstVisibleItem;  
        mVisibleItemCount = visibleItemCount;  
        if (isFirstRunning && visibleItemCount > 0) {//首次进入时加载图片  
            loadBitmap(mFirstVisibleItem, mVisibleItemCount);  
            isFirstRunning = false;  
        }  
    }  

    /** 
     * 加载图片到ImageView中 
     * 
     * @param mFirstVisibleItem 
     * @param mVisibleItemCount 
     */  
    private void loadBitmap(int mFirstVisibleItem, int mVisibleItemCount) {  
        //首先判断图片在不在缓存中,如果不在就开启异步线程去下载该图片  
        for (int i = mFirstVisibleItem; i < mFirstVisibleItem + mVisibleItemCount; i++) {  
            final String url = datas.get(i);  
            String key = String2MD5Tools.hashKeyForDisk(url);  
            Bitmap bitmap = getBitmapFromLruCache(key);  
            if (bitmap != null) {  
                //缓存中存在该图片的话就设置给ImageView  
                ImageView mImageView = (ImageView) mGridView.findViewWithTag(String2MD5Tools.hashKeyForDisk(url));  
                if (mImageView != null) {  
                    mImageView.setImageBitmap(bitmap);  
                }  
            } else {  
                //不存在的话就开启一个异步线程去下载  
                DownloadTask task = new DownloadTask();  
                mDownloadTaskList.add(task);//把下载任务添加至下载集合中  
                task.execute(url);  
            }  
        }  
    }  

    class DownloadTask extends AsyncTask<String, Void, Bitmap> {  
        String url;  
        @Override  
        protected Bitmap doInBackground(String... params) {  
            //在后台开始下载图片  
            url = params[0];  
            Bitmap bitmap = downloadBitmap(url);  
            if (bitmap != null) {  
                //把下载好的图片放入LruCache中  
                String key = String2MD5Tools.hashKeyForDisk(url);  
                putBitmapToLruCache(key, bitmap);  
            }  
            return bitmap;  
        }  

        @Override  
        protected void onPostExecute(Bitmap bitmap) {  
            super.onPostExecute(bitmap);  
            //把下载好的图片显示出来  
            ImageView mImageView = (ImageView) mGridView.findViewWithTag(String2MD5Tools.hashKeyForDisk(url));  
            if (mImageView != null && bitmap != null) {  
                mImageView.setImageBitmap(bitmap);  
                mDownloadTaskList.remove(this);//把下载好的任务移除  
            }  
        }  

    }  

    /** 
     * @param tasks 
     * 取消所有的下载任务 
     */  
    public void cancelAllDownloadTask(){  
        if(mDownloadTaskList!=null){  
            for (int i = 0; i < mDownloadTaskList.size(); i++) {  
                mDownloadTaskList.get(i).cancel(true);  
            }  
        }  
    }  
    /** 
     * 建立网络链接下载图片 
     * 
     * @param urlStr 
     * @return 
     */  
    private Bitmap downloadBitmap(String urlStr) {  
        HttpURLConnection connection = null;  
        Bitmap bitmap = null;  
        try {  
            URL url = new URL(urlStr);  
            connection = (HttpURLConnection) url.openConnection();  
            connection.setConnectTimeout(5000);  
            connection.setReadTimeout(5000);  
            connection.setDoInput(true);  
            connection.connect();  
            if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {  
                InputStream mInputStream = connection.getInputStream();  
                bitmap = BitmapFactory.decodeStream(mInputStream);  
            }  
        } catch (MalformedURLException e) {  
            e.printStackTrace();  
        } catch (IOException e) {  
            e.printStackTrace();  
        } finally {  
            if (connection != null) {  
                connection.disconnect();  
            }  
        }  
        return bitmap;  
    }  
}  

好了,LruCache就介绍到这了,其中上面的例子有个不足就是没有做图片大小检查,过大的图片没有压缩。。。
下一篇来介绍下怎么对过大的图片进行压缩!!!
源码地址:http://download.csdn.net/detail/u010687392/8920169

发布了27 篇原创文章 · 获赞 187 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/Mr_wzc/article/details/51497415
今日推荐