首頁(yè)技術(shù)文章正文

Android培訓(xùn)之內(nèi)存泄露的各種原因詳解

更新時(shí)間:2017-04-14 來(lái)源:黑馬程序員Android培訓(xùn)學(xué)院 瀏覽量:

在Android開(kāi)發(fā)過(guò)程中,最為讓我們頭疼的就是內(nèi)存的泄露問(wèn)題了,很可能你很小的一個(gè)錯(cuò)誤都會(huì)引起內(nèi)存的泄露.

1.資源對(duì)象沒(méi)關(guān)閉造成的內(nèi)存泄漏

描述:

資源性對(duì)象比如(Cursor,F(xiàn)ile文件等)往往都用了一些緩沖,我們?cè)诓皇褂玫臅r(shí)候,應(yīng)該及時(shí)關(guān)閉它們,以便它們的緩沖及時(shí)回收內(nèi)存。它們的緩沖不僅存在于 java虛擬機(jī)內(nèi),還存在于java虛擬機(jī)外。如果我們僅僅是把它的引用設(shè)置為null,而不關(guān)閉它們,往往會(huì)造成內(nèi)存泄漏。因?yàn)橛行┵Y源性對(duì)象,比如 SQLiteCursor(在析構(gòu)函數(shù)finalize(),如果我們沒(méi)有關(guān)閉它,它自己會(huì)調(diào)close()關(guān)閉),如果我們沒(méi)有關(guān)閉它,系統(tǒng)在回收它時(shí)也會(huì)關(guān)閉它,但是這樣的效率太低了。因此對(duì)于資源性對(duì)象在不使用的時(shí)候,應(yīng)該調(diào)用它的close()函數(shù),將其關(guān)閉掉,然后才置為null.在我們的程序退出時(shí)一定要確保我們的資源性對(duì)象已經(jīng)關(guān)閉。

程序中經(jīng)常會(huì)進(jìn)行查詢數(shù)據(jù)庫(kù)的操作,但是經(jīng)常會(huì)有使用完畢Cursor后沒(méi)有關(guān)閉的情況。如果我們的查詢結(jié)果集比較小,對(duì)內(nèi)存的消耗不容易被發(fā)現(xiàn),只有在常時(shí)間大量操作的情況下才會(huì)復(fù)現(xiàn)內(nèi)存問(wèn)題,這樣就會(huì)給以后的測(cè)試和問(wèn)題排查帶來(lái)困難和風(fēng)險(xiǎn)。

示例代碼:


1
2
3
4
5
6
7
<font style="color:rgb(51, 51, 51)"><font style="background-color:rgb(248, 248, 248)"><font face="宋體">Cursor cursor = getContentResolver().query(uri...);
 
if (cursor.moveToNext()) {
 
... ...
 
} </font></font></font>

修正示例代碼:

 

[Java] 純文本查看 復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<font style="color:rgb(51, 51, 51)"><font style="background-color:rgb(248, 248, 248)"><font face="宋體">Cursor cursor = null;
 
try {
 
cursor = getContentResolver().query(uri...);
 
if (cursor != null &&cursor.moveToNext()) {
 
... ...
 
}
 
} finally {
 
if (cursor != null) {
 
try {
 
cursor.close();
 
} catch (Exception e) {
 
//ignore this
 
}
 
}
 
} </font></font></font>

 

2.構(gòu)造Adapter時(shí),沒(méi)有使用緩存的convertView

描述:

以構(gòu)造ListView的BaseAdapter為例,在BaseAdapter中提供了方法:

public View getView(int position, ViewconvertView, ViewGroup parent)

 

來(lái)向ListView提供每一個(gè)item所需要的view對(duì)象。初始時(shí)ListView會(huì)從BaseAdapter中根據(jù)當(dāng)前的屏幕布局實(shí)例化一定數(shù)量的 view對(duì)象,同時(shí)ListView會(huì)將這些view對(duì)象緩存起來(lái)。當(dāng)向上滾動(dòng)ListView時(shí),原先位于最上面的list item的view對(duì)象會(huì)被回收,然后被用來(lái)構(gòu)造新出現(xiàn)的最下面的list item。

 

這個(gè)構(gòu)造過(guò)程就是由getView()方法完成的,getView()的第二個(gè)形參View convertView就是被緩存起來(lái)的list item的view對(duì)象(初始化時(shí)緩存中沒(méi)有view對(duì)象則convertView是null)。由此可以看出,如果我們不去使用 convertView,而是每次都在getView()中重新實(shí)例化一個(gè)View對(duì)象的話,即浪費(fèi)資源也浪費(fèi)時(shí)間,也會(huì)使得內(nèi)存占用越來(lái)越大。 ListView回收l(shuí)ist item的view對(duì)象的過(guò)程可以查看:

 

android.widget.AbsListView.java --> voidaddScrapView(View scrap) 方法。

示例代碼:

 

[Java] 純文本查看 復(fù)制代碼
1
2
3
4
5
6
7
8
9
<font style="color:rgb(51, 51, 51)"><font style="background-color:rgb(248, 248, 248)"><font face="宋體">public View getView(int position, ViewconvertView, ViewGroup parent) {
 
View view = new Xxx(...);
 
... ...
 
return view;
 
} </font></font></font>

 

