Android內(nèi)存泄漏原因及解決案例

造成 Android 內(nèi)存泄漏的最根本原因就是生命周期較長(zhǎng)的對(duì)象持有生命周期較短的對(duì)象的引用。

1.單例造成的內(nèi)存泄漏

如果一個(gè) Activity 被一個(gè)單例對(duì)象所引用,那么當(dāng)退出這個(gè) Activity 時(shí),由于單例的對(duì)象依然存在(單例對(duì)象的生命周期跟整個(gè) App 的生命周期一致),而單例對(duì)象又持有 Activity 的引用,這就導(dǎo)致了此 Activity 無(wú)法被回收,從而造成內(nèi)存泄漏。

public class SingleTon { 

    private static SingleTon singleTon; 
    private Context context; 
    private SingleTon(Context context) { 
        this.context = context; 
    } 

    public static SingleTon getInstance(Context context) { 
        if (singleTon == null) { 
            synchronized (SingleTon.class) { 
                if (singleTon == null) { 
                    singleTon = new SingleTon(context); 
                } 
            } 
        } 
        return singleTon; 
    } 
} 

這是單例模式餓漢式的雙重校驗(yàn)鎖的寫(xiě)法,這里的 singleTon 持有 Context 對(duì)象,如果 Activity 中調(diào)用 getInstance 方法并傳入 this 時(shí),singleTon 就持有了此 Activity 的引用,當(dāng)退出 Activity 時(shí),Activity 就無(wú)法回收,造成內(nèi)存泄漏。

解決方法:
private SingleTon(Context context) { 
    this.context = context.getApplicationContext(); 
}

通過(guò) getApplicationContext 來(lái)獲取 Application 的 Context,讓它被單例持有,這樣退出 Activity 時(shí),Activity 對(duì)象就能正常被回收了,而 Application 的 Context 的生命周期和單例的生命周期是一致的,所有再整個(gè) App 運(yùn)行過(guò)程中都不會(huì)造成內(nèi)存泄漏。

2.Handler 或 Runnable 作為非靜態(tài)內(nèi)部類

handler 和 runnable 都有定時(shí)器的功能,當(dāng)它們作為非靜態(tài)內(nèi)部類的時(shí)候,同樣會(huì)持有外部類的引用,如果它們的內(nèi)部有延遲操作,在延遲操作還沒(méi)有發(fā)生的時(shí)候,銷毀了外部類,那么外部類對(duì)象無(wú)法回收,從而造成內(nèi)存泄漏。

public class MainActivity extends AppCompatActivity {  

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

        new Handler().postDelayed(new Runnable() {  
            @Override  
            public void run() {  
            }  
        }, 10 * 1000);  
    }  
}

上面的代碼中,Handler 和 Runnable 作為匿名內(nèi)部類,都會(huì)持有 MainActivity 的引用,而它們內(nèi)部有一個(gè) 10 秒鐘的定時(shí)器,如果在打開(kāi) MainActivity 的 10 秒內(nèi)關(guān)閉了 MainActivity,那么由于 Handler 和 Runnable 的生命周期比 MainActivity 長(zhǎng),會(huì)導(dǎo)致 MainActivity 無(wú)法被回收,從而造成內(nèi)存泄漏。

解決方法:

那么應(yīng)該如何避免內(nèi)存泄漏呢?這里的一般套路就是把 Handler 和 Runnable 定義為靜態(tài)內(nèi)部類,這樣它們就不再持有 MainActivity 的引用了,從而避免了內(nèi)存泄漏:

public class MainActivity extends AppCompatActivity {  

    private Handler handler;  
    private Runnable runnable;  

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

        handler = new TestHandler();  
        runnable = new TestRunnable();  
        handler.postDelayed(runnable, 10 * 1000);  
    }  

    private static class TestHandler extends Handler {  
    }  

    private static class TestRunnable implements Runnable {  
        @Override  
        public void run() {  
        }  
    }  
}

最好再在 onDestory 調(diào)用 handler 的 removeCallbacks 方法來(lái)移除 Message,這樣不但能避免內(nèi)存泄漏,而且在退出 Activity 時(shí)取消了定時(shí)器,保證 10 秒以后也不會(huì)執(zhí)行 run 方法:

@Override  
protected void onDestroy() {  
    super.onDestroy();  
    handler.removeCallbacks(runnable);  
}

還有一種特殊情況,如果 Handler 或者 Runnable 中持有 Context 對(duì)象,那么即使使用靜態(tài)內(nèi)部類,還是會(huì)發(fā)生內(nèi)存泄漏:

public class MainActivity extends AppCompatActivity { 

    private Handler handler; 
    private Runnable runnable; 

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

        handler = new TestHandler(this); 
        runnable = new TestRunnable(); 
        handler.postDelayed(runnable, 10 * 1000); 
    } 

    private static class TestHandler extends Handler { 
        private Context context; 
        private TestHandler(Context context) { 
            this.context = context; 
        } 
    } 

    private static class TestRunnable implements Runnable { 
        @Override 
        public void run() { 
        } 
    } 
}

