Android常見內(nèi)存泄漏以及優(yōu)化方案

單例導(dǎo)致內(nèi)存泄漏

首先來看一下一種單例的寫法:多種單例寫法參考

public class Utils{

private static Utils mInstance;

private Context mContext;

private Utils(Context context){

this.mContext = context;

}

public static Utils getInstance(Context context){

if(mInstance == null){

Util = new Utils(context);

}

return mInstance;

}

}

以Activity為例,當(dāng)我們啟動一個Activity,并調(diào)用getInstance(Context context)Utils绣硝,傳入Activity.this作為context躺同,這樣Utils類的單例mInstance就持有了Activity的引用老赤,當(dāng)我們退出Activity時臼膏,該Activity就沒有用了浦旱,但是因為mIntance作為靜態(tài)單例(在應(yīng)用程序的整個生命周期中存在)會繼續(xù)持有這個Activity的引用直奋,導(dǎo)致這個Activity對象無法被回收釋放能庆,這就造成了內(nèi)存泄露。

為了避免這樣單例導(dǎo)致內(nèi)存泄露脚线,我們可以將context參數(shù)改為全局的上下文:

java private Utils(Context context){ this.mContext=context.getApplicationContext(); }

全局的上下文Application Context就是應(yīng)用程序的上下文搁胆,和單例的生命周期一樣長,這樣就避免了內(nèi)存泄漏邮绿。單例模式對應(yīng)應(yīng)用程序的生命周期渠旁,所以我們在構(gòu)造單例的時候盡量避免使用Activity的上下文,而是使用Application的上下文船逮。

非靜態(tài)內(nèi)部類導(dǎo)致內(nèi)存泄漏

我們都知道內(nèi)部類會持有外部類引用顾腊。 原因通過編譯查看.class文件可知,編譯器會為內(nèi)部類構(gòu)造傳入外部類實例.所以非靜態(tài)內(nèi)部類會持有外部類引用挖胃。

這樣在特定情況下就會產(chǎn)生一個問題杂靶。就是如果內(nèi)部類的生命周期比外部類的生命周期長,那么在外部類無用時酱鸭,內(nèi)部類依然持有外部類引用吗垮,導(dǎo)致外部類無法釋放,從而導(dǎo)致內(nèi)存泄漏凹髓。

Android 中典型的使用場景就是Handler.通常我們的寫法是這樣:

public class MainActivity extends AppCompatActivity{

@Override

protected void onCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

start();

}

private void start(){

Messagemsg=Message.obtain();

msg.what=1;

mHandler.sendMessage(msg);

}

private Handler mHandler=new Handler(){

@Override

public void handleMessage(Message msg){

if(msg.what==1){

//做相應(yīng)邏輯

}

}

};

}


熟悉Handler消息機(jī)制的都知道烁登,mHandler會作為成員變量保存在發(fā)送的消息msg中,即msg持有mHandler的引用蔚舀,而mHandler是Activity的非靜態(tài)內(nèi)部類實例饵沧,即mHandler持有Activity的引用锨络,那么我們就可以理解為msg間接持有Activity的引用。msg被發(fā)送后先放到消息隊列MessageQueue中狼牺,然后等待Looper的輪詢處理: MessageQueue和Looper都是與線程相關(guān)聯(lián)的羡儿,MessageQueue是Looper引用的成員變量,而Looper是保存在ThreadLocal中的锁右。那么當(dāng)Activity退出后失受,msg可能仍然存在于消息對列MessageQueue中未處理或者正在處理,那么這樣就會導(dǎo)致Activity無法被回收咏瑟,以致發(fā)生Activity的內(nèi)存泄露拂到。

通常在Android開發(fā)中如果要使用內(nèi)部類,但又要規(guī)避內(nèi)存泄露码泞,一般都會采用靜態(tài)內(nèi)部類+弱引用的方式兄旬。

在Activity中創(chuàng)建靜態(tài)內(nèi)部類:

