讓App中增加LruCache緩衝,輕鬆解決圖片過多造成的OOM

來源:互聯網
上載者:User

標籤:nic   隊列   電話   prot   圖片顯示   sdn   代碼   des   roi   

上次有過電話面試中問到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







讓App中增加LruCache緩衝,輕鬆解決圖片過多造成的OOM

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.