上面的代碼,使用 leakcanary 工具會(huì)發(fā)現(xiàn)依然會(huì)發(fā)生內(nèi)存泄漏,而且造成內(nèi)存泄漏的原因和之前用非靜態(tài)內(nèi)部類是一樣的,那么為什么會(huì)出現(xiàn)這種情況呢?

解決方法:

這是由于在 Handler 中持有 Context 對(duì)象,而這個(gè) Context 對(duì)象是通過(guò) TestHandler 的構(gòu)造方法傳入的,它是一個(gè) MainActivity 對(duì)象,也就是說(shuō),雖然 TestHandler 作為靜態(tài)內(nèi)部類不會(huì)持有外部類 MainActivity 的引用,但是我們?cè)谡{(diào)用它的構(gòu)造方法時(shí),自己傳入了 MainActivity 的對(duì)象,從而 handler 對(duì)象持有了 MainActivity 的引用,handler 的生命周期比 MainActivity 的生命周期長(zhǎng),因此會(huì)造成內(nèi)存泄漏,這種情況可以使用弱引用的方式來(lái)引用 Context 來(lái)避免內(nèi)存泄漏,代碼如下:

public class MainActivity extends AppCompatActivity { 

    private Handler handler; 
    private Runnable runnable; 

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

        handler = new TestHandler(new WeakReference<Context>(this)); 
        runnable = new TestRunnable(); 
        handler.postDelayed(runnable, 10 * 1000); 
    } 

    private static class TestHandler extends Handler { 
        private Context context; 
        private TestHandler(WeakReference<Context> weakContext) { 
            context = weakContext.get(); 
        } 
    } 

    private static class TestRunnable implements Runnable { 
        @Override 
        public void run() { 
        } 
    } 
} 
3.外部類中持有非靜態(tài)內(nèi)部類的靜態(tài)對(duì)象
public class MainActivity extends AppCompatActivity { 

    private static Test test; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
        if (test == null) { 
            test = new Test(); 
        } 
    } 

    private class Test { 
    } 
}
解決方法:

這個(gè)其實(shí)和單例的原理是一樣的,由于靜態(tài)對(duì)象 test 的生命周期和整個(gè)應(yīng)用的生命周期一致,而非靜態(tài)內(nèi)部類 Test 持有外部類 MainActivity 的引用,導(dǎo)致 MainActivity 退出的時(shí)候不能被回收,從而造成內(nèi)存泄漏,解決的方法也很簡(jiǎn)單,把 test 改成非靜態(tài),這樣 test 的生命周期和 MainActivity 是一樣的了,就避免了內(nèi)存泄漏?;蛘咭部梢园?Test 改成靜態(tài)內(nèi)部類,讓 test 不持有 MainActivity 的引用,不過(guò)一般沒(méi)有這種操作。

4.非靜態(tài)內(nèi)部類造成的內(nèi)存泄漏

非靜態(tài)內(nèi)部類會(huì)持有外部類的引用,如果這個(gè)非靜態(tài)的內(nèi)部類的生命周期比它的外部類的生命周期長(zhǎng),那么當(dāng)銷毀外部類的時(shí)候,它無(wú)法被回收,就會(huì)造成內(nèi)存泄漏。

解決方法:

1.通過(guò)靜態(tài)內(nèi)部類來(lái)去除隱式引用
2.修改靜態(tài)內(nèi)部類的構(gòu)造方式,手動(dòng)引入其外部類引用
3.當(dāng)內(nèi)存不可用時(shí),不執(zhí)行不可控代碼(Android可以結(jié)合智能指針,WeakReference包裹外部類實(shí)例)
(注意:并不是所有的內(nèi)部類只能使用靜態(tài)內(nèi)部類,只有在該內(nèi)部類中的生命周期不可控制的情況下,我們要采用靜態(tài)內(nèi)部類,其它時(shí)候大家可以照舊。)

5.其他內(nèi)存泄漏情況

還有一些其他的會(huì)導(dǎo)致內(nèi)存泄漏的情況,比如 BraodcastReceiver 未取消注冊(cè),InputStream 未關(guān)閉等,平時(shí)寫(xiě)代碼時(shí)多多注意即可避免。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏...
    _痞子閱讀 1,700評(píng)論 0 8
  • 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏大家都不陌生了,簡(jiǎn)單粗俗的講,...
    宇宙只有巴掌大閱讀 2,488評(píng)論 0 12
  • 前言 之前研究過(guò)一段時(shí)間關(guān)于 Android 內(nèi)存泄漏的知識(shí),大致了解了導(dǎo)致內(nèi)存泄漏的一些原因,但是沒(méi)有深入去探究...
    Zackratos閱讀 19,607評(píng)論 19 49
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏...
    apkcore閱讀 1,305評(píng)論 2 7
  • 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏大家都不陌生了,簡(jiǎn)單粗俗的講,...
    DreamFish閱讀 868評(píng)論 0 5

友情鏈接更多精彩內(nèi)容