• 让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就介绍到这了。当中上面的样例有个不足就是没有做图片大小检查,过大的图片没有压缩。。

    下一篇来介绍下怎么对过大的图片进行压缩!!!

    源代码地址:http://download.csdn.net/detail/u010687392/8920169







  • 相关阅读:
    Postgresql
    Partitioning with PostgreSQL v11 (转发)
    Partitioning with PostgreSQL v11 (转发)
    What is Data Partitioning?(转发)(未完待续)
    How to use table partitioning to scale PostgreSQL(转发)
    PostgreSQL 创建分区表(转发)
    json vs jsonb
    性能不佳的多线程应用程序的常见模式(microsoft)
    提高.net程序性能和稳定性-CLR Profile(转发)
    检查c#代码内存泄露工具-CLR Profiler工具使用(转发)
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/8893128.html
Copyright © 2020-2023  润新知