Android编程之内存溢出解决方案(OOM)实例总结
本文实例总结了android编程之内存溢出解决方案(oom)。分享给大家供大家参考,具体如下:
在最近做的工程中发现加载的图片太多或图片过大时经常出现oom问题,找网上资料也提供了很多方法,但自己感觉有点乱,特此,今天在不同型号的三款安卓手机上做了测试,因为有效果也有结果,今天小马就做个详细的总结,以供朋友们共同交流学习,也供自己以后在解决oom问题上有所提高,提前讲下,片幅有点长,涉及的东西太多,大家耐心看,肯定有收获的,里面的很多东西小马也是学习参考网络资料使用的,先来简单讲下下:
一般我们大家在遇到内存问题的时候常用的方式网上也有相关资料,大体如下几种:
一:在内存引用上做些处理,常用的有软引用、强化引用、弱引用
二:在内存中加载图片时直接在内存中做处理,如:边界压缩
三:动态回收内存
四:优化dalvik虚拟机的堆内存分配
五:自定义堆内存大小
可是真的有这么简单吗,就用以上方式就能解决oom了?不是的,继续来看...
下面小马就照着上面的次序来整理下解决的几种方式,数字序号与上面对应:
1:软引用(softreference)、虚引用(phantomrefrence)、弱引用(weakreference),这三个类是对heap中java对象的应用,通过这个三个类可以和gc做简单的交互,除了这三个以外还有一个是最常用的强引用
1.1:强引用,例如下面代码:
object o=new object(); object o1=o;
上面代码中第一句是在heap堆中创建新的object对象通过o引用这个对象,第二句是通过o建立o1到new object()这个heap堆中的对象的引用,这两个引用都是强引用.只要存在对heap中对象的引用,gc就不会收集该对象.如果通过如下代码:
o=null; o1=null
heap中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到达对象。应用的强弱顺序是强、软、弱、和虚。对于对象是属于哪种可及的对象,由他的最强的引用决定。如下:
string abc=new string("abc"); //1 softreference<string> abcsoftref=new softreference<string>(abc); //2 weakreference<string> abcweakref = new weakreference<string>(abc); //3 abc=null; //4 abcsoftref.clear();//5
上面的代码中:
第一行在heap对中创建内容为“abc”的对象,并建立abc到该对象的强引用,该对象是强可及的。第二行和第三行分别建立对heap中对象的软引用和弱引用,此时heap中的对象仍是强可及的。第四行之后heap中对象不再是强可及的,变成软可及的。同样第五行执行之后变成弱可及的。
1.2:软引用
软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于gc的算法和gc运行时可用内存的大小。当gc决定要收集软引用是执行以下过程,以上面的abcsoftref为例:
1 首先将abcsoftref的referent设置为null,不再引用heap中的new string("abc")对象。
2 将heap中的new string("abc")对象设置为可结束的(finalizable)。
3 当heap中的new string("abc")对象的finalize()方法被运行而且该对象占用的内存被释放, abcsoftref被添加到它的referencequeue中。
注:对referencequeue软引用和弱引用可以有可无,但是虚引用必须有,参见:
reference(t paramt, referencequeue<? super t>paramreferencequeue)
被 soft reference 指到的对象,即使没有任何 direct reference,也不会被清除。一直要到 jvm 内存不足且 没有 direct reference 时才会清除,softreference 是用来设计 object-cache 之用的。如此一来 softreference 不但可以把对象 cache 起来,也不会造成内存不足的错误 (outofmemoryerror)。我觉得 soft reference 也适合拿来实作 pooling 的技巧。
a obj = new a(); refenrence sr = new softreference(obj); //引用时 if(sr!=null){ obj = sr.get(); }else{ obj = new a(); sr = new softreference(obj); }
1.3:弱引用
当gc碰到弱可及对象,并释放abcweakref的引用,收集该对象。但是gc可能需要对此运用才能找到该弱可及对象。通过如下代码可以了明了的看出它的作用:
string abc=new string("abc"); weakreference<string> abcweakref = new weakreference<string>(abc); abc=null; system.out.println("before gc: "+abcweakref.get()); system.gc(); system.out.println("after gc: "+abcweakref.get());
运行结果:
before gc: abc
after gc: null
gc收集弱可及对象的执行过程和软可及一样,只是gc不会根据内存情况来决定是不是收集该对象。如果你希望能随时取得某对象的信息,但又不想影响此对象的垃圾收集,那么你应该用 weak reference 来记住此对象,而不是用一般的 reference。
a obj = new a(); weakreference wr = new weakreference(obj); obj = null; //等待一段时间,obj对象就会被垃圾回收 ... if (wr.get()==null) { system.out.println("obj 已经被清除了 "); } else { system.out.println("obj 尚未被清除,其信息是 "+obj.tostring()); } ... }
在此例中,透过 get() 可以取得此 reference 的所指到的对象,如果返回值为 null 的话,代表此对象已经被清除。这类的技巧,在设计 optimizer 或 debugger 这类的程序时常会用到,因为这类程序需要取得某对象的信息,但是不可以 影响此对象的垃圾收集。
1.4:虚引用
就是没有的意思,建立虚引用之后通过get方法返回结果始终为null,通过源代码你会发现,虚引用通向会把引用的对象写进referent,只是get方法返回结果为null.先看一下和gc交互的过程在说一下他的作用.
1.4.1 不把referent设置为null, 直接把heap中的new string("abc")对象设置为可结束的(finalizable).
1.4.2 与软引用和弱引用不同, 先把phantomrefrence对象添加到它的referencequeue中.然后在释放虚可及的对象.
你会发现在收集heap中的new string("abc")对象之前,你就可以做一些其他的事情.通过以下代码可以了解他的作用.
import java.lang.ref.phantomreference; import java.lang.ref.reference; import java.lang.ref.referencequeue; import java.lang.reflect.field; public class test { public static boolean isrun = true; public static void main(string[] args) throws exception { string abc = new string("abc"); system.out.println(abc.getclass() + "@" + abc.hashcode()); final referencequeue referencequeue = new referencequeue<string>(); new thread() { public void run() { while (isrun) { object o = referencequeue.poll(); if (o != null) { try { field rereferent = reference.class .getdeclaredfield("referent"); rereferent.setaccessible(true); object result = rereferent.get(o); system.out.println("gc will collect:" + result.getclass() + "@" + result.hashcode()); } catch (exception e) { e.printstacktrace(); } } } } }.start(); phantomreference<string> abcweakref = new phantomreference<string>(abc, referencequeue); abc = null; thread.currentthread().sleep(3000); system.gc(); thread.currentthread().sleep(3000); isrun = false; } }
结果为
class java.lang.string@96354
gc will collect:class java.lang.string@96354
好了,关于引用就讲到这,下面看2
2:在内存中压缩小马做了下测试,对于少量不太大的图片这种方式可行,但太多而又大的图片小马用个笨的方式就是,先在内存中压缩,再用软引用避免oom,两种方式代码如下,大家可参考下:
方式一代码如下:
@suppresswarnings("unused") private bitmap copressimage(string imgpath){ file picture = new file(imgpath); options bitmapfactoryoptions = new bitmapfactory.options(); //下面这个设置是将图片边界不可调节变为可调节 bitmapfactoryoptions.injustdecodebounds = true; bitmapfactoryoptions.insamplesize = 2; int outwidth = bitmapfactoryoptions.outwidth; int outheight = bitmapfactoryoptions.outheight; bmap = bitmapfactory.decodefile(picture.getabsolutepath(), bitmapfactoryoptions); float imagew = 150; float imageh = 150; int yratio = (int) math.ceil(bitmapfactoryoptions.outheight / imageh); int xratio = (int) math .ceil(bitmapfactoryoptions.outwidth / imagew); if (yratio > 1 || xratio > 1) { if (yratio > xratio) { bitmapfactoryoptions.insamplesize = yratio; } else { bitmapfactoryoptions.insamplesize = xratio; } } bitmapfactoryoptions.injustdecodebounds = false; bmap = bitmapfactory.decodefile(picture.getabsolutepath(), bitmapfactoryoptions); if(bmap != null){ //ivwcouponimage.setimagebitmap(bmap); return bmap; } return null; }
方式二代码如下:
package com.lvguo.scanstreet.activity; import java.io.file; import java.lang.ref.softreference; import java.util.arraylist; import java.util.hashmap; import java.util.list; import android.app.activity; import android.app.alertdialog; import android.content.context; import android.content.dialoginterface; import android.content.intent; import android.content.res.typedarray; import android.graphics.bitmap; import android.graphics.bitmapfactory; import android.graphics.bitmapfactory.options; import android.os.bundle; import android.view.view; import android.view.viewgroup; import android.view.windowmanager; import android.widget.adapterview; import android.widget.adapterview.onitemlongclicklistener; import android.widget.baseadapter; import android.widget.gallery; import android.widget.imageview; import android.widget.toast; import com.lvguo.scanstreet.r; import com.lvguo.scanstreet.data.applicationdata; /** * @title: photoscanactivity.java * @description: 照片预览控制类 * @author xiaoma */ public class photoscanactivity extends activity { private gallery gallery ; private list<string> imagelist; private list<string> it ; private imageadapter adapter ; private string path ; private string shoptype; private hashmap<string, softreference<bitmap>> imagecache = null; private bitmap bitmap = null; private softreference<bitmap> srf = null; @override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); getwindow().setflags(windowmanager.layoutparams.flag_fullscreen, windowmanager.layoutparams.flag_fullscreen); setcontentview(r.layout.photoscan); intent intent = this.getintent(); if(intent != null){ if(intent.getbundleextra("bundle") != null){ bundle bundle = intent.getbundleextra("bundle"); path = bundle.getstring("path"); shoptype = bundle.getstring("shoptype"); } } init(); } private void init(){ imagecache = new hashmap<string, softreference<bitmap>>(); gallery = (gallery)findviewbyid(r.id.gallery); imagelist = getsd(); if(imagelist.size() == 0){ toast.maketext(getapplicationcontext(), "无照片,请返回拍照后再使用预览", toast.length_short).show(); return ; } adapter = new imageadapter(this, imagelist); gallery.setadapter(adapter); gallery.setonitemlongclicklistener(longlistener); } /** * gallery长按事件操作实现 */ private onitemlongclicklistener longlistener = new onitemlongclicklistener() { @override public boolean onitemlongclick(adapterview<?> parent, view view, final int position, long id) { //此处添加长按事件删除照片实现 alertdialog.builder dialog = new alertdialog.builder(photoscanactivity.this); dialog.seticon(r.drawable.warn); dialog.settitle("删除提示"); dialog.setmessage("你确定要删除这张照片吗?"); dialog.setpositivebutton("确定", new dialoginterface.onclicklistener() { @override public void onclick(dialoginterface dialog, int which) { file file = new file(it.get(position)); boolean issuccess; if(file.exists()){ issuccess = file.delete(); if(issuccess){ imagelist.remove(position); adapter.notifydatasetchanged(); //gallery.setadapter(adapter); if(imagelist.size() == 0){ toast.maketext(getapplicationcontext(), getresources().getstring(r.string.phosizezero), toast.length_short).show(); } toast.maketext(getapplicationcontext(), getresources().getstring(r.string.phodelsuccess), toast.length_short).show(); } } } }); dialog.setnegativebutton("取消",new dialoginterface.onclicklistener() { @override public void onclick(dialoginterface dialog, int which) { dialog.dismiss(); } }); dialog.create().show(); return false; } }; /** * 获取sd卡上的所有图片文件 * @return */ private list<string> getsd() { /* 设定目前所在路径 */ file filek ; it = new arraylist<string>(); if("newadd".equals(shoptype)){ //如果是从查看本人新增列表项或商户列表项进来时 filek = new file(applicationdata.temp); }else{ //此时为纯粹新增 filek = new file(path); } file[] files = filek.listfiles(); if(files != null && files.length>0){ for(file f : files ){ if(getimagefile(f.getname())){ it.add(f.getpath()); options bitmapfactoryoptions = new bitmapfactory.options(); //下面这个设置是将图片边界不可调节变为可调节 bitmapfactoryoptions.injustdecodebounds = true; bitmapfactoryoptions.insamplesize = 5; int outwidth = bitmapfactoryoptions.outwidth; int outheight = bitmapfactoryoptions.outheight; float imagew = 150; float imageh = 150; int yratio = (int) math.ceil(bitmapfactoryoptions.outheight / imageh); int xratio = (int) math .ceil(bitmapfactoryoptions.outwidth / imagew); if (yratio > 1 || xratio > 1) { if (yratio > xratio) { bitmapfactoryoptions.insamplesize = yratio; } else { bitmapfactoryoptions.insamplesize = xratio; } } bitmapfactoryoptions.injustdecodebounds = false; bitmap = bitmapfactory.decodefile(f.getpath(), bitmapfactoryoptions); //bitmap = bitmapfactory.decodefile(f.getpath()); srf = new softreference<bitmap>(bitmap); imagecache.put(f.getname(), srf); } } } return it; } /** * 获取图片文件方法的具体实现 * @param fname * @return */ private boolean getimagefile(string fname) { boolean re; /* 取得扩展名 */ string end = fname .substring(fname.lastindexof(".") + 1, fname.length()) .tolowercase(); /* 按扩展名的类型决定mimetype */ if (end.equals("jpg") || end.equals("gif") || end.equals("png") || end.equals("jpeg") || end.equals("bmp")) { re = true; } else { re = false; } return re; } public class imageadapter extends baseadapter{ /* 声明变量 */ int mgalleryitembackground; private context mcontext; private list<string> lis; /* imageadapter的构造符 */ public imageadapter(context c, list<string> li) { mcontext = c; lis = li; typedarray a = obtainstyledattributes(r.styleable.gallery); mgalleryitembackground = a.getresourceid(r.styleable.gallery_android_galleryitembackground, 0); a.recycle(); } /* 几定要重写的方法getcount,传回图片数目 */ public int getcount() { return lis.size(); } /* 一定要重写的方法getitem,传回position */ public object getitem(int position) { return lis.get(position); } /* 一定要重写的方法getitemid,传并position */ public long getitemid(int position) { return position; } /* 几定要重写的方法getview,传并几view对象 */ public view getview(int position, view convertview, viewgroup parent) { system.out.println("lis:"+lis); file file = new file(it.get(position)); softreference<bitmap> srf = imagecache.get(file.getname()); bitmap bit = srf.get(); imageview i = new imageview(mcontext); i.setimagebitmap(bit); i.setscaletype(imageview.scaletype.fit_xy); i.setlayoutparams( new gallery.layoutparams(windowmanager.layoutparams.wrap_content, windowmanager.layoutparams.wrap_content)); return i; } } }
上面两种方式第一种直接使用边界压缩,第二种在使用边界压缩的情况下间接的使用了软引用来避免oom,但大家都知道,这些函数在完成decode后,最终都是通过java层的createbitmap来完成的,需要消耗更多内存,如果图片多且大,这种方式还是会引用oom异常的,不着急,有的是办法解决,继续看,以下方式也大有妙用的:
1.
inputstream is = this.getresources().openrawresource(r.drawable.pic1); bitmapfactory.options options=new bitmapfactory.options(); options.injustdecodebounds = false; options.insamplesize = 10; //width,hight设为原来的十分一 bitmap btp =bitmapfactory.decodestream(is,null,options);
2.
if(!bmp.isrecycle() ){ bmp.recycle() //回收图片所占的内存 system.gc() //提醒系统及时回收 }
上面代码与下面代码大家可分开使用,也可有效缓解内存问题哦...吼吼...
/** 这个地方大家别搞混了,为了方便小马把两个贴一起了,使用的时候记得分开使用 * 以最省内存的方式读取本地资源的图片 */ public static bitmap readbitmap(context context, int resid){ bitmapfactory.options opt = new bitmapfactory.options(); opt.inpreferredconfig = bitmap.config.rgb_565; opt.inpurgeable = true; opt.ininputshareable = true; //获取资源图片 inputstream is = context.getresources().openrawresource(resid); return bitmapfactory.decodestream(is,null,opt); }
3:大家可以选择在合适的地方使用以下代码动态并自行显式调用gc来回收内存:
if(bitmapobject.isrecycled()==false) //如果没有回收 bitmapobject.recycle();
4:这个就好玩了,优化dalvik虚拟机的堆内存分配,听着很强大,来看下具体是怎么一回事
对于android平台来说,其托管层使用的dalvik javavm从目前的表现来看还有很多地方可以优化处理,比如我们在开发一些大型游戏或耗资源的应用中可能考虑手动干涉gc处理,使用 dalvik.system.vmruntime类提供的settargetheaputilization方法可以增强程序堆内存的处理效率。当然具体原理我们可以参考开源工程,这里我们仅说下使用方法: 代码如下:
在程序oncreate时就可以调用
即可
5:自定义我们的应用需要多大的内存,这个好暴力哇,强行设置最小内存大小,代码如下:
private final static int cwj_heap_size = 6* 1024* 1024 ; //设置最小heap内存为6mb大小 vmruntime.getruntime().setminimumheapsize(cwj_heap_size);
好了,文章写完了,片幅有点长,因为涉及到的东西太多了,其它文章小马都会贴源码,这篇文章小马是直接在项目中用三款安卓真机测试的,有效果,项目原码就不在这贴了,不然泄密了都,吼吼,但这里讲下还是会因为手机的不同而不同,大家得根据自己需求选择合适的方式来避免oom,大家加油呀,每天都有或多或少的收获,这也算是进步,加油加油!
希望本文所述对大家android程序设计有所帮助。