修正示例代碼:

 

[Java] 純文本查看 復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<font style="color:rgb(51, 51, 51)"><font style="background-color:rgb(248, 248, 248)"><font face="宋體">public View getView(int position, ViewconvertView, ViewGroup parent) {
 
View view = null;
 
if (convertView != null) {
 
view = convertView;
 
populate(view, getItem(position));
 
...
 
} else {
 
view = new Xxx(...);
 
...
 
}
 
return view;
 
} </font></font></font>

 

3.Bitmap對(duì)象不在使用時(shí)調(diào)用recycle()釋放內(nèi)存

描述:

有時(shí)我們會(huì)手工的操作Bitmap對(duì)象,如果一個(gè)Bitmap對(duì)象比較占內(nèi)存,當(dāng)它不在被使用的時(shí)候,可以調(diào)用Bitmap.recycle()方法回收此對(duì)象的像素所占用的內(nèi)存,但這不是必須的,視情況而定。可以看一下代碼中的注釋?zhuān)?/span>

 

[Java] 純文本查看 復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<font style="color:rgb(51, 51, 51)"><font style="background-color:rgb(248, 248, 248)"><font face="宋體">/**
 
?Free up the memory associated with thisbitmap's pixels, and mark the
 
?bitmap as "dead", meaning itwill throw an exception if getPixels() or
 
?setPixels() is called, and will drawnothing. This operation cannot be
 
?reversed, so it should only be called ifyou are sure there are no
 
?further uses for the bitmap. This is anadvanced call, and normally need
 
?not be called, since the normal GCprocess will free up this memory when
 
?there are no more references to thisbitmap.
 
*/ </font></font></font>

 

4.試著使用關(guān)于application的context來(lái)替代和activity相關(guān)的context

 

這是一個(gè)很隱晦的內(nèi)存泄漏的情況。有一種簡(jiǎn)單的方法來(lái)避免context相關(guān)的內(nèi)存泄漏。最顯著地一個(gè)是避免context逃出他自己的范圍之外。使用Application context。

 

這個(gè)context的生存周期和你的應(yīng)用的生存周期一樣長(zhǎng),而不是取決于activity的生存周期。如果你想保持一個(gè)長(zhǎng)期生存的對(duì)象,并且這個(gè)對(duì)象需要一個(gè)context,記得使用application對(duì)象。你可以通過(guò)調(diào)用 Context.getApplicationContext() or Activity.getApplication()來(lái)獲得。更多的請(qǐng)看這篇文章如何避免Android內(nèi)存泄漏。


5.注冊(cè)沒(méi)取消造成的內(nèi)存泄漏

 

一些Android程序可能引用我們的Anroid程序的對(duì)象(比如注冊(cè)機(jī)制)。即使我們的Android程序已經(jīng)結(jié)束了,但是別的引用程序仍然還有對(duì)我們的Android程序的某個(gè)對(duì)象的引用,泄漏的內(nèi)存依然不能被垃圾回收。調(diào)用registerReceiver后未調(diào)用unregisterReceiver。

 

比如:假設(shè)我們希望在鎖屏界面(LockScreen)中,監(jiān)聽(tīng)系統(tǒng)中的電話服務(wù)以獲取一些信息(如信號(hào)強(qiáng)度等),則可以在LockScreen中定義一個(gè) PhoneStateListener的對(duì)象,同時(shí)將它注冊(cè)到TelephonyManager服務(wù)中。對(duì)于LockScreen對(duì)象,當(dāng)需要顯示鎖屏界面的時(shí)候就會(huì)創(chuàng)建一個(gè)LockScreen對(duì)象,而當(dāng)鎖屏界面消失的時(shí)候LockScreen對(duì)象就會(huì)被釋放掉。

 

但是如果在釋放 LockScreen對(duì)象的時(shí)候忘記取消我們之前注冊(cè)的PhoneStateListener對(duì)象,則會(huì)導(dǎo)致LockScreen無(wú)法被垃圾回收。如果不斷的使鎖屏界面顯示和消失,則最終會(huì)由于大量的LockScreen對(duì)象沒(méi)有辦法被回收而引起OutOfMemory,使得system_process 進(jìn)程掛掉。

 

雖然有些系統(tǒng)程序,它本身好像是可以自動(dòng)取消注冊(cè)的(當(dāng)然不及時(shí)),但是我們還是應(yīng)該在我們的程序中明確的取消注冊(cè),程序結(jié)束時(shí)應(yīng)該把所有的注冊(cè)都取消掉。

 

6.集合中對(duì)象沒(méi)清理造成的內(nèi)存泄漏

 

我們通常把一些對(duì)象的引用加入到了集合中,當(dāng)我們不需要該對(duì)象時(shí),并沒(méi)有把它的引用從集合中清理掉,這樣這個(gè)集合就會(huì)越來(lái)越大。如果這個(gè)集合是static的話,那情況就更嚴(yán)重了。

本文版權(quán)歸黑馬程序員Android培訓(xùn)學(xué)院所有,歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明作者出處。謝謝!
作者:黑馬程序員Android培訓(xùn)學(xué)院
首發(fā):http://m.pantone-color.com.cn/news/Android.html
分享到:
在線咨詢 我要報(bào)名
和我們?cè)诰€交談!