Android Handler內(nèi)存泄漏詳解及其解決方案
關(guān)聯(lián)篇:深入Android的消息機(jī)制源碼詳解-Handler,MessageQueue與Looper關(guān)系
關(guān)聯(lián)篇:HandlerThread 使用及其源碼完全解析
在android開發(fā)過程中,我們可能會遇到過令人奔潰的OOM異常,面對這樣的異常我們是既熟悉又深惡痛絕的,因?yàn)樵斐蒓OM的原因有很多種情況,如加載圖片過大,某已不再使用的類未被GC及時(shí)回收等等......本篇我們就來分析其中一種造成OOM的場景,它就是罪惡的內(nèi)存泄漏。對于這樣的稱呼,我們并不陌生,甚至屢次與之"并肩作戰(zhàn)",只不過它就是一個豬隊(duì)友,只會不斷送塔.......
本篇分為3部分:
1.Handler內(nèi)存泄漏例子說明以及原理闡明
2.問題驗(yàn)證(如果感覺繁瑣請直接跳過)
3.Handler內(nèi)存泄漏解決方法
1.Handler內(nèi)存泄漏例子說明以及原理闡明
Handler,我們已經(jīng)相當(dāng)熟悉了,而且經(jīng)常用得不亦樂乎,但就是因?yàn)樘煜ち?,才會偶爾被它反捅一刀,血流不?.....還記得我們曾經(jīng)滿懷信心地使用著如下的優(yōu)美而又簡潔的代碼不?
不怕你嚇著,實(shí)話告訴你,這個代碼已經(jīng)造成內(nèi)存泄漏了?。。〔幌嘈牛课覀兪褂肁ndroid lint工具檢測一下該類的代碼:
面對現(xiàn)實(shí)吧,那為什么會這樣呢?在java中非靜態(tài)內(nèi)部類和匿名內(nèi)部類都會隱式持有當(dāng)前類的外部引用,由于Handler是非靜態(tài)內(nèi)部類所以其持有當(dāng)前Activity的隱式引用,如果Handler沒有被釋放,其所持有的外部引用也就是Activity也不可能被釋放,當(dāng)一個對象一句不需要再使用了,本來該被回收時(shí),而有另外一個正在使用的對象持有它的引用從而導(dǎo)致它不能被回收,這導(dǎo)致本該被回收的對象不能被回收而停留在堆內(nèi)存中,這就產(chǎn)生了內(nèi)存泄漏(上面的例子就是這個原因)。最終也就造成了OOM.......我們再來段清晰的代碼,我們來使用mHandler發(fā)送一個延遲消息:
分析:當(dāng)我們執(zhí)行了HandlerActivity的界面時(shí),被延遲的消息會在被處理之前存在于主線程消息隊(duì)列中5分鐘,而這個消息中又包含了Handler的引用,而我們創(chuàng)建的Handler又是一個匿名內(nèi)部類的實(shí)例,其持有外部HandlerActivity的引用,這將導(dǎo)致了HandlerActivity無法回收,進(jìn)行導(dǎo)致HandlerActivity持有的很多資源都無法回收,從而就造成了傳說中的內(nèi)存泄露問題!
2.問題驗(yàn)證(如果感覺繁瑣請直接跳過)
為了進(jìn)一步驗(yàn)證內(nèi)存泄漏問題,我們在該類中創(chuàng)建一個int數(shù)組,該數(shù)組分配的內(nèi)存大小為2m,然后我們用DDMS來查看heap內(nèi)存,然后使用GC回收,看看內(nèi)存會不會有變化:
第一次啟動app時(shí),head內(nèi)存為12.5M,已經(jīng)分配內(nèi)容(Allocated):8.5M,空閑內(nèi)存:4M,我們頻繁點(diǎn)擊GC按鈕,內(nèi)存并沒有發(fā)生明顯變化,現(xiàn)在我們點(diǎn)擊手機(jī)返回健,推出應(yīng)用,然后再重新進(jìn)入,同樣檢測一下head內(nèi)存:
我們發(fā)現(xiàn)head內(nèi)存:20.5M,Allocated:16.5M,Free:4M,堆內(nèi)存和已經(jīng)分配內(nèi)存近乎翻倍,我們繼續(xù)頻繁點(diǎn)擊GC, 看看能否被回收?結(jié)果內(nèi)存并沒有明顯變化,現(xiàn)在我們繼續(xù)點(diǎn)擊手機(jī)返回健,推出應(yīng)用,然后再重新進(jìn)入,同樣再次檢測一下head內(nèi)存:
我們發(fā)現(xiàn)head內(nèi)存:28.5M,Allocated:24.5M,Free:4M,堆內(nèi)存和已經(jīng)分配內(nèi)存又增加了,而且無論我們?nèi)绾吸c(diǎn)擊GC回收內(nèi)存,內(nèi)存都沒有明顯變化,而且每啟動一次該頁面,內(nèi)存就增加一倍!這也就說存在在某個類只創(chuàng)建而沒銷毀的情況,其實(shí)就是存在內(nèi)存泄漏問題。我們使用MAT工具進(jìn)一步驗(yàn)證這個問題,我們來看一組Histogram的數(shù)據(jù)和dominator tree數(shù)據(jù),首先是Histogram的數(shù)據(jù):
dominator tree數(shù)據(jù):
同時(shí)存在三個一樣的HandlerActivity和內(nèi)部類,這就足以說明HandlerActvity只有創(chuàng)建沒被銷毀了吧,也就是說Handler造成的內(nèi)存泄漏真的存在。
3.Handler內(nèi)存泄漏解決方法
解決這個問題思路就是使用靜態(tài)內(nèi)部類并繼承Handler時(shí)(或者也可以單獨(dú)存放成一個類文件)。因?yàn)殪o態(tài)的內(nèi)部類不會持有外部類的引用,所以不會導(dǎo)致外部類實(shí)例的內(nèi)存泄露。當(dāng)你需要在靜態(tài)內(nèi)部類中調(diào)用外部的Activity時(shí),我們可以使用弱引用來處理。另外關(guān)于同樣也需要將Runnable設(shè)置為靜態(tài)的成員屬性。修改后不會導(dǎo)致內(nèi)存泄露的代碼如下:
package com.zejian.handlerlooper; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import java.lang.ref.WeakReference; /** * Created by zejian on 16/3/6. */ public class HandlerActivity extends Activity { //創(chuàng)建一個2M大小的int數(shù)組 int[] datas=new int[1024*1024*2]; // Handler mHandler = new Handler(){ // @Override // public void handleMessage(Message msg) { // super.handleMessage(msg); // } // }; /** * 創(chuàng)建靜態(tài)內(nèi)部類 */ private static class MyHandler extends Handler{ //持有弱引用HandlerActivity,GC回收時(shí)會被回收掉. private final WeakReference<HandlerActivity> mActivty; public MyHandler(HandlerActivity activity){ mActivty =new WeakReference<HandlerActivity>(activity); } @Override public void handleMessage(Message msg) { HandlerActivity activity=mActivty.get(); super.handleMessage(msg); if(activity!=null){ //執(zhí)行業(yè)務(wù)邏輯 } } } private static final Runnable myRunnable = new Runnable() { @Override public void run() { //執(zhí)行我們的業(yè)務(wù)邏輯 } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler_leak); MyHandler myHandler=new MyHandler(this); //解決了內(nèi)存泄漏,延遲5分鐘后發(fā)送 myHandler.postDelayed(myRunnable, 1000 * 60 * 5); } }
Handler的內(nèi)存泄漏問題到此分析解決完成。其實(shí)產(chǎn)生內(nèi)存泄漏的還有好幾種情況,比如多線程造成的內(nèi)存泄漏,靜態(tài)變量造成的內(nèi)存泄漏,單例模式造成的內(nèi)存泄漏等等.......當(dāng)然這些不在本篇的范圍內(nèi),就不過多分析啦。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Flutter?Widget之FutureBuilder使用示例詳解
這篇文章主要為大家介紹了Flutter?Widget之FutureBuilder使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11android實(shí)現(xiàn)手機(jī)與單片機(jī)藍(lán)牙模塊通信
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)手機(jī)與單片機(jī)藍(lán)牙模塊通信的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05Android使用第三方服務(wù)器Bmob實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼
這篇文章主要介紹了Android使用第三方服務(wù)器Bmob實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼的思路詳解,需要的朋友可以參考下2016-09-09Android開發(fā)自學(xué)筆記(六):聲明權(quán)限和Activity
這篇文章主要介紹了Android開發(fā)自學(xué)筆記(六):聲明權(quán)限和Activity,本文是上一篇的補(bǔ)充,需要的朋友可以參考下2015-04-04Jetpack Compose實(shí)現(xiàn)對話框和進(jìn)度條實(shí)例解析
對話框和進(jìn)度條其實(shí)并無多大聯(lián)系,放在一起寫是因?yàn)閮烧叩膬?nèi)容都不多,所以湊到一起,對話框是我們平時(shí)開發(fā)使用得比較多的組件,進(jìn)度條的使用頻率也很高,比如下載文件,上傳文件,處理任務(wù)時(shí)都可以使用進(jìn)度條2023-04-04Flutter listview如何實(shí)現(xiàn)下拉刷新上拉加載更多功能
這篇文章主要給大家介紹了關(guān)于Flutter listview如何實(shí)現(xiàn)下拉刷新上拉加載更多功能的相關(guān)資料,對于新聞列表數(shù)據(jù)的更新和加載更多是必不可少的,而實(shí)現(xiàn)下拉刷新與上劃加載更多的方式有很多種,需要的朋友可以參考下2021-08-08Android實(shí)現(xiàn)院系專業(yè)三級聯(lián)動
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)院系專業(yè)三級聯(lián)動,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03Android自定義View實(shí)現(xiàn)微信語音界面
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)微信語音界面,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11