常見的內存泄漏場景分析

博主最近遇到了很多內存泄漏的問題,其實說白了,在Android裏面的內存泄漏最多的就是activity或者fragment對象,
當他們執行了ondestory週期函數之後,內存當中的對象卻得不到釋放,因而造成了內存泄漏。
以下是常見的幾種容易造成內存泄漏的場景。

1 匿名內部類

匿名內部類非常常見,特別是一些回調函數經常會使用。匿名內部類會持有外部類的引用,如果在activity當中使用,就得注意是否可能會超出activity的生命週期。
例子
例如以下的一段代碼,Runnable就是一個匿名內部類,在其內部持有了外部類的引用,若當Activity執行了ondestroy週期函數之後,Runnable還在排隊或者還未執行完成,就會因爲Runnable持有Activity引用,而使得Activity無法得到釋放,造成內存泄漏。

ThreadPool.execute(new Runnable() {
    @Override
    public void run() {
        //持有外部類的引用
    }
});

改進
可以改成靜態變量

private static Runnable myRunnable = new Runnable() {
    @Override
    public void run() {
        //不持有Activity引用
    }
};

ThreadPool.execute(myRunnable);

或者改成靜態內部類

static class MyRunnable implements Runnable{

    @Override
    public void run() {
        //不持有Activity引用,或者可以通過參數的方式傳遞一個activity的弱飲用
    }
}

ThreadPool.execute(new MyRunnable());

2 非靜態內部類

非靜態內部類會隱試持有外部類,若超過了activity的生命週期,就會造成泄露
例子
MyRunnable是一個非靜態內部類,會持有外部類引用,若將其放在activity內部,則會持有activity引用,會有內存泄漏的風險。

class MyRunnable implements Runnable{

    @Override
    public void run() {
        //持有Activity引用
    }
}

ThreadPool.execute(new MyRunnable());

改進
參照匿名內部類的改進方法,將其改成靜態內部類

3 單例或者靜態變量持有

單例和靜態變量持有其實是一回事,單例模式也是一個靜態變量。
例子
如下所示是一個很常見的單例模式,有時候activity需要回調函數來更新界面,例如網絡訪問,圖片下載等場景,就有類似addCallBack的函數,當網絡數據返回時,再執行callback函數。若callback持有了activity,而list持有callback,instance持有list,這樣就形成了一條引用鏈。
當activity已經執行完ondestroy之後,instance仍舊持有這一條引用鏈,就會造成內存泄漏。

public class TestSingle {

    private static volatile TestSingle instance;

    private List<CallBack> list;

    private TestSingle() {
        list = new ArrayList<>();
    }

    public static TestSingle getInstance() {
        if (instance == null) {
            synchronized (TestSingle.class) {
                if (instance == null) {
                    instance = new TestSingle();
                }
            }
        }
        return instance;
    }

    public void addCallBack(CallBack callBack) {
        list.add(callBack);
    }

    public void removeCallBack(Callback callback) {
        list.remove(callback);
    }

}

改進
在週期函數裏面執行removeCallBack函數,切斷引用鏈

TestSingle.getInstance().removeCallBack(callBack);

5 Handler

如以下例子所示,activity可能已經被銷燬,但是handler當中仍然有未被執行的runnable
例子

public class TestActivity extends Activity {


    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //message持有了activity飲用
            }
        },5000);
    }
    
}

改進
在ondestroy裏面清空所有的message

handler.removeCallbacksAndMessages(null);

或者將Runnable做成靜態內部類,弱引用外部activity

6 webview泄露

webview佔用內存較大,產生內存泄漏也不好排查,這裏不去深究造成內存泄漏的原因,只提供一種解決思路,爲webview單獨開闢一個進程,當使用結束之後結束進程,這樣就可以避免webview造成內存泄漏了。
例子

public class WebviewActivity extends Activity {

    private WebView mWebView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mWebView = (WebView) findViewById(R.id.test_webview);
        mWebView.loadUrl("www.baidu.com");
    }

    protected void destroyWebview(){
        if(mWebView != null){
            mWebView.pauseTimers();
            mWebView.removeAllViews();
            mWebView.destroy();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        destroyWebview();
        android.os.Process.killProcess(Process.myPid());
    }
}

總結

這裏介紹了常見的容易造成內存泄漏的場景,還有一些場景如Asynctask或者HandlerThread其實都能在這當中能夠找到對應的類型,就不再單獨分析了。

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