標籤: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