1. 程式人生 > >使用LruCache快取,輕鬆解決圖片過多造成的OOM

使用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

相關推薦

使用LruCache快取,輕鬆解決圖片過多造成OOM

本文轉載於我果:Sunzxyong 原文連線:http://blog.csdn.net/u010687392/article/details/46985653 Android中一般情況下采取的快取策略是使用二級快取,即記憶體快取+硬碟快取—>LruCa

三步解決幀動畫圖片過多造成OOM

主要三個步驟: 說明:我只測試了63張,蠻好用,推薦給大家 1.首先我們匯入一個我們需要的   commons-io-1.4.jar 2.新建一個 工具類MyAnimationDrawable.Java   () package com.example.frame.de

解決TIME_WAIT過多造成的問題

#netstat -n | awk '/^tcp/ {++S[$NF]} END { for(a in S) print(a,S[a])}'LAST_ACK 14SYN_RECV 348ESTABLISHED 70FIN_WAIT1 229FIN_WAIT2 30CLOSI

Android TextView載入帶有多張圖片的HTML,並且解決圖片造成OOM

相信大家都有過需求需要載入從後臺返回的部分HTML程式碼到我們的Android手機上需求,現有的android 原生控制元件有WebView 和 TextView 可以去載入HTML,由於現在的需求我們需要記載一段HTML程式碼在RecyclerView

Android BitmapFactory.Options 解決圖片加載OOM問題

evel 寬高 lock github 取圖 math 使用 最終 過程 當我們在Android使用bitmap加載圖片過程中,它會將整張圖片所有像素都存在內存中,由於Android對圖片內存使用的限制,很容易出現OOM(Out of Memory)問題。 為了避免此類問題

關於Android載入圖片時的OOM的一些解決方法和優化

1、通過強引用和弱引用以及LRU演算法。 private static final int HARD_CACHE_CAPACITY = 20;//強引用的bitmap的數量 //為了提高圖片的利用率,通過單鏈表實現先進先出,將老的圖片移到軟引用裡面儲存 private st

Android 非同步載入圖片-LruCache和SD卡或手機快取-三級快取原理載入圖片

非同步載入圖片的例子,網上也比較多,大部分用了HashMap<String, SoftReference<Drawable>> imageCache ,但是現在已經不再推薦使用這種方式了,因為從 Android 2.3 (API Level 9)開始,垃圾回

教你怎麼十秒解決百張圖片幀動畫oom終極騷問題

在我們做動畫的時候,為了絢麗的效果,這時候我們就要用上我們的幀動畫,通過快速播放多張圖片實現動畫的效果,可當我圖片放的太多的時候,這時候就會造成oom。為了解決這個難題,我都老了0.005歲。好了,現在我們現在正題開始。先從怎麼寫幀動畫開始,這個很簡單,我們直接秒過。 首先

LruCache快取圖片+清除本地快取

/** * * 本應用資料清除管理器 */public class DataClearManager {    /**     * 獲取本應用快取大小     * @param context     * @return     */    public static Str

android--------Universal-Image-Loader圖片載入框架和結合LruCache快取圖片

本部落格包含包含Android-Universal-Image-Loader 網路圖片載入框架實現圖片載入和結合universal-image-loader與LruCache來自定義快取圖片,可以設定快取與不快取。 Android-Universal-Image-Load

Android逐幀動畫,逐幀動畫載入圖片過多時OOM異常的解決和替代方法

1.首先新增逐幀動畫 播放逐幀動畫,在工程中res目錄下建立一個anim資料夾,新增動畫anim_welcome.xml檔案如下: <?xml version="1.0" encoding="utf-8"?> <animation-li

輕鬆解決上傳圖片的變形的問題

有時候,我們編寫一個圖片上傳程式,首先需要的做的就是將圖片變成合適的大小,這就要求我們按比例來縮放我們的圖片,有的是通過編寫程式來計算,很麻煩,其實 通過一個css屬性就能輕鬆的解決這個圖片比例的問題 通過這個簡單的設定,就可以實現圖片的按比例縮放了

高效顯示Bitmap+listview衝突解決+圖片記憶體快取+硬碟快取

Android高效載入大圖 BitmapFactory提供了一些解碼(decode)的方法(decodeByteArray(), decodeFile(), decodeResource()等),用來從不同 的資源中建立一個Bitmap。 我們應該

如何解決圖片快取

對於圖片的快取現在都傾向於使用開源專案,這裡我列出幾個我搜到的: 1. Android-Universal-Image-Loader 圖片快取 2. picasso square開源的圖片快取

Android使用LruCache、DiskLruCache實現圖片快取+圖片瀑布流

PPS:本文僅用於學習利用LruCache、DiskLruCache圖片快取策略、實現瀑布流和Matix檢視大圖縮放移動等功能,如果想用到專案中,建議用更成熟的框架,如glide、picasso 等。 先上效果圖: ###圖片瀑布流 這個用

Android使用 LruCache 快取圖片

使用圖片快取技術 在你應用程式的UI介面載入一張圖片是一件很簡單的事情,但是當你需要在介面上載入一大堆圖片的時候,情況就變得複雜起來。在很多情況下,(比如使用ListView, GridView 或者 ViewPager 這樣的元件),螢幕上顯示的圖片可以通過滑動螢幕等

利用LruCache和DiskLruCache使用圖片載入的三級快取

在上一篇文章當中,我們學習了DiskLruCache的概念和基本用法,但僅僅是掌握理論知識顯然是不夠的,那麼本篇文章我們就來繼續進階一下,看一看在實戰當中應該怎樣合理使用DiskLruCache。還不熟悉DiskLruCache用法的朋友可以先去參考我的上一篇文章

關於解決版本更新造成快取問題

        我是個前端工程師,目前的工作是做一款基於即時通迅的協同辦公軟體。說是軟體,也只是用迅雷框體封裝起來的網站。         在每次的版本更新時,都會出現資原始檔載入混亂。沒錯的,這都是快取的問題,有經驗的都會嘗試著按ctrl+F5進行重新整理;但是這說到底對於使用者來講是一個功能BUG。在前

Android LruCache原始碼分析,圖片快取演算法的實現原理

今日科技快訊昨日,記者從中國聯通處獨家獲悉,聯通計劃在北京、天津、上海、深圳、杭州、南京、雄安7

使用filter: blur() 的時候解決圖片周圍泛白和容器外範圍變模糊的問題

.cn lur overflow -1 隱藏 http scale img filter 類似於這種,這個時候出現了周圍變模糊,並且邊緣泛白的情況 周圍模糊這個問題很好解決,給父容器加overflow:hidden;就可以了 效果如上,至於周圍泛白的問題就需要動點腦