Android內存優(yōu)化之——static使用篇

在Android開發(fā)中,我們經(jīng)常會使用到static來修飾我們的成員變量桩撮,其本意是為了讓多個對象共用一份空間介劫,節(jié)省內存,或者是使用單例模式,讓該類只生產(chǎn)一個實例而在整個app中使用锨推。然而在某些時候不恰當?shù)氖褂没蛘呤蔷幊痰牟灰?guī)范卻會造成了內存泄露現(xiàn)象(java上的內存泄漏指內存得不到gc的及時回收,從而造成內存占用過多的現(xiàn)象)

本文中我們主要分析的是static變量對activtiy的不恰當引用而造成的內存泄漏公壤,因為對于同一個Activity頁面一般每次打開時系統(tǒng)都會重新生成一個該activity的對象(standard模式下)换可,而每個activity對象一般都含有大量的視圖對象和bitmap對象,如果之前的activity對象不能得到及時的回收,從而就造成了內存的泄漏現(xiàn)象厦幅。

下面一邊看代碼一邊講解沾鳄。

單例模式不正確的獲取context

public?class?LoginManager?{

private?Context?context;

private?static?LoginManager?manager;

public?static?LoginManager?getInstance(Context?context)?{

if(manager?==null)

manager?=newLoginManager(context);

returnmanager;

}

private?LoginManager(Context?context)?{

this.context?=?context;

}

在LoginActivity中

public?class?LoginActivity?extends?Activity??{

private?LoginManager?loginManager;

@Override

protected?void?onCreate(Bundle?savedInstanceState)?{

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_login);

loginManager?=?LoginManager.getInstance(this);

}

這種方式大家應該一看就明白問題在哪里了,在LoginManager的單例中context持有了LoginActivity的this對象确憨,即使登錄成功后我們跳轉到了其他Activity頁面译荞,LoginActivity的對象仍然得不到回收因為他被單例所持有,而單例的生命周期是同Application保持一致的休弃。

正確的獲取context的方式

public?class?LoginManager?{

private?Context?context;

private?static?LoginManager?manager;

public?static?LoginManager?getInstance(Context?context)?{

if(manager?==null)

manager?=newLoginManager(context);

returnmanager;

}

private?LoginManager(Context?context)?{

this.context?=?context.getApplicationContext();

}

修改方式也非常簡單我們單例中context不再持有Activity的context而是持有Application的context即可吞歼,因為Application本來就是單例,所以這樣就不會存在內存泄漏的的現(xiàn)象了玫芦。

單例模式中通過內部類持有activity對象

第一種方式內存泄漏太過與明顯浆熔,相信大家都不會犯這種錯誤,接下來要介紹的這種泄漏方式會比較不那么容易發(fā)現(xiàn)桥帆,內部類的使用造成activity對象被單例持有医增。

還是看代碼再分析,下面是一個單例的類:

25public?class?TestManager?{

public?static?final?TestManager?INSTANCE?=newTestManager();

private?List?mListenerList;

private?TestManager()?{

mListenerList?=newArrayList();

}

public?static?TestManager?getInstance()?{

returnINSTANCE;

}

public?void?registerListener(MyListener?listener)?{

if(!mListenerList.contains(listener))?{

mListenerList.add(listener);

}

}

public?void?unregisterListener(MyListener?listener)?{

mListenerList.remove(listener);

}

}

interface?MyListener?{

public?void?onSomeThingHappen();

}

然后是activity:public?class?TestActivity?extends?AppCompatActivity?{

private?MyListener?mMyListener=newMyListener()?{

@Override

public?void?onSomeThingHappen()?{

}

};

private?TestManager?testManager=TestManager.getInstance();

@Override

protected?void?onCreate(Bundle?savedInstanceState)?{

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_test);

testManager.registerListener(mMyListener);

}

}

我們知道在java中老虫,非靜態(tài)的內部類的對象都是會持有指向外部類對象的引用的叶骨,因此我們將內部類對象mMyListener讓單例所持有時,由于mMyListener引用了我們的activity對象祈匙,因此造成activity對象也不能被回收了忽刽,從而出現(xiàn)內存泄漏現(xiàn)象天揖。

修改以上代碼,避免內存泄漏跪帝,在activity中添加以下代碼:`@Override

protected?void?onDestroy()?{

testManager.unregisterListener(mMyListener);

super.onDestroy();

}

AsyncTask不正確使用造成的內存泄漏

介紹完以上兩種情況的內存泄漏后今膊,我們在來看一種更加容易被忽略的內存泄漏現(xiàn)象,對于AsyncTask不正確使用造成內存泄漏的問題伞剑。mTask=newAsyncTask()

{

@Override

protected?Void?doInBackground(String...?params)?{

//doSamething..

returnnull;

}

}.execute("a?task");

一般我們在主線程中開啟一個異步任務都是通過實現(xiàn)一個內部類其繼承自AsyncTask類然后實現(xiàn)其相應的方法來完成的斑唬,那么自然的mTask就會持有對activity實例對象的引用了。查看AsyncTask的實現(xiàn)黎泣,我們會通過一個SerialExecutor串行線程池來對我們的任務進行排隊恕刘,而這個SerialExecutor對象就是一個static final的常量。

具體的引用關系是:

1.我們的任務被封裝在一個FutureTask的對象中(它充當一個runable的作用)抒倚,F(xiàn)utureTask的實現(xiàn)也是通過內部類來實現(xiàn)的褐着,因此它也為持有AsyncTask對象,而AsyncTask對象引用了activity對象托呕,因此activity對象間接的被FutureTask對象給引用了含蓉。

2.futuretask對象會被添加到一個ArrayDeque類型的任務隊列的mTasks實例中

3.mTasks任務隊列又被SerialExecutor對象所持有,剛也說了這個SerialExecutor對象是一個static final的常量镣陕。

具體AsyncTask的實現(xiàn)大家可以去參照下其源代碼谴餐,我這里就通過文字描述一下其添加任務的實現(xiàn)過程就可以了姻政,總之分析了這么多通過層層引用后我們的activity會被一個static變量所引用到呆抑。所以我們在使用AsyncTask的時候不宜在其中執(zhí)行太耗時的操作,假設activity已經(jīng)退出了汁展,然而AsyncTask里任務還沒有執(zhí)行完成或者是還在排隊等待執(zhí)行鹊碍,就會造成我們的activity對象被回收的時間延后,一段時間內內存占有率變大食绿。

解決方法在activity退出的時候應該調用cancel()函數(shù)@Override

protected?void?onDestroy()?{

//mTask.cancel(false);

mTask.cancel(true);

super.onDestroy();

}

具體cancel()里傳遞true or false依實際情況而定:

1.當我們的任務還在排隊沒有被執(zhí)行侈咕,調用cancel()無論true or false,任務會從排隊隊列中移除,即任務都不會被執(zhí)行到了器紧。

2.當我們的任務已經(jīng)開始執(zhí)行了(doInBackground被調用)耀销,傳入?yún)?shù)為false時并不會打斷doInBackground的執(zhí)行,傳入?yún)?shù)為true時铲汪,如果我們的線程處于休眠或阻塞(如:sleep,wait)狀況是會打斷其執(zhí)行熊尉。

這里具體解釋下cancle(true)的意義:mTask=newAsyncTask()

{

@Override

protected?Void?doInBackground(String...?params)?{

try{

Thread.sleep(10000);

}catch(InterruptedException?e)?{

e.printStackTrace();

}

Log.d("test","task?is?running");

returnnull;

}

}.execute("a?task");

try{

//保證task得以執(zhí)行

Thread.sleep(2000);

}catch(InterruptedException?e)?{

e.printStackTrace();

}

mTask.cancel(true);

在這樣的情況下我們的線程處于休眠狀態(tài)調用cancel(true)方法會打斷doInBackground的執(zhí)行——即不會看到log語句的輸出。

但在下面的這種情況的時候卻打斷不了

18mTask=newAsyncTask()

{

@Override

protected?Void?doInBackground(String...?params)?{

Boolean?loop=true;

while(loop)?{

Log.d("test","task?is?running");

}

returnnull;

}

}.execute("a?task");

try{

Thread.sleep(2000);

}catch(InterruptedException?e)?{

e.printStackTrace();

}

mTask.cancel(true);

由于我們的線程不處于等待或休眠的狀況及時調用cancel(true)也不能打斷doInBackground的執(zhí)行——現(xiàn)象:log函數(shù)一直在打印輸出掌腰。

解決方法:

20mTask=newAsyncTask()

{

@Override

protected?Void?doInBackground(String...?params)?{

//doSomething..

Boolean?loop=true;

while(loop)?{

if(isCancelled())

returnnull;

Log.d("test","task?is?running");

}

returnnull;

}

}.execute("a?task");

try{

Thread.sleep(2000);

}catch(InterruptedException?e)?{

e.printStackTrace();

}

mTask.cancel(true);

這里我們通過在每次循環(huán)是檢查任務是否已經(jīng)被cancle掉狰住,如果是則退出。因此對于AsyncTask我們也得注意按照正確的方式進行使用齿梁,不然也會造成程序內存泄漏的現(xiàn)象催植。

以上內容就是在使用static時肮蛹,我們需要怎么做才能優(yōu)化內存的使用,當然對于以上3種情況是我們編程中使用static經(jīng)常遇到的內存泄漏的情況创南,但仍然還有很多情況我們不易察覺到伦忠。比如:如果不做介紹,上面的第三種情況就很難察覺到稿辙,這時我們最終的內存泄漏優(yōu)化方法就是:使用內存泄漏分析工具缓苛,在下一篇文章里我會參照第三種情況(AsyncTask)造成的內存泄漏,通過使用MAT工具進行分析邓深,講解MAT排除內存泄漏的使用方法未桥。


http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1225/3800.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市芥备,隨后出現(xiàn)的幾起案子冬耿,更是在濱河造成了極大的恐慌,老刑警劉巖萌壳,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亦镶,死亡現(xiàn)場離奇詭異,居然都是意外死亡袱瓮,警方通過查閱死者的電腦和手機缤骨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尺借,“玉大人绊起,你說我怎么就攤上這事×钦叮” “怎么了虱歪?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長栅表。 經(jīng)常有香客問我笋鄙,道長,這世上最難降的妖魔是什么怪瓶? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任萧落,我火速辦了婚禮,結果婚禮上洗贰,老公的妹妹穿的比我還像新娘找岖。我一直安慰自己,他們只是感情好哆姻,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布宣增。 她就那樣靜靜地躺著,像睡著了一般矛缨。 火紅的嫁衣襯著肌膚如雪爹脾。 梳的紋絲不亂的頭發(fā)上帖旨,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音灵妨,去河邊找鬼解阅。 笑死,一個胖子當著我的面吹牛泌霍,可吹牛的內容都是我干的货抄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼朱转,長吁一口氣:“原來是場噩夢啊……” “哼蟹地!你這毒婦竟也來了?” 一聲冷哼從身側響起藤为,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤怪与,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后缅疟,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體分别,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年存淫,在試婚紗的時候發(fā)現(xiàn)自己被綠了耘斩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡桅咆,死狀恐怖括授,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情轧邪,我是刑警寧澤刽脖,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站忌愚,受9級特大地震影響,放射性物質發(fā)生泄漏却邓。R本人自食惡果不足惜硕糊,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望腊徙。 院中可真熱鬧简十,春花似錦、人聲如沸撬腾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽民傻。三九已至胰默,卻和暖如春场斑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背牵署。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工漏隐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奴迅。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓青责,卻偏偏與公主長得像,于是被迫代替她去往敵國和親取具。 傳聞我的和親對象是個殘疾皇子脖隶,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內容