让App中增加LruCache缓存,轻松解决图片过多造成的OOM
上次有过电话面试中问到Android中的缓存策略,当时模糊不清的回答,如今好好理一下吧。
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核心思想就两点:
1、LruCache使用的是最近最少使用算法。最近使用最少的将会移至到队尾。而最近刚刚使用的则会移至到头部。
2、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()方法:一般须要重写的:
 protected int sizeOf(K key, V value) {
        return 1;
    }
重写它计算不同的Value的大小。
一般我们会这样重写:
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就介绍到这了。当中上面的样例有个不足就是没有做图片大小检查,过大的图片没有压缩。。
。
下一篇来介绍下怎么对过大的图片进行压缩!!!
让App中增加LruCache缓存,轻松解决图片过多造成的OOM的更多相关文章
- 让App中加入LruCache缓存,轻松解决图片过多造成的OOM
		上次有过电话面试中问到Android中的缓存策略,当时模糊不清的回答,现在好好理一下吧. Android中一般情况下采取的缓存策略是使用二级缓存,即内存缓存+硬盘缓存->LruCache+Dis ... 
- 使用memcached缓存 替代solr中的LRUCache缓存
		前沿 在搜索引擎中,缓存被当做是不可缺少的部分,但是很多情况下,将缓存的实现过度依赖于分发服务器及webserver会很大程度上加重webserver 的负担,具体表现就是经常性的假死,拒绝服务,因此 ... 
- 在iOS App中增加完整的照片多选功能
		转自:http://blog.csdn.net/jasonblog/article/details/8141850 主要参考了ELCImagePickerController,不过由于UI展现上需要定 ... 
- android 使用LruCache缓存网络图片
		加载图片,图片如果达到一定的上限,如果没有一种合理的机制对图片进行释放必然会引起程序的崩溃. 为了避免这种情况,我们可以使用Android中LruCache来缓存下载的图片,防止程序出现OOM. ... 
- android应用中增加权限判断
		android6.0系统允许用户管理应用权限,可以关闭/打开权限. 所以需要在APP中增加权限判断,以免用户关闭相应权限后,APP运行异常. 以MMS为例,在系统设置——应用——MMS——权限——&g ... 
- Cordova for android怎样在App中处理退出button事件
		项目须要在HTML5 Android App中增加对返回键的处理,发现直接在Activity中加返回键处理代码不起作用,分析cordova源代码发现返回键已经被WebView处理掉了,所以仅仅能在js ... 
- Android开发之清除缓存功能实现方法,可以集成在自己的app中,增加一个新功能。
		作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 Android开发之清除缓存功能实现方法,可以集成在自己的app中,增加一个新功能. 下面是一个效果图 ... 
- 一招解决微信小程序中的H5缓存问题
		一招解决微信小程序中的H5缓存问题1.问题描述开发过程中,为了更新代码方便,往往会在小程序中嵌入H5页面.但问题来了,小程序原生代码更新版本后,简单的从微信中删除或者代码强刷就可以解决缓存问题,但小程 ... 
- 解决“iOS 7 app自动更新,无法在app中向用户展示更新内容”问题
		转自cocoachina iOS 7能在后台自动app,这对开发者来说和用户都很方便,但是还是有一些缺点.用户不会知道app本次更新的内容,除非他们上到app的App Store页面去查看.开发者也会 ... 
随机推荐
- 二叉查找树(二叉排序树)的详细实现,以及随机平衡二叉查找树Treap的分析与应用
			这是一篇两年前写的东西,自我感觉还是相当不错的Treap教程.正好期末信息科学技术概论课要求交一个论文,就把这个东西修改了一下交了,顺便也发到这里吧. 随机平衡二叉查找树Treap的分析与应用 1.序 ... 
- Java并发(十八):阻塞队列BlockingQueue
			阻塞队列(BlockingQueue)是一个支持两个附加操作的队列. 这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用. 阻塞队列常用于生产 ... 
- hdu1242 Rescue(BFS +优先队列 or BFS )
			http://acm.hdu.edu.cn/showproblem.php?pid=1242 题意: Angel被传说中神秘的邪恶的Moligpy人抓住了!他被关在一个迷宫中.迷宫的长.宽不超 ... 
- Programming 2D Games 读书笔记(第三章)
			示例一:DirectX Window Graphics类用于初始化Direct 3D 主流程: 仅需要粗体部分 try{ // Create Graphics object graphics = ... 
- sigmod2017.org
			http://sigmod2017.org/sigmod-program/#ssession20 
- AngularJS中module的导入导出
			关于AngularJS中module的导入导出,在Bob告诉我之前还没写过,谢谢Bob在这方面的指导,给到我案例代码. 在AngularJS实际项目中,我们可能需要把针对某个领域的各个方面放在不同的m ... 
- 避免string.Format方法的装箱
			我们知道,使用string.Format方法可能会存在装箱的情况.比如如下: static void Main(string[] args) { string s = string.Format(&q ... 
- delphi Format格式化函数
			Format是一个很常用,却又似乎很烦的方法,本人试图对这个方法的帮助进行一些翻译,让它有一个完整的概貌,以供大家查询之用: 首先看它的声明:function Format(const Format: ... 
- css 滚动条样式
			1,Overflow内容溢出时的设置 overflow 水平及垂直方向内容溢出时的设置 overflow-x 水平方向内容溢出时的设置 overflow-y 垂直方向内容溢出时的设置 以上三个属性设置 ... 
- 【linux】在linux上生成SSH-key  简单原理介绍+生成步骤
			1.首先什么是SSH Secure Shell (SSH) 是一个允许两台电脑之间通过安全的连接进行数据交换的网络协议.通过加密保证了数据的保密性和完整性.SSH采用公钥加密技术来验证远程主机,以及( ... 
