当前位置: 移动技术网 > 移动技术>移动开发>Android > Android编程之内存溢出解决方案(OOM)实例总结

Android编程之内存溢出解决方案(OOM)实例总结

2019年07月24日  | 移动技术网移动技术  | 我要评论

本文实例总结了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方法可以增强程序堆内存的处理效率。当然具体原理我们可以参考开源工程,这里我们仅说下使用方法: 代码如下:

复制代码 代码如下:
private final static floattarget_heap_utilization = 0.75f;

在程序oncreate时就可以调用
复制代码 代码如下:
vmruntime.getruntime().settargetheaputilization(target_heap_utilization);

即可

5:自定义我们的应用需要多大的内存,这个好暴力哇,强行设置最小内存大小,代码如下:

private final static int cwj_heap_size = 6* 1024* 1024 ;
 //设置最小heap内存为6mb大小
vmruntime.getruntime().setminimumheapsize(cwj_heap_size);

好了,文章写完了,片幅有点长,因为涉及到的东西太多了,其它文章小马都会贴源码,这篇文章小马是直接在项目中用三款安卓真机测试的,有效果,项目原码就不在这贴了,不然泄密了都,吼吼,但这里讲下还是会因为手机的不同而不同,大家得根据自己需求选择合适的方式来避免oom,大家加油呀,每天都有或多或少的收获,这也算是进步,加油加油!

希望本文所述对大家android程序设计有所帮助。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网