public class MainActivity extends AppcompatActivity{

private static class MyHandler extends Handler{

? ? ? ? private WeakReference<MainActivity> activityWeakReference;

? ? ? ? public MyHandler(MainActivity activity){

? ? ? ? ? ? activityWeakReference = new WeakReference<>(activity);

? ? ? ? }

? ? ? ? @Override

? ? ? ? public void handleMessage(@NonNull Message msg) {

? ? ? ? ? ? if(activityWeakReference.get() != null){

? ? ? ? ? ? ? ? if(msg.what == 1 ){

? ? ? ? ? ? ? ? ? ? //do something

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? }


上面的做法確實避免了Activity導(dǎo)致的內(nèi)存泄露,發(fā)送的msg不再已經(jīng)沒有持有Activity的引用了余寥,但是msg還是有可能存在消息隊列MessageQueue中领铐,所以更好的是在Activity銷毀時就將mHandler的回調(diào)和發(fā)送的消息給移除掉。

@Override

protected void onDestroy(){

super.onDestroy();

mHandler.removeCallbacksAndMessages(null);

}


非靜態(tài)內(nèi)部類造成內(nèi)存泄露還有一種情況就是使用Thread或者AsyncTask宋舷。 同樣類似于handler,生命周期的不一致導(dǎo)致內(nèi)存泄漏绪撵。

總之,在使用內(nèi)部類時祝蝠,要時刻注意生命周期是否一致音诈,如果不一致就要考慮是否會發(fā)生內(nèi)存泄漏的問題。

未取消注冊或回調(diào)導(dǎo)致內(nèi)存泄露

在注冊觀察則模式的時候绎狭,如果不及時取消也會造成內(nèi)存泄露细溅。比如使用Retrofit+RxJava注冊網(wǎng)絡(luò)請求的觀察者回調(diào),同樣作為匿名內(nèi)部類持有外部引用儡嘶,所以需要記得在不用或者銷毀的時候取消注冊`

靜態(tài)變量導(dǎo)致內(nèi)存泄露

靜態(tài)變量存儲在方法區(qū)喇聊,它的生命周期從類加載開始,到整個進(jìn)程結(jié)束蹦狂。一旦靜態(tài)變量初始化后誓篱,它所持有的引用只有等到進(jìn)程結(jié)束才會釋放。

盡量少地使用靜態(tài)持有的變量凯楔,以避免發(fā)生內(nèi)存泄露窜骄。當(dāng)然,我們也可以在適當(dāng)?shù)臅r候講靜態(tài)量重置為null啼辣,使其不再持有引用,這樣也可以避免內(nèi)存泄露御滩。

不要在類初始時初始化靜態(tài)成員鸥拧〉吃叮可以考慮lazy初始化。

架構(gòu)設(shè)計上要思考是否真的有必要這樣做富弦,盡量避免沟娱。如果架構(gòu)需要這么設(shè)計,那么此對象的生命周期你有責(zé)任管理起來腕柜。

集合中的對象未清理造成內(nèi)存泄露

這個比較好理解济似,如果一個對象放入到ArrayList、HashMap等集合中盏缤,這個集合就會持有該對象的引用砰蠢。當(dāng)我們不再需要這個對象時,也并沒有將它從集合中移除唉铜,這樣只要集合還在使用(而此對象已經(jīng)無用了)台舱,這個對象就造成了內(nèi)存泄露。并且如果集合被靜態(tài)引用的話潭流,集合里面那些沒有用的對象更會造成內(nèi)存泄露了竞惋。所以在使用集合時要及時將不用的對象從集合remove,或者clear集合灰嫉,以避免內(nèi)存泄漏

屬性動畫造成內(nèi)存泄露

動畫同樣是一個耗時任務(wù)拆宛,比如在Activity中啟動了屬性動畫(ObjectAnimator),但是在銷毀的時候讼撒,沒有調(diào)用cancle方法浑厚,雖然我們看不到動畫了,但是這個動畫依然會不斷地播放下去椿肩,動畫引用所在的控件瞻颂,所在的控件引用Activity,這就造成Activity無法正常釋放郑象。因此同樣要在Activity銷毀的時候cancel掉屬性動畫贡这,避免發(fā)生內(nèi)存泄漏。

資源未關(guān)閉或釋放導(dǎo)致內(nèi)存泄露

在使用IO厂榛、File流或者Sqlite盖矫、Cursor等資源時要及時關(guān)閉。這些資源在進(jìn)行讀寫操作時通常都使用了緩沖击奶,如果及時不關(guān)閉辈双,這些緩沖對象就會一直被占用而得不到釋放,以致發(fā)生內(nèi)存泄露柜砾。因此我們在不需要使用它們的時候就及時關(guān)閉湃望,以便緩沖能及時得到釋放,從而避免內(nèi)存泄露

Bitmap 沒調(diào)用 recycle()方法,對于 Bitmap 對象在不使用時,我們應(yīng)該先調(diào)用 recycle() 釋放內(nèi)存证芭,然后才它設(shè)置為 null. 因為加載 Bitmap 對象的內(nèi)存空間瞳浦,一部分是 java 的,一部分 C的(因為 Bitmap 分配的底層是通過 JNI 調(diào)用的 )废士。 而這個 recyle() 就是針對 C 部分的內(nèi)存釋放叫潦。

總結(jié)

內(nèi)存泄露在Android內(nèi)存優(yōu)化是一個比較重要的一個方面,很多時候程序中發(fā)生了內(nèi)存泄露我們不一定就能注意到官硝,所有在編碼的過程要養(yǎng)成良好的習(xí)慣矗蕊。

總結(jié)下來只要做到以下這幾點(diǎn)就能避免大多數(shù)情況的內(nèi)存泄漏:

構(gòu)造單例的時候盡量別用Activity的引用;

靜態(tài)引用時注意應(yīng)用對象的置空或者少用靜態(tài)引用氢架;

使用靜態(tài)內(nèi)部類+軟引用代替非靜態(tài)內(nèi)部類傻咖;

及時取消廣播或者觀察者注冊;

耗時任務(wù)达箍、屬性動畫在Activity銷毀時記得cancel没龙;

文件流、Cursor等資源及時關(guān)閉缎玫;

Activity銷毀時WebView的移除和銷毀(android5.1上的bug,當(dāng)Activity銷毀時硬纤,webView并沒有被真正移除掉,還持有Activity的引用)赃磨。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末筝家,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子邻辉,更是在濱河造成了極大的恐慌溪王,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件值骇,死亡現(xiàn)場離奇詭異莹菱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)吱瘩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門道伟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人使碾,你說我怎么就攤上這事蜜徽。” “怎么了票摇?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵拘鞋,是天一觀的道長。 經(jīng)常有香客問我矢门,道長盆色,這世上最難降的妖魔是什么灰蛙? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮隔躲,結(jié)果婚禮上缕允,老公的妹妹穿的比我還像新娘。我一直安慰自己蹭越,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布教届。 她就那樣靜靜地躺著响鹃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪案训。 梳的紋絲不亂的頭發(fā)上买置,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機(jī)與錄音强霎,去河邊找鬼忿项。 笑死,一個胖子當(dāng)著我的面吹牛城舞,可吹牛的內(nèi)容都是我干的轩触。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼家夺,長吁一口氣:“原來是場噩夢啊……” “哼脱柱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拉馋,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤榨为,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后煌茴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體随闺,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年蔓腐,在試婚紗的時候發(fā)現(xiàn)自己被綠了矩乐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡合住,死狀恐怖绰精,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情透葛,我是刑警寧澤笨使,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站僚害,受9級特大地震影響硫椰,放射性物質(zhì)發(fā)生泄漏繁调。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一靶草、第九天 我趴在偏房一處隱蔽的房頂上張望蹄胰。 院中可真熱鬧,春花似錦奕翔、人聲如沸裕寨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宾袜。三九已至,卻和暖如春驾窟,著一層夾襖步出監(jiān)牢的瞬間庆猫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工绅络, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留月培,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓恩急,卻偏偏與公主長得像杉畜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子衷恭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359