前言
前面总结了下内存泄露,一般情况下,内存泄露最后都会导致内存溢出,现在说说如何防止内存溢出,关于内存泄露和检测以及定位请参考上一篇文章。
进入主题:
这里总结下常见的出现内存溢出的情况和其解决方法。
Android使用基于寄存器的Dalvik虚拟机,每个android应用单独使用一个Dalvik虚拟机,每个虚拟机使用的堆内存是有限的,超过了限制就会引发OOM错误,进而可能出现应用卡顿,崩溃。
一般来说,出现OOM,有以下几种常见情况:
1.加载对象过大(比如:大图)。
2.相应资源过多,没有来不及释放(比如:大量图片)。
3.其他。
解决这样的问题,也有以下几种:
1.针对1,可以使用图片的边界压缩算法进行减小图片大小,进而减小图片所占内存大小,或者在不影响画质的情况下可以对图片的分辨率进行设置。
2.正对情况2,首先利用多线程多任务队列分页加载图片,在显示图片之前根据需求看是否可以改变图片的大小和画质来减小所占内存的大小(记住:尽量优化ListView,莫在onCreateView中进行耗时操作,否则可能或出现卡顿的情况),然后图片显示完在根据需求可以手动回收内存,最后,为了提升下载显示图片的效率,可以设置三级缓存:一级内存缓存,可以使用软引用(Android5.0已经不建议使用软引用了);二级文件缓存,这里可以对图片进行加密处理,最常见的就是MD5加密,文件缓存最好设置下时间,因为大多情况下不可能需要一直缓存;三级网络缓存。(在实际应用中根据需求使用三级缓存
附加:对于缓存工具,一般网络框架会自带,比如:Volley (Android6.0已经嫌弃了他) ,OkHttp,NoHttp等;图片加载框架,比如:ImageLoader,Fresco等;纯缓存工具,比如:ASimpleCache等。
3.优化Dalvik虚拟机的堆内存分配。
1.高效加载大图
i.边界压缩,官方推荐的图片压缩算法如下所示:
package com.ysj.bitmap.utils;
import java.io.InputStream;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
public class BitmapCompressUtil {
/**
* 图的压缩
*
* @param res
* @param resId
* @param reqWidth
* @param reqHeight
* @return
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res,
InputStream is, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
// 这将通知BitmapFactory类只须返回该图像的范围,而无须尝试解码图像本身。
// 目的就是让解析方法禁止为bitmap分配内存
// 返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。
options.inJustDecodeBounds = true;
if (BitmapFactory.decodeStream(is, null, options) != null) {
System.out.println("BitMap为null");
}
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeStream(is, null, options);
}
/**
* 计算压缩图片的尺寸
*
* @param options
* @param reqWidth
* @param reqHeight
* @return
*/
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
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;
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
System.out.println(inSampleSize);
return inSampleSize;
}
}
ii.采用低内存占用量的编码方式,比如Bitmap.Config.ARGB_4444比Bitmap.Config.ARGB_8888更省内存。
2.多图解决方案
i.缓存。Imageload,Lur等缓存框架。
主要关注设置缓存大小和缓存线程池的大小。
ii.及时回收图像,如果引用了大量的Bitmap对象,而应用又不需要同时显示所有图片。可以将暂时不用到的Bitmap对象及时回收掉,对于一些明确直到图片使用情况的场景可以主动recycle回收,或者使用软引用。
if (!bitmap.isRecycled()) {
bitmap.recycle(); // 目的来使它尽早被GC,
System.gc(); // 提醒系统及时回收
bitmap = null;
}
关于bitmap.recycle(),API文档是这样解释的:
/**
* Free the native object associated with this bitmap, and clear the
* reference to the pixel data. This will not free the pixel data
* synchronously; 释放本地对象相关联的对象,并且清除引用的像素数据,不会同步的去释放像素数据(bitmap不为null);
* it simply allows it to be garbage collected if there are no other
* references. 它只允许在没有其他引用指向它的时候被当做垃圾回收。 The bitmap is marked as "dead",
* meaning it will throw an exception if getPixels() or setPixels() is
* called,and will draw nothing. 这个bitmap对象被标记为"死亡",意味着如果调用getPixels()
* or setPixels()将会抛出一个异常,并且不会绘画任何东西 This operation cannot be
* reversed,so it should only be called if you are sure there are no
* further uses for the bitmap.
* 这个操作不能够被调用,因此仅当如果你确定这里不再去使用bitmap对象的时候才去调用这个操作。 This is an advanced
* call, and normally need not be called, since the normal GC process
* will free up this memory when there are no more references to this
* bitmap. 这是一个有利的调用,通常不需要我们自己去调用,因为正常的GC过程会释放该内存当这里引用这个位图对象。
*/
参考网址:http://mobile.51cto.com/abased-406980.htm
3.不恰当的使用static关键字。静态变量时属于类而不是对象,所以使用static保存大资源(比如Context)非常容易造成OOM。
解决方案:
尽量不要使用static 变量保存大资源(比如Context等),使用软应用,使用application Context。
4.内部类线程引发OOM。线程的生命周期不可控,在线程没有完成run方法之前线程不会被销毁,同时由于采用内部类产生的线程同时用保存Activity的引用。同理使用Activity的内部类Asyncatask也容易造成OOM。
解决方案:
将内部类线程改为静态内部类,在线程内部使用弱引用保存Context引用,解决模型如下:
public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget> extends
AsyncTask<Params, Progress, Result> {
protected WeakReference<WeakTarget> mTarget;
public WeakAsyncTask(WeakTarget target) {
mTarget = new WeakReference<WeakTarget>(target);
}
@Override
protected final void onPreExecute() {
final WeakTarget target = mTarget.get();
if (target != null) {
this.onPreExecute(target);
}
}
@Override
protected final Result doInBackground(Params... params) {
final WeakTarget target = mTarget.get();
if (target != null) {
return this.doInBackground(target, params);
} else {
return null;
}
}
@Override
protected final void onPostExecute(Result result) {
final WeakTarget target = mTarget.get();
if (target != null) {
this.onPostExecute(target, result);
}
}
protected void onPreExecute(WeakTarget target) {
// No default action
}
protected abstract Result doInBackground(WeakTarget target, Params... params);
protected void onPostExecute(WeakTarget target, Result result) {
// No default action
}
}
5.游标Cursor的使用。
使用游标cursor完毕应该及时关闭cursor,使用cursorAdapter的情况下应该在Activity的ondestory中调用cursor的close方法。6.对于需要创建大量对象而且,最好创建一个对象池,好重复利用。
。。。。。。
最后的最后
还有很多情况下的OOM没有总结,以后会补上,更多内容请见