堆内存用于存储实例对象,当程序不断建立对象,而且对象都有引用指向,那么垃圾回收机制就不会清理这些对象,当对象多到挤满堆内存的上限后,就产生OOM异常。Android系统为每一个应用程序使用的内存设置了一个上限。这个上限值能够用下面的方法取得: long maxSize = Runtime.getRuntime().maxMemory();java
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。android
对于java中再也不使用的资源须要尽快的释放,即设置成null。
尽可能少用static方法和static成员。
对于再也不使用的bitmap应该手动调用recycle方法,而且设置成null。图片还应尽可能使用软引用方式,这样能够加快垃圾回收。web
内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache,这个类很是适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,而且把最近最少使用的对象在缓存值达到预设定值以前从内存中移除。算法
为了可以选择一个合适的缓存大小给LruCache, 有如下多个因素应该放入考虑范围内,例如:缓存
1.设备能够为每一个应用程序可分配多大的内存。
2.屏幕上一次最多须要显示多少个图片,有多少图片须要进行预加载,由于有可能很快也会显示在屏幕上。
3.屏幕大小和分辨率。一个超高分辨率的设备比起一个较低分辨率的设备,在持有相同数量图片的时候,须要更大的缓存空间。
4.图片的尺寸和大小,还有每张图片会占据多少内存空间。
5.图片被访问的频率有多高。如某些图片的访问频率比其它图片要高,应该使用多个 LruCache 对象来区分不一样组的图片。
6.维持好数量和质量之间的平衡。存储多个低像素的图片,而在后台线程加载高像素的图片会更有效。网络
并无一个指定的缓存大小能够知足全部的应用程序,应该去分析程序内存的使用状况,而后制定出一个合适的解决方案。
一个过小的缓存空间,有可能形成图片频繁地被释放和从新加载,这并无好处。而一个太大的缓存空间,则更有可能会引发 java.lang.OutOfMemory 的异常,由于 LruCache 中的强引用不能被释放,而程序又须要内存。app
使用LruCache,须要重写sizeOf方法,返回占用的内存大小。下面是一个图片缓存的实现:webapp
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/8 maxSize = maxSize >> 3; // maxSize = 1 << 1024 << 1024; Log.d(TAG, "cache used maxSize = " + toMB(maxSize)); mBitmapCache = new LruCache<String, Bitmap>((int) maxSize) { @Override protected 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大小的格式,以减小对内存的占用,并取得一个良好的显示效果。ide
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(); } @Override protected 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; } @Override protected 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; } @Override public 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; } @Override public int hashCode() { int result = width; result = 31 * result + height; return result; } @Override public 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 dimensions BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = true; BitmapFactory.decodeFile(pathName, opts); // Calculate inSampleSize opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight); // Decode bitmap with inSampleSize set opts.inJustDecodeBounds = false; return BitmapFactory.decodeFile(pathName, opts); } public static Bitmap decodeSampledBitmapFromData(byte[] data, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(data, 0, data.length, opts); // Calculate inSampleSize opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight); // Decode bitmap with inSampleSize set opts.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 dimensions BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, id, opts); // Calculate inSampleSize opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight); // Decode bitmap with inSampleSize set opts.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 image final 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; @Override protected 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); } @Override protected 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; @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == SCROLL_STATE_IDLE) { loadBitmaps(firstVisibleItem, visibleItemCount); } else { cancelAll(); } } @Override public 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); } @Override public int getCount() { return ImageUrls.IMAGE_URLS.length; } @Override public String getItem(int position) { return ImageUrls.IMAGE_URLS[position]; } @Override public long getItemId(int position) { return position; } @Override public 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"?> <RelativeLayout xmlns: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"> <GridView android: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"> <ImageView android: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[]{ "http://img.my.csdn.net/uploads/201309/01/1378037235_3453.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037235_9280.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037234_3539.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037234_6318.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037194_2965.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037193_1687.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037193_1286.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037192_8379.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037178_9374.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037177_1254.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037177_6203.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037152_6352.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037151_9565.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037151_7904.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037148_7104.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037129_8825.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037128_5291.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037128_3531.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037127_1085.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037095_7515.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037094_8001.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037093_7168.jpg", "http://img.my.csdn.net/uploads/201309/01/1378037091_4950.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949643_6410.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949642_6939.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949630_4505.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949630_4593.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949629_7309.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949629_8247.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949615_1986.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949614_8482.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949614_3743.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949614_4199.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949599_3416.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949599_5269.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949598_7858.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949598_9982.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949578_2770.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949578_8744.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949577_5210.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949577_1998.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949482_8813.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949481_6577.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949480_4490.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949455_6792.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949455_6345.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949442_4553.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949441_8987.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949441_5454.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949454_6367.jpg", "http://img.my.csdn.net/uploads/201308/31/1377949442_4562.jpg" }; }