淺談 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);

參考文章
https://blog.csdn.net/qq_28607155/article/details/76148989
https://blog.csdn.net/u014674558/article/details/62234008
https://blog.csdn.net/guolin_blog/article/details/9316683

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章