Android 內存溢出解決方案(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 abcSoftRef=new SoftReference(abc); //2

WeakReference abcWeakRef = new WeakReference(abc); //3

abc=null; //4

abcSoftRef.clear();//5

上面的代碼中:
第一行在heap對中創建內容為“abc”的對象,並建立abc到該對象的強引用,該對象是強可及的。第二行和第三行分別建立對heap中對象的軟引用和弱引用,此時heap中的對象仍是強可及的。第四行之後heap中對象不再是強可及的,變成軟可及的。同樣第五行執行之後變成弱可及的。
1.2:軟引用
軟引用是主要用於內存敏感的高速緩存。在jvm報告內存不足之前會清除所有的軟引用,這樣以來gc就有可能收集軟可及的對象,可能解決內存吃緊問題,避免內存溢出。什麼時候會被收集取決於gc的算法和gc運行時可用內存的大小。當gc決定要收集軟引用是執行以下過程,以上面的abcSoftRef為例:

1
2
3
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, ReferenceQueueparamReferenceQueue)

被 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 abcWeakRef = new WeakReference(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();

1
2
3
4
5
WeakReference wr = new WeakReference(obj);    

obj = null;    

//等待一段時間,obj對象就會被垃圾回收   

  …

  if (wr.get()==null) {

  System.out.println(obj 已經被清除瞭 );

  } else {

  System.out.println(obj 尚未被清除,其信息是 obj.toString());

  }

  …

}

1
2
3
4
5
6
7
在此例中,透過 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;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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();       
    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 abcWeakRef = new PhantomReference(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;
}

1
2
3
4
5
6
7
8
9
}  
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 ImageList;
private List it ;
private ImageAdapter adapter ;

private String path ;
private String shopType;
private HashMap> imageCache = null;
private Bitmap bitmap = null;
private SoftReference srf = null;

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
@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>(); 
     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 getSD() { 
    /* 設定目前所在路徑 */ 
    File fileK ; 
    it = new ArrayList(); 
    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); 
                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 lis; 

    /* ImageAdapter的構造符 */ 
    public ImageAdapter(Context c, List 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 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() //提醒系統及時回收
}
上面代碼與下面代碼大傢可分開使用,也可有效緩解內存問題哦…吼吼…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/** 這個地方大傢別搞混瞭,為瞭方便小馬把兩個貼一起瞭,使用的時候記得分開使用
 * 以最省內存的方式讀取本地資源的圖片 
 */   
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);

說明:以上的幾種方法中,可能最常用的是第二種方法,其他的幾種不是很常用,如果大傢有什麼問題可以聯系原文可能會有更多的信息。

 

 

發佈留言