LruCache:从网络加载图片缓存实例
OOM異常
堆內存用于存儲實例對象,當程序不斷創建對象,并且對象都有引用指向,那么垃圾回收機制就不會清理這些對象,當對象多到擠滿堆內存的上限后,就產生OOM異常。Android系統為每個應用程序使用的內存設置了一個上限。這個上限值可以用下面的方法取得: long maxSize = Runtime.getRuntime().maxMemory();
OOM異常通常分為下面幾種情況:
1.內存泄漏導致的OOM:new出來的很多對象已經不需要了,但仍然有引用指向,所以垃圾回收機制無法回收。
其場景類似于:創建了一個Handler,并且執行了一個Delay的任務沒有完成,此時擁有此Handler對象的宿主對象亦不能被回收。
或者,static的方法或成員太多,被外部使用,而外部的牽引對象沒有對其進行釋放,那么整個static的類都不會被釋放,也就造成內存泄漏。
2.內存溢出:new出來的對象都是需要的,但堆內存太小裝不下了。
如在一個GridView中顯示圖片,如果不壓縮圖片,采用為1920*1080的32位圖片,每個將占用1920*1080*4=7MB內存,如此大的圖片,只能存儲幾張,超出即可導致內存溢出。
3.關于 Bitmap 引起的泄漏,網上還有另一種說法:
一個進程的內存可以由2個部分組成:java使用內存,C使用內存,這兩個內存的和必須小于16M(假設為這么多),不然就會出現大家熟悉的OOM,這個就是第一種OOM的情況。
一旦內存分配給Java后,以后這塊內存即使釋放后,也只能給Java的使用,這個估計和java虛擬機里把內存分成好幾塊進行緩存的原因有關。
所以如果Java突然占用了一個大塊內存,即使很快釋放了,C代碼也無法使用,而Bitmap的生成是通過malloc進行內存分配的,占用的是C的內存,如果Bitmap需要的內存大于C可用內存也會導致OOM。
避免OOM
對于java中不再使用的資源需要盡快的釋放,即設置成null。
盡量少用static方法和static成員。
對于不再使用的bitmap應該手動調用recycle方法,并且設置成null。圖片還應盡量使用軟引用方式,這樣可以加快垃圾回收。
LruCache
內存緩存技術對那些大量占用應用程序寶貴內存的圖片提供了快速訪問的方法。其中最核心的類是LruCache,這個類非常適合用來緩存圖片,它的主要算法原理是把最近使用的對象用強引用存儲在 LinkedHashMap 中,并且把最近最少使用的對象在緩存值達到預設定值之前從內存中移除。
為了能夠選擇一個合適的緩存大小給LruCache, 有以下多個因素應該放入考慮范圍內,例如:
1.設備可以為每個應用程序可分配多大的內存。
2.屏幕上一次最多需要顯示多少個圖片,有多少圖片需要進行預加載,因為有可能很快也會顯示在屏幕上。
3.屏幕大小和分辨率。一個超高分辨率的設備比起一個較低分辨率的設備,在持有相同數量圖片的時候,需要更大的緩存空間。
4.圖片的尺寸和大小,還有每張圖片會占據多少內存空間。
5.圖片被訪問的頻率有多高。如某些圖片的訪問頻率比其它圖片要高,應該使用多個 LruCache 對象來區分不同組的圖片。
6.維持好數量和質量之間的平衡。存儲多個低像素的圖片,而在后臺線程加載高像素的圖片會更有效。
并沒有一個指定的緩存大小可以滿足所有的應用程序,應該去分析程序內存的使用情況,然后制定出一個合適的解決方案。
一個太小的緩存空間,有可能造成圖片頻繁地被釋放和重新加載,這并沒有好處。而一個太大的緩存空間,則更有可能會引起 java.lang.OutOfMemory 的異常,因為 LruCache 中的強引用不能被釋放,而程序又需要內存。
使用LruCache,需要重寫sizeOf方法,返回占用的內存大小。下面是一個圖片緩存的實現:
import android.graphics.Bitmap; import android.support.v4.util.LruCache; import android.util.Log;public class BitmapCache {private static final String TAG = "debug";private LruCache<String, Bitmap> mBitmapCache;public BitmapCache() {// 獲取到可用內存的最大值,使用內存超出這個值會引起OutOfMemory異常。long maxSize = Runtime.getRuntime().maxMemory();Log.d(TAG, "maxMemory size = " + toMB(maxSize));// LruCache 使用的緩存值,使用系統分配給應用程序大小的 1/8maxSize = maxSize >> 3;// maxSize = 1 << 1024 << 1024;Log.d(TAG, "cache used maxSize = " + toMB(maxSize));mBitmapCache = new LruCache<String, Bitmap>((int) maxSize) {@Overrideprotected int sizeOf(String key, Bitmap value) {return value.getByteCount();}};}public void add(String key, Bitmap value) {mBitmapCache.put(key, value);}public void remove(String key) {mBitmapCache.remove(key);}public Bitmap get(String key) {return mBitmapCache.get(key);}public boolean containsKey(String key) {return mBitmapCache.get(key) != null;}public static long toMB(long byteOfSize) {return byteOfSize >> 20;} }?
使用緩存技術從網絡下載圖片顯示的實例類
BitmapDownloadTask 類從網絡下載圖片,并將其解析為適配ImageView大小的格式,以減少對內存的占用,并取得一個良好的顯示效果。
import android.graphics.Bitmap; import android.os.AsyncTask; import android.util.Log; import android.widget.ImageView;import java.lang.ref.SoftReference; import java.net.HttpURLConnection; import java.net.URL; import java.util.List;public class BitmapDownloadTask extends AsyncTask<Void, Void, Bitmap> {private static final String TAG = "debug";private String mImgUrl;private SoftReference<ImageView> mImageViewSoftReference;private BitmapCache mBitmapCache;private List<BitmapDownloadTask> mTaskList;private int mReqWidth;private int mReqHeight;public BitmapDownloadTask(BitmapCache bitmapCache, List<BitmapDownloadTask> tasks, ImageView imgView, String url) {mBitmapCache = bitmapCache;mTaskList = tasks;mImageViewSoftReference = new SoftReference<>(imgView);mImgUrl = url;mReqWidth = imgView.getMeasuredWidth();mReqHeight = imgView.getMeasuredHeight();}@Overrideprotected Bitmap doInBackground(Void... params) {Bitmap bitmap;HttpURLConnection conn = null;try {URL url = new URL(mImgUrl);conn = (HttpURLConnection) url.openConnection();conn.setConnectTimeout(5000);conn.setReadTimeout(5000);bitmap = BitmapTools.decodeSampledBitmapFromInputStream(conn.getInputStream(), mReqWidth, mReqHeight);Log.d(TAG, "bitmap size: " + bitmap.getWidth() + "/" + bitmap.getHeight() + ", " + bitmap.getConfig().name() + ", " + format(bitmap.getByteCount()));} catch (Exception e) {Log.e(TAG, "", e);bitmap = null;} finally {if (conn != null)conn.disconnect();}return bitmap;}@Overrideprotected void onPostExecute(Bitmap bitmap) {mTaskList.remove(this);if (bitmap != null) {mBitmapCache.add(mImgUrl, bitmap);}ImageView imgView = mImageViewSoftReference.get();if (isCancelled() || imgView == null || imgView.getTag() != mImgUrl) {bitmap = null;}if (bitmap != null) {imgView.setImageBitmap(bitmap);}super.onPostExecute(bitmap);}private String format(long byteOfSize) {long KB = byteOfSize >> 10;long MB = KB >> 10;if (MB != 0) {return MB + " MB";}return KB + " KB";} }?
public class BitmapTools {private static final String TAG = "debug";public static class Size {public Size(int width, int height) {this.width = width;this.height = height;}public void resize(int width, int height) {this.width = width;this.height = height;}public int getWidth() {return width;}public int getHeight() {return height;}@Overridepublic boolean equals(Object o) {if (this == o)return true;if (o == null || getClass() != o.getClass())return false;Size size = (Size) o;if (width != size.width)return false;return height == size.height;}@Overridepublic int hashCode() {int result = width;result = 31 * result + height;return result;}@Overridepublic String toString() {return "Size{" + "width=" + width + ", height=" + height + '}';}private int width;private int height;}public static Bitmap decodeSampledBitmapFromPath(String pathName, int reqWidth, int reqHeight) {// First decode with inJustDecodeBounds=true to check dimensionsBitmapFactory.Options opts = new BitmapFactory.Options();opts.inJustDecodeBounds = true;BitmapFactory.decodeFile(pathName, opts);// Calculate inSampleSizeopts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight);// Decode bitmap with inSampleSize setopts.inJustDecodeBounds = false;return BitmapFactory.decodeFile(pathName, opts);}public static Bitmap decodeSampledBitmapFromData(byte[] data, int reqWidth, int reqHeight) {// First decode with inJustDecodeBounds=true to check dimensionsBitmapFactory.Options opts = new BitmapFactory.Options();opts.inJustDecodeBounds = true;BitmapFactory.decodeByteArray(data, 0, data.length, opts);// Calculate inSampleSizeopts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight);// Decode bitmap with inSampleSize setopts.inJustDecodeBounds = false;return BitmapFactory.decodeByteArray(data, 0, data.length, opts);}public static Bitmap decodeSampledBitmapFromResource(Resources res, int id, int reqWidth, int reqHeight) {// First decode with inJustDecodeBounds=true to check dimensionsBitmapFactory.Options opts = new BitmapFactory.Options();opts.inJustDecodeBounds = true;BitmapFactory.decodeResource(res, id, opts);// Calculate inSampleSizeopts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight);// Decode bitmap with inSampleSize setopts.inJustDecodeBounds = false;return BitmapFactory.decodeResource(res, id, opts);}public static Bitmap decodeSampledBitmapFromInputStream(InputStream is, int reqWidth, int reqHeight) {Bitmap bitmap = BitmapFactory.decodeStream(is);if (bitmap == null)return null;// Log.d(TAG, "req size: " + reqWidth + ", " + reqHeight);// Log.d(TAG, "bitmap size: " + bitmap.getWidth() + ", " + bitmap.getHeight());if (bitmap.getWidth() > reqWidth || bitmap.getHeight() > reqHeight) {Size srcSize = new Size(bitmap.getWidth(), bitmap.getHeight());Size reqSize = new Size(reqWidth, reqHeight);Size newSize = calculateNewSize(srcSize, reqSize);bitmap = Bitmap.createScaledBitmap(bitmap, newSize.getWidth(), newSize.getHeight(), false);}return bitmap;}public static Size calculateNewSize(Size srcSize, Size reqSize) {int newWidth;int newHeight;if (srcSize.getWidth() > srcSize.getHeight()) {newWidth = reqSize.getWidth();newHeight = newWidth * srcSize.getHeight() / srcSize.getWidth();} else {newHeight = reqSize.getHeight();newWidth = newHeight * srcSize.getWidth() / srcSize.getHeight();}return new Size(newWidth, newHeight);}public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {// Raw height and width of imagefinal int height = options.outHeight;final int width = options.outWidth;int inSampleSize = 1;if (height > reqHeight || width > reqWidth) {final int halfHeight = height / 2;final int halfWidth = width / 2;// Calculate the largest inSampleSize value that is a power of 2 and// keeps both height and width larger than the requested height and// width.while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {inSampleSize *= 2;}}Log.d(TAG, "inSampleSize=" + inSampleSize);return inSampleSize;} }?
?
Activity類,在滑動時取消所有的任務,沒有滑動時自動加載可見范圍內的所有圖片,以避免加載時滑動產生卡頓的問題。
public class LruAcy extends AppCompatActivity {private static final String TAG = "debug";private BitmapCache mBitmapCache;private List<BitmapDownloadTask> mBitmapDownloadTasks;private GridView mImageWall;private BaseAdapter mAdapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.acy_lru);mBitmapCache = new BitmapCache();mBitmapDownloadTasks = new ArrayList<>();mImageWall = (GridView) findViewById(R.id.image_wall);mAdapter = new BaseAdapter(this);mImageWall.setOnScrollListener(mOnScrollListener);mImageWall.setAdapter(mAdapter);}@Overrideprotected void onDestroy() {super.onDestroy();cancelAll();}private void setImageView(String url, ImageView imgView) {Bitmap bitmap = mBitmapCache.get(url);if (bitmap == null) {imgView.setImageResource(R.mipmap.ic_launcher);} else {imgView.setImageBitmap(bitmap);}}private void cancelAll() {for (BitmapDownloadTask task : mBitmapDownloadTasks) {task.cancel(true);}mBitmapDownloadTasks.clear();}private void loadBitmaps(int firstVisibleItem, int visibleItemCount) {try {for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {String imageUrl = ImageUrls.IMAGE_URLS[i];Bitmap bitmap = mBitmapCache.get(imageUrl);ImageView imageView = (ImageView) mImageWall.findViewWithTag(imageUrl);if (imageView == null)continue;// Log.d(TAG, "bitmap=" + bitmap + ", imageView=" + imageView);if (bitmap == null) {BitmapDownloadTask task = new BitmapDownloadTask(mBitmapCache, mBitmapDownloadTasks, imageView,imageUrl);mBitmapDownloadTasks.add(task);task.execute();} else {imageView.setImageBitmap(bitmap);}}} catch (Exception e) {e.printStackTrace();}}private AbsListView.OnScrollListener mOnScrollListener = new AbsListView.OnScrollListener() {private boolean mIsFirstRun = true;private int firstVisibleItem;private int visibleItemCount;@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {if (scrollState == SCROLL_STATE_IDLE) {loadBitmaps(firstVisibleItem, visibleItemCount);} else {cancelAll();}}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {this.firstVisibleItem = firstVisibleItem;this.visibleItemCount = visibleItemCount;if (mIsFirstRun && totalItemCount > 0) {mIsFirstRun = false;loadBitmaps(firstVisibleItem, visibleItemCount);}}};private class BaseAdapter extends android.widget.BaseAdapter {private LayoutInflater mInflater;public BaseAdapter(Context context) {mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);}@Overridepublic int getCount() {return ImageUrls.IMAGE_URLS.length;}@Overridepublic String getItem(int position) {return ImageUrls.IMAGE_URLS[position];}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {if (convertView == null) {convertView = mInflater.inflate(R.layout.image_layout, parent, false);}String url = getItem(position);ImageView img = (ImageView) convertView.findViewById(R.id.img);img.setTag(url);setImageView(url, img);return convertView;}} }?
界面布局?acy_lru.xml?image_layout.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context="com.john.webapp.LruAcy"><GridViewandroid:id="@+id/image_wall"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:horizontalSpacing="5dp"android:numColumns="4"android:verticalSpacing="5dp"/></RelativeLayout> <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><ImageViewandroid:id="@+id/img"android:layout_width="100dp"android:layout_height="100dp"android:scaleType="fitXY"/></LinearLayout>?
測試使用的Url
public class ImageUrls {public final static String[] IMAGE_URLS = new String[]{"https://img-my.csdn.net/uploads/201309/01/1378037235_3453.jpg","https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg","https://img-my.csdn.net/uploads/201309/01/1378037235_9280.jpg","https://img-my.csdn.net/uploads/201309/01/1378037234_3539.jpg","https://img-my.csdn.net/uploads/201309/01/1378037234_6318.jpg","https://img-my.csdn.net/uploads/201309/01/1378037194_2965.jpg","https://img-my.csdn.net/uploads/201309/01/1378037193_1687.jpg","https://img-my.csdn.net/uploads/201309/01/1378037193_1286.jpg","https://img-my.csdn.net/uploads/201309/01/1378037192_8379.jpg","https://img-my.csdn.net/uploads/201309/01/1378037178_9374.jpg","https://img-my.csdn.net/uploads/201309/01/1378037177_1254.jpg","https://img-my.csdn.net/uploads/201309/01/1378037177_6203.jpg","https://img-my.csdn.net/uploads/201309/01/1378037152_6352.jpg","https://img-my.csdn.net/uploads/201309/01/1378037151_9565.jpg","https://img-my.csdn.net/uploads/201309/01/1378037151_7904.jpg","https://img-my.csdn.net/uploads/201309/01/1378037148_7104.jpg","https://img-my.csdn.net/uploads/201309/01/1378037129_8825.jpg","https://img-my.csdn.net/uploads/201309/01/1378037128_5291.jpg","https://img-my.csdn.net/uploads/201309/01/1378037128_3531.jpg","https://img-my.csdn.net/uploads/201309/01/1378037127_1085.jpg","https://img-my.csdn.net/uploads/201309/01/1378037095_7515.jpg","https://img-my.csdn.net/uploads/201309/01/1378037094_8001.jpg","https://img-my.csdn.net/uploads/201309/01/1378037093_7168.jpg","https://img-my.csdn.net/uploads/201309/01/1378037091_4950.jpg","https://img-my.csdn.net/uploads/201308/31/1377949643_6410.jpg","https://img-my.csdn.net/uploads/201308/31/1377949642_6939.jpg","https://img-my.csdn.net/uploads/201308/31/1377949630_4505.jpg","https://img-my.csdn.net/uploads/201308/31/1377949630_4593.jpg","https://img-my.csdn.net/uploads/201308/31/1377949629_7309.jpg","https://img-my.csdn.net/uploads/201308/31/1377949629_8247.jpg","https://img-my.csdn.net/uploads/201308/31/1377949615_1986.jpg","https://img-my.csdn.net/uploads/201308/31/1377949614_8482.jpg","https://img-my.csdn.net/uploads/201308/31/1377949614_3743.jpg","https://img-my.csdn.net/uploads/201308/31/1377949614_4199.jpg","https://img-my.csdn.net/uploads/201308/31/1377949599_3416.jpg","https://img-my.csdn.net/uploads/201308/31/1377949599_5269.jpg","https://img-my.csdn.net/uploads/201308/31/1377949598_7858.jpg","https://img-my.csdn.net/uploads/201308/31/1377949598_9982.jpg","https://img-my.csdn.net/uploads/201308/31/1377949578_2770.jpg","https://img-my.csdn.net/uploads/201308/31/1377949578_8744.jpg","https://img-my.csdn.net/uploads/201308/31/1377949577_5210.jpg","https://img-my.csdn.net/uploads/201308/31/1377949577_1998.jpg","https://img-my.csdn.net/uploads/201308/31/1377949482_8813.jpg","https://img-my.csdn.net/uploads/201308/31/1377949481_6577.jpg","https://img-my.csdn.net/uploads/201308/31/1377949480_4490.jpg","https://img-my.csdn.net/uploads/201308/31/1377949455_6792.jpg","https://img-my.csdn.net/uploads/201308/31/1377949455_6345.jpg","https://img-my.csdn.net/uploads/201308/31/1377949442_4553.jpg","https://img-my.csdn.net/uploads/201308/31/1377949441_8987.jpg","https://img-my.csdn.net/uploads/201308/31/1377949441_5454.jpg","https://img-my.csdn.net/uploads/201308/31/1377949454_6367.jpg","https://img-my.csdn.net/uploads/201308/31/1377949442_4562.jpg"}; }?
轉載于:https://www.cnblogs.com/diysoul/p/5597346.html
總結
以上是生活随笔為你收集整理的LruCache:从网络加载图片缓存实例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (转载)grep的使用
- 下一篇: 史上比较用心的纯代码实现 AutoLay