1. 程式人生 > >淺談 Android 記憶體溢位與記憶體洩漏

淺談 Android 記憶體溢位與記憶體洩漏

概念

  • 記憶體溢位(Out of memory):系統會給每個APP分配記憶體,預設16M記憶體,每個手機廠商的預設值不一樣,當APP所需要的記憶體大於了系統分配的記憶體,就會造成記憶體溢位;記憶體溢位就是分配的記憶體被用光了,不夠用了。

  • 記憶體洩漏(Memory leak):當一個物件不再使用了,本應該被垃圾回收器(GC)回收,但是這個物件由於被其他正在使用的物件所持有,造成無法被回收,導致一部分記憶體一直被佔著。

  • 記憶體洩漏和記憶體溢位的關係:記憶體洩露過多會導致記憶體溢位。記憶體洩露導致一部分記憶體沒有被回收,一直被佔著,可利用記憶體變少了,當洩露過多 時,可利用的記憶體越來越少,就會引起記憶體溢位了。

原因及優化

記憶體溢位

  • Bitmap使用不當 1、使用Bitmap物件要用recycle釋放 2、使用軟引用、弱引用 3、使用圖片快取技術(例如:LruCache)

記憶體洩漏

  • 在記憶體比較低的系統上對大量的圖片、音訊、視訊處理 優化: 建議使用第三方,或者JNI來進行處理

  • Handler使用不當 例如: 在日常開發中我們通常都是這樣定義Handler物件:

Handler handler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {  
            dosomething();  

        }  
    };

但是這樣也存在著一個隱藏的問題:在Activity中使用Handler建立匿名內部類會隱式的持有外部Activity物件的引用,當子執行緒使用Handler暫時無法完成非同步任務時,handler物件無法銷燬,同時由於隱式的持有activity物件的引用,造成activity物件以及相關的元件與資原始檔同樣無法銷燬,造成記憶體洩露。我們普通的Handler,平時使用時也是可以不用考慮這些的,但是當你在子執行緒中有耗時操作通知UI時,要考慮到這一點,以免activity結束時子執行緒持有Handler物件,導致Activity無法被釋放。 優化解決方法: 1)使用靜態變數定義Handler

static Handler handler = new Handler() {  

        @Override  
        public void handleMessage(Message msg) {  
            dosomething();  

        }  
    };  

這樣的話,handler物件由於是靜態型別無法持有外部activity的引用,但是這樣Handler不再持有外部類的引用,導致程式不允許在Handler中操作activity中的物件了,這時候我們需要在Handler物件中增加一個隊activity的弱引用;

2)Handler使用弱引用

static class MyHandler extends Handler {  
    WeakReference<Activity > mActivityReference;  
    MyHandler(Activity activity) {  
        mActivityReference= new WeakReference<Activity>(activity);  
    }  
    @Override  
    public void handleMessage(Message msg) {  
        final Activity activity = mActivityReference.get();  
        if (activity != null) {  
            mImageView.setImageBitmap(mBitmap);  
        }  
    }  
} 

最後在ondestory方法中將後面的訊息移除 在activity執行onDestory時,判斷是否有handler已經執行完成,否則做相關邏輯操作;

mhanHandler.removeCallbacksAndMessages(null);
  • Context物件使用不當 例如自定義單例物件
public class Single {
    private static Single instance;
    private Context context;
    private Object obj = new Object();

    private Single(Context context) {
        this.context = context;
    }

    public static Single getInstance(Context context) {
        if (instance == null) {
            synchronized(obj) {
                if (instance == null) {
                    instance = new Single(context);
                }
            }
        }
        return instance;
    }
}

優化解決方法:使用全域性的的Context或者context.getApplication(); 單列模式應該儘量少持有生命週期不同的外部物件,一旦持有該物件的時候,必須在該物件的生命週期結束前制null

  • 非靜態內部類,靜態例項化 static關鍵字修飾的變數由於生命週期過長,容易造成記憶體洩漏 例如:
public class MyActivity extends AppCompatActivity {
    /** 靜態成員變數*/
    public static InnerClass innerClass = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);

        innerClass = new InnerClass();
    }

    class InnerClass {

        public void test() {
            //TODO ...
        }
    }
}

這裡內部類InnerClass隱式的持有外部類MyActivity的引用,而在MyActivity的onCreate方法中呼叫了 innerClass = new InnerClass();這樣innerClass就會在MyActivity建立的時候是有了他的引用,而innerClass是靜態型別的不會被垃圾回收,MyActivity在執行onDestory方法的時候由於被innerClass持有了引用而無法被回收,所以這樣MyActivity就總是被innerClass持有而無法回收造成記憶體洩露。

優化解決方法:儘量少使用靜態變數,一定要使用要及時進行制null處理

  • 屬性動畫或迴圈動畫使用不當 優化解決方法:在Activity中使用了屬性迴圈動畫,在onDestroy()方法中未正確停止動畫

  • BraodcastReceiver、File、Cursor等資源的使用未及時關閉 優化解決方法:在銷燬activity時,應該及時銷燬或者回收

  • 框架使用了註冊方法而未反註冊 例如: 使用的事件匯流排框架-EventBus,當我們需要註冊某個Activity時需要在onCreate中:

EventBus.getDefault().register(this);

然後這樣之後就沒有其他操作的話就會出現記憶體洩露的情況,因為EventBus物件會是有該Activity的引用,即使執行了改Activity的onDestory方法,由於被EventBus隱式的持有了該物件的引用,造成其無法被回收。 優化解決方法:需要在onDestory方法中執行反註冊:

EventBus.getDefault().unregister(this);