Android內(nèi)存管理與內(nèi)存溢出防范_圖文_第1頁
Android內(nèi)存管理與內(nèi)存溢出防范_圖文_第2頁
Android內(nèi)存管理與內(nèi)存溢出防范_圖文_第3頁
Android內(nèi)存管理與內(nèi)存溢出防范_圖文_第4頁
Android內(nèi)存管理與內(nèi)存溢出防范_圖文_第5頁
已閱讀5頁,還剩28頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

1、內(nèi)存管理與內(nèi)存溢出防范目錄內(nèi)存管理與內(nèi)存溢出防范 1一內(nèi)存分配跟蹤工具DDMS >Allocation tracker 使用 2二內(nèi)存監(jiān)測工具DDMS->Heap 2三內(nèi)存分析工具M(jìn)AT(MemoryAnalyzerTool 31.生成.hprof文件 42.使用MAT導(dǎo)入.hprof文件 53.使用MAT的視圖工具分析內(nèi)存 5四MAT使用實例 51.生成heap dump 72.用MAT分析heap dumps 93.使用MAT比較heap dumps 11五防范不良代碼 111查詢數(shù)據(jù)庫沒有關(guān)閉游標(biāo) 112緩存 convertView 123Bitmap對象釋放內(nèi)存 134釋放

2、對象的引用 135Context的使用 146線程 177其他 20六優(yōu)化代碼 201.使用自身方法(Use Native Methods) 202.使用虛擬優(yōu)于使用接口 203.使用靜態(tài)優(yōu)于使用虛擬 204.盡可能避免使用內(nèi)在的Get、Set方法 205.緩沖屬性調(diào)用Cache Field Lookups 216.聲明Final常量 217.慎重使用增強型For循環(huán)語句 228.避免列舉類型Avoid Enums 239.通過內(nèi)聯(lián)類使用包空間 2310.避免浮點類型的使用 2411.一些標(biāo)準(zhǔn)操作的時間比較 2412.為響應(yīng)靈敏性設(shè)計 25一內(nèi)存分配跟蹤工具DDMS >Allocatio

3、n tracker 使用運行DDMS,只需簡單的選擇應(yīng)用進(jìn)程并單擊Allocation tracker標(biāo)簽,就會打開一個新的窗口,單擊“Start Tracing”按鈕;然后,讓應(yīng)用運行你想分析的代碼。運行完畢后,單擊“Get Allocations”按鈕,一個已分配對象的列表就會出現(xiàn)第一個表格中。單擊第一個表格中的任何一項,在表格二中就會出現(xiàn)導(dǎo)致該內(nèi)存分配的棧跟蹤信息。通過allocation tracker,不僅知道分配了哪類對象,還可以知道在哪個線程、哪個類、哪個文件的哪一行。盡管在性能關(guān)鍵的代碼路徑上移除所有的內(nèi)存分配操作不是必須的,甚至有時候是不可能的,但allocation tra

4、cker可以幫你識別代碼中的一些重要問題。舉例來說,許多應(yīng)用中發(fā)現(xiàn)的一個普遍錯誤:每次進(jìn)行繪制都創(chuàng)建一個新的Paint對象。將Paint的創(chuàng)建移到一個實例區(qū)域里,是一個能極大提高程序性能的簡單舉措。二內(nèi)存監(jiān)測工具DDMS->Heap無論怎么小心,想完全避免badcode是不可能的,此時就需要一些工具來幫助我們檢查代碼中是否存在會造成內(nèi)存泄漏的地方。Androidtools中的DDMS就帶有一個很不錯的內(nèi)存監(jiān)測工具Heap(這里我使eclipse的ADT插件,并以真機為例,在模擬器中的情況類似。用Heap監(jiān)測應(yīng)用進(jìn)程使用內(nèi)存情況的步驟如下:1.啟動eclipse后,切換到DDMS透視圖,并

5、確認(rèn)Devices視圖、Heap視圖都是打開的;2.將手機通過USB鏈接至電腦,鏈接時需要確認(rèn)手機是處于“USB調(diào)試”模式,而不是作為“MassStorage”;3.鏈接成功后,在DDMS的Devices視圖中將會顯示手機設(shè)備的序列號,以及設(shè)備中正在運行的部分進(jìn)程信息;4.點擊選中想要監(jiān)測的進(jìn)程,比如system_process進(jìn)程;5.點擊選中Devices視圖界面中最上方一排圖標(biāo)中的“UpdateHeap”圖標(biāo);6.點擊Heap視圖中的“CauseGC”按鈕;7.此時在Heap視圖中就會看到當(dāng)前選中的進(jìn)程的內(nèi)存使用量的詳細(xì)情況a點擊“CauseGC”按鈕相當(dāng)于向虛擬機請求了一次gc操作;b

6、當(dāng)內(nèi)存使用信息第一次顯示以后,無須再不斷的點擊“CauseGC”,Heap視圖界面會定時刷新,在對應(yīng)用的不斷的操作過程中就可以看到內(nèi)存使用的變化;c內(nèi)存使用信息的各項參數(shù)根據(jù)名稱即可知道其意思,在此不再贅述。如何才能知道我們的程序是否有內(nèi)存泄漏的可能性呢。這里需要注意一個值:Heap視圖中部有一個Type叫做dataobject,即數(shù)據(jù)對象,也就是我們的程序中大量存在的類類型的對象。在dataobject一行中有一列是“TotalSize”,其值就是當(dāng)前進(jìn)程中所有Java數(shù)據(jù)對象的內(nèi)存總量,一般情況下,這個值的大小決定了是否會有內(nèi)存泄漏??梢赃@樣判斷:a不斷的操作當(dāng)前應(yīng)用,同時注意觀察data

7、object的TotalSize值;b正常情況下TotalSize值都會穩(wěn)定在一個有限的范圍內(nèi),也就是說由于程序中的的代碼良好,沒有造成對象不被垃圾回收的情況,所以說雖然我們不斷的操作會不斷的生成很多對象,而在虛擬機不斷的進(jìn)行GC的過程中,這些對象都被回收了,內(nèi)存占用量會會落到一個穩(wěn)定的水平;c反之如果代碼中存在沒有釋放對象引用的情況,則dataobject的TotalSize值在每次GC后不會有明顯的回落,隨著操作次數(shù)的增多TotalSize的值會越來越大,直到到達(dá)一個上限后導(dǎo)致進(jìn)程被kill掉。d此處已system_process進(jìn)程為例,在我的測試環(huán)境中system_process進(jìn)程所

8、占用的內(nèi)存的dataobject的TotalSize正常情況下會穩(wěn)定在2.22.8之間,而當(dāng)其值超過3.55后進(jìn)程就會被kill。三內(nèi)存分析工具M(jìn)AT(MemoryAnalyzerTool如果使用DDMS確實發(fā)現(xiàn)了我們的程序中存在內(nèi)存泄漏,那又如何定位到具體出現(xiàn)問題的代碼片段,最終找到問題所在呢?如果從頭到尾的分析代碼邏輯,那肯定會把人逼瘋,特別是在維護別人寫的代碼的時候。這里介紹一個極好的內(nèi)存分析工具M(jìn)emoryAnalyzerTool(MAT。MAT是一個Eclipse插件,同時也有單獨的RCP客戶端。官方下載地址、MAT介紹和詳細(xì)的使用教程請參見:,在此不進(jìn)行說明了。另外在MAT安裝后的

9、幫助文檔里也有完備的使用教程。在此僅舉例說明其使用方法。我自己使用的是MAT的eclipse插件,使用插件要比RCP稍微方便一些。使用MAT進(jìn)行內(nèi)存分析需要幾個步驟,包括:生成.hprof文件、打開MAT并導(dǎo)入.hprof文件、使用MAT的視圖工具分析內(nèi)存。以下詳細(xì)介紹。1.生成.hprof文件a 打開eclipse并切換到DDMS透視圖,同時確認(rèn)Devices、Heap和logcat視圖已經(jīng)打開了;b 將手機設(shè)備鏈接到電腦,并確保使用“USB調(diào)試”模式鏈接,而不是“Mass Storage“模式;c 鏈接成功后在Devices視圖中就會看到設(shè)備的序列號,和設(shè)備中正在運行的部分進(jìn)程;d 點擊選

10、中想要分析的應(yīng)用的進(jìn)程,在Devices視圖上方的一行圖標(biāo)按鈕中,同時選中“Update Heap”和“Dump HPROF file”兩個按鈕;e 這是DDMS工具將會自動生成當(dāng)前選中進(jìn)程的.hprof文件,并將其進(jìn)行轉(zhuǎn)換后存放在sdcard當(dāng)中,如果你已經(jīng)安裝了MAT插件,那么此時MAT將會自動被啟用,并開始對.hprof文件進(jìn)行分析;注意:第4步和第5步能夠正常使用前提是我們需要有sdcard,并且當(dāng)前進(jìn)程有向sdcard中寫入的權(quán)限(WRITE_EXTERNAL_STORAGE,否則.hprof文件不會被生成,在logcat中會顯示諸如的信息。如果我們沒有sdcard,或者當(dāng)前進(jìn)程沒有

11、向sdcard寫入的權(quán)限(如system_process),那我們可以這樣做:在當(dāng)前程序中,例如framework中某些代碼中,可以使用中的public static void dumpHprofData(String fileName throws IOException方法,手動的指定.hprof文件的生成位置。例如:xxxButton.setOnClickListener(newView.OnClickListener(publicvoidonClick(Viewview.上述代碼意圖是希望在xxxButton被點擊的時候開始抓取內(nèi)存使用信息,并保存在我們指定的位置:/data/temp

12、/myapp.hprof,這樣就沒有權(quán)限的限制了,而且也無須用sdcard。但要保證/data/temp目錄是存在的。這個路徑可以自己定義,當(dāng)然也可以寫成sdcard當(dāng)中的某個路徑。2.使用MAT導(dǎo)入.hprof文件a 如果是eclipse自動生成的.hprof文件,可以使用MAT插件直接打開(可能是比較新的ADT才支持);b 如果eclipse自動生成的.hprof文件不能被MAT直接打開,或者是使用方法手動生成的.hprof文件,則需要將.hprof文件進(jìn)行轉(zhuǎn)換,轉(zhuǎn)換的方法:將.hprof文件拷貝到PC上的/ANDROID_SDK/tools目錄下,并輸入命令hprof-conv xxx.

13、hprof yyy.hprof,其中xxx.hprof為原始文件,yyy.hprof為轉(zhuǎn)換過后的文件。轉(zhuǎn)換過后的文件自動放在/ANDROID_SDK/tools目錄下。OK,到此為止,.hprof文件處理完畢,可以用來分析內(nèi)存泄露情況了。c 在Eclipse中點擊Windows->OpenPerspective->Other->MemoryAnalyzer,或者打MemoryAnalyzerTool的RCP。在MAT中點擊File->OpenFile,瀏覽并導(dǎo)入剛剛轉(zhuǎn)換而得到的.hprof文件。3.使用MAT的視圖工具分析內(nèi)存導(dǎo)入.hprof文件以后,MAT會自動解析并

14、生成報告,點擊DominatorTree,并按Package分組,選擇自己所定義的Package類點右鍵,在彈出菜單中選擇Listobjects->Withincomingreferences。這時會列出所有可疑類,右鍵點擊某一項,并選擇PathtoGCRoots->excludeweak/softreferences,會進(jìn)一步篩選出跟程序相關(guān)的所有有內(nèi)存泄露的類。據(jù)此,可以追蹤到代碼中的某一個產(chǎn)生泄露的類。具體的分析方法在MAT的官方網(wǎng)站和客戶端的幫助文檔中有十分詳盡,使用MAT分析內(nèi)存查找內(nèi)存泄漏的根本思路,就是找到哪個類的對象的引用沒有被釋放,找到?jīng)]有被釋放的原因,也就可以很

15、容易定位代碼中的哪些片段的邏輯有問題了。四MAT使用實例使用DDMS檢查這個應(yīng)用的heap使用情況。你可以使用下面的方法啟動DDMS:From Eclipse : click Window > Open Perspective>Other.>DDMS在左邊的面板選擇進(jìn)程com.founder.android.,然后在工具條上邊點擊heap updates按鈕。這個時候切換到DDMS的VMHeap分頁。它會顯示每次gc后heap內(nèi)存的一些基本數(shù)據(jù)。要看第一次gc后的數(shù)據(jù)內(nèi)容,點擊CauseGC按鈕:1.生成heap dump我們現(xiàn)在使用heap dump來追蹤這個問題。點擊DD

16、MS工具條上面的Dump HPROF文件按鈕,選擇文件存儲位置,然后在運行hprof-conv。如果你使用ADT(它包含DDMS的插件)同時也在eclipse里面安裝了MAT,點擊“dump HPROF”按鈕將會自動地做轉(zhuǎn)換(用hprof-conv)同時會在eclipse里面打開轉(zhuǎn)換后的hprof文件(它其實用MAT打開)。將生成的.hprof文件導(dǎo)入到MAT中,選擇Leak Suspects Report ,得到下圖:2.用MAT分析heap dumps啟動MAT然后加載剛才我們生成的HPROF文件。MAT是一個強大的工具,講述它所有的特性超出了本文的范圍,所以我只想演示一種你可以用來檢測泄

17、露的方法:直方圖(Histogram)視圖。它顯示了一個可以排序的類實例的列表,內(nèi)容包括:shallow heap(所有實例的內(nèi)存使用總和),或者retained heap(所有類實例被分配的內(nèi)存總和,里面也包括他們所有引用的對象)。如果我們按照shallow heap排序,自從Android3.0(Honeycomb),Bitmap的像素數(shù)據(jù)被存儲在byte數(shù)組里(之前是被存儲在Dalvik的heap里),所以基于這個對象的大小來判斷。右擊byte類然后選擇List Objects>with incoming references。它會生成一個heap上的所有byte數(shù)組的列表,在列表

18、里,我們可以按照Shallow Heap的使用情況來排序。選擇并展開一個比較大的對象,它將展示從根到這個對象的路徑-就是一條保證對象有效的鏈條。MAT不會明確告訴我們這就是泄露,因為它也不知道這個東西是不是程序還需要的,只有程序員知道。在這個案例里面,緩存使用的大量的內(nèi)存會影響到后面的應(yīng)用程序,所以我們可以考慮限制緩存的大小。3.使用MAT比較heap dumps調(diào)試內(nèi)存泄露時,有時候適時比較2個地方的heap狀態(tài)是很有用的。這時你就需要生成2個單獨的HPROF文件(不要忘了轉(zhuǎn)換格式)。下面是一些關(guān)于如何在MAT里比較2個heapdumps的內(nèi)容(有一點復(fù)雜):a 第一個HPROF文件(usi

19、ngFile>OpenHeapDump.b 打開Histogram view.c 在Navigation Historyview里(如果看不到就從Window>NavigationHistory找.右擊histogram然后選擇AddtoCompareBasket.d 打開第二個HPROF文件然后重做步驟2和3.e 切換到CompareBasketview,然后點擊ComparetheResults(視圖右上角的紅色"!"圖標(biāo)。五防范不良代碼1查詢數(shù)據(jù)庫沒有關(guān)閉游標(biāo)程序中經(jīng)常會進(jìn)行查詢數(shù)據(jù)庫的操作,但是經(jīng)常會有使用完畢Cursor后沒有關(guān)閉的情況。如果我們的查詢

20、結(jié)果集比較小,對內(nèi)存的消耗不容易被發(fā)現(xiàn),只有在常時間大量操作的情況下才會復(fù)現(xiàn)內(nèi)存問題,這樣就會給以后的測試和問題排查帶來困難和風(fēng)險。示例代碼:Cursor cursor=getContentResolver(.query(uri.;if(cursor.moveToNext(.修正示例代碼:Cursor cursor = null;trycursor=getContentResolver(.query(uri.;if(cursor!=null && cursor.moveToNext(.finallyif(cursor != nulltrycursor.close(;catch(

21、Exception e2緩存 convertView以構(gòu)造ListView的BaseAdapter為例,在BaseAdapter中提高了方法:public View getView(int position,Viewconvert View, ViewGroup parent來向ListView提供每一個item所需要的view對象。初始時ListView會從BaseAdapter中根據(jù)當(dāng)前的屏幕布局實例化一定數(shù)量的view對象,同時ListView會將這些view對象緩存起來。當(dāng)向上滾動ListView時,原先位于最上面的listitem的view對象會被回收,然后被用來構(gòu)造新出現(xiàn)的最下面的

22、listitem。這個構(gòu)造過程就是由getView(方法完成的,getView(的第二個形參View convertView就是被緩存起來的listitem的view對象(初始化時緩存中沒有view對象則convertView是null。由此可以看出,如果我們不去使用convertView,而是每次都在getView(中重新實例化一個View對象的話,即浪費資源也浪費時間,也會使得內(nèi)存占用越來越大。ListView回收listitem的view對象的過程可以查看 addScrapView(Viewscrap方法。示例代碼:public View getView(int position,Vie

23、wconvert View,ViewGroup parentView view = new Xxx(.;.return view;修正示例代碼:public View getView(int position,Viewconvert View,ViewGroup parentView view = null;if(convertView != nullview = convertView;populate(view , getItem(position;.elseview = new Xxx(.;.return view;3Bitmap對象釋放內(nèi)存有時我們會手工的操作Bitmap對象,如果一個

24、Bitmap對象比較占內(nèi)存,當(dāng)它不在被使用的時候,可以調(diào)用Bitmap.recycle(方法回收此對象的像素所占用的內(nèi)存,但這不是必須的,視情況而定。4釋放對象的引用這種情況描述起來比較麻煩,舉兩個例子進(jìn)行說明。示例A:假設(shè)有如下操作public class DemoActivity extends Activity.private Handler mHandler=.private Object obj;public void operation(obj = init Obj(;.MarkmHandler.post(new Runnable(public void run(use Obj(o

25、bj;我們有一個成員變量obj,在operation(中我們希望能夠?qū)⑻幚韔bj實例的操作post到某個線程的MessageQueue中。在以上的代碼中,即便是mHandler所在的線程使用完了obj所引用的對象,但這個對象仍然不會被垃圾回收掉,因為DemoActivity.obj還保有這個對象的引用。所以如果在DemoActivity中不再使用這個對象了,可以在Mark的位置釋放對象的引用,而代碼可以修改為:.public void operation(obj = init Obj(;.final Object o= obj;obj = null;mHandler.post(new Runn

26、able(public void run(useObj(o;.示例B:假設(shè)我們希望在鎖屏界面(LockScreen中,監(jiān)聽系統(tǒng)中的電話服務(wù)以獲取一些信息(如信號強度等,則可以在LockScreen中定義一個PhoneStateListener的對象,同時將它注冊到TelephonyManager服務(wù)中。對于LockScreen對象,當(dāng)需要顯示鎖屏界面的時候就會創(chuàng)建一個LockScreen對象,而當(dāng)鎖屏界面消失的時候LockScreen對象就會被釋放掉。但是如果在釋放LockScreen對象的時候忘記取消我們之前注冊的PhoneStateListener對象,則會導(dǎo)致LockScreen無法被垃

27、圾回收。如果不斷的使鎖屏界面顯示和消失,則最終會由于大量的LockScreen對象沒有辦法被回收而引起OutOfMemory,使得system_process進(jìn)程掛掉??傊?dāng)一個生命周期較短的對象A,被一個生命周期較長的對象B保有其引用的情況下,在A的生命周期結(jié)束時,要在B中清除掉對A的引用。5Context的使用Android應(yīng)用程序堆最大為16MB,至少在G1之前是這樣,即便沒有將這些內(nèi)存用完的打算,開發(fā)者也應(yīng)盡量減少內(nèi)存開銷以便其他應(yīng)用能夠在后臺運行而不會被強制關(guān)閉。這樣的話,Android在內(nèi)存中保存的應(yīng)用越多,用戶在應(yīng)用間的切換就越快。Android應(yīng)用程序的內(nèi)存泄露問題,大部分時間

28、里,這些問題都是源自同一個錯誤:對Context(上下文環(huán)境)的長時間引用。在Android上,Context用于多種操作,但最多的還是用來加載和訪問資源。這也是為什么所有的Widges在其構(gòu)造函數(shù)中都有一個Context參數(shù)。常規(guī)的Android應(yīng)用中,有兩類Context:ActivityContext和ApplicationContext,通常前者被開發(fā)者傳遞給需要Context的類和方法。1. Override2. Protected void onCreate(Bundle state3. super.onCreate(state;4. 5. TextView label = new

29、 TextView(this;6. label.setText("Leaks are bad"7. 8. setContentView(label;9. 這就意味著那些視圖引用了整個Activity及其所擁有的一切:一般是整個視圖層和所有資源。因此,如果泄露了這類Context(這里的泄露指的是引用Context,從而阻止了GC(垃圾回收)操作),就泄露了很多內(nèi)存空間。如果不小心,泄露整個Activity是非常容易的事。在進(jìn)行屏幕方向改變的時候,系統(tǒng)默認(rèn)做法是保持狀態(tài)不變的情況下,銷毀當(dāng)前Activity并重新創(chuàng)建一個新的Activity。這樣做,Android會從資源文件

30、中重新裝載當(dāng)前應(yīng)用的UI?,F(xiàn)在假設(shè)你寫了一個帶有很大一幅位圖的應(yīng)用,但你不想在每次屏幕旋轉(zhuǎn)時都裝載一次位圖,最簡單的做法就是將其保存在一個靜態(tài)區(qū)域中:1. Private static Drawables Background;2. 3. Override4. Protected void onCreate(Bundle state5. super.onCreate(state;6. 7. TextView label = new TextView(this;8. label.setText("Leaks are bad"9. 10. if(sBackground = nu

31、ll11. 12. 13. label.setBackgroundDrawable(sBackground;14. 15. setContentView(label;16. 這段代碼執(zhí)行的快,同時也很有問題:在進(jìn)行第一次屏幕方向改變的時候泄露了第一個Activity所占的內(nèi)存空間。當(dāng)一個Drawable連接到一個視圖上時,視圖被設(shè)置為Drawable上的一個回調(diào),在上面的代碼片段中,這就意味著Drawable引用了TextView,而TextView又引用了ActivityContext,ActivityContext又進(jìn)一步引用了更多的東西(依賴與你的代碼)。上面這段示例是最簡單的泄露Act

32、ivityContext的情況,你可以到HomeScreen'sSourceCode(查看unbindDrawables(方法)中看看我們是如何通過在Acitivity銷毀時將存儲Drawable的回調(diào)置為null來解決該問題的。如果再有興趣的話,某些情況下會產(chǎn)生一個由泄露的Context形成的鏈,這很糟糕,會很快使得內(nèi)存耗盡。有兩種方法可以避免ActivityContext相關(guān)的內(nèi)存泄露:最明顯的一種是避免在ActivityContext自身的范圍之外對其進(jìn)行引用。上面這段示例展示了靜態(tài)引用的情況,但對內(nèi)部類和外部類的隱式引用同樣都是危險的。第二種解決方法是用ApplicationC

33、ontext,因為該Context與應(yīng)用的生命周期一樣長,并不依賴Activity的生命周期。如果想擁有一個生命期足夠長的object(對象),但卻需要給其一個必須的Context的話,別忘了ApplicationObject。獲取ApplicationContext的方法很簡單:執(zhí)行Context.getApplicationContext(或Activity.getApplication(??傊?,為了避免ActivityContext相關(guān)的內(nèi)存泄露,記住下面幾條:a 不要長時間引用一個ActivityContext(引用周期應(yīng)與Acitivity的生命周期一樣長)b 嘗試使用Applica

34、tionContext代替AcitivityContextc 在Activity中,避免使用你無法控制其生命周期的非靜態(tài)的內(nèi)部類,使用靜態(tài)的內(nèi)部類,并對Activity內(nèi)部進(jìn)行弱引用。就是在靜態(tài)的內(nèi)部類中對外部類進(jìn)行弱引用,就如在ViewRoot及其W內(nèi)部類中的做法那樣d 垃圾回收(GC)無法保證內(nèi)存泄露e 使用WeakReference代替強引用。比如可以使用WeakReference mContextRef;該部分的詳細(xì)內(nèi)容也可以參考Android文檔中Article部分。6線程線程也是造成內(nèi)存泄露的一個重要的源頭。線程產(chǎn)生內(nèi)存泄露的主要原因在于線程生命周期的不可控。我們來考慮下面一段代碼

35、。Public class MyActivity extends ActivityPublic void onCreate(Bundle savedInstanceStatesuper.onCreate(savedInstanceState;newMyThread(.start(;Private class MyThread extends ThreadOverridePublic void run(super.run(;/dosomthing這段代碼很平常也很簡單,是我們經(jīng)常使用的形式。我們思考一個問題:假設(shè)MyThread的run函數(shù)是一個很費時的操作,當(dāng)我們開啟該線程后,將設(shè)備的橫屏變?yōu)?/p>

36、了豎屏,一般情況下當(dāng)屏幕轉(zhuǎn)換時會重新創(chuàng)建Activity,按照我們的想法,老的Activity應(yīng)該會被銷毀才對,然而事實上并非如此。由于我們的線程是Activity的內(nèi)部類,所以MyThread中保存了Activity的一個引用,當(dāng)MyThread的run函數(shù)沒有結(jié)束時,MyThread是不會被銷毀的,因此它所引用的老的Activity也不會被銷毀,因此就出現(xiàn)了內(nèi)存泄露的問題。有些人喜歡用Android提供的AsyncTask,但事實上AsyncTask的問題更加嚴(yán)重,Thread只有在run函數(shù)不結(jié)束時才出現(xiàn)這種內(nèi)存泄露問題,然而AsyncTask內(nèi)部的實現(xiàn)機制是運用了ThreadPoolE

37、xcutor,該類產(chǎn)生的Thread對象的生命周期是不確定的,是應(yīng)用程序無法控制的,因此如果AsyncTask作為Activity的內(nèi)部類,就更容易出現(xiàn)內(nèi)存泄露的問題。這種線程導(dǎo)致的內(nèi)存泄露問題應(yīng)該如何解決呢?第一、將線程的內(nèi)部類,改為靜態(tài)內(nèi)部類。第二、在線程內(nèi)部采用弱引用保存Context引用。解決的模型如下:Public abstract class WeakAsyncTask extendsAsyncTask Protected WeakReference mTarget;Public WeakAsyncTask(WeakTarget targetmTarget = new WeakRe

38、ference (target; OverrideProtected final void onPreExecute(Final WeakTarget target=mTarget.get(;if(target != nullthis.onPreExecute(target;OverrideProtected final Result doInBackground(Params.paramsFinal WeakTarget target=mTarget.get(;if(target!=nullreturn this.doInBackground(target,params;elseReturn

39、 null;OverrideProtected final void onPostExecute(Result resultFinal WeakTarget target=mTarget.get(;if(target != nullthis.onPostExecute(target,result;Protected void onPreExecute(WeakTarget target/NodefaultactionProtected abstract Result doInBackground(WeakTarget target,Params.params;Protected void on

40、PostExecute(WeakTarget target ,Result result/Nodefaultaction7其他Android應(yīng)用程序中最典型的需要注意釋放資源的情況是在Activity的生命周期中,在onPause(、onStop(、onDestroy(方法中需要適當(dāng)?shù)尼尫刨Y源的情況。六優(yōu)化代碼1.使用自身方法(Use Native Methods)當(dāng)處理字符串的時候,不要猶豫,盡可能多的使用諸如String.indexOf(、String.lastIndexOf(這樣對象自身帶有的方法。因為這些方法使用C/C+來實現(xiàn)的,要比在一個java循環(huán)中做同樣的事情快10-100倍。還

41、有一點要補充說明的是,這些自身方法使用的代價要比那些解釋過的方法高很多,因而,對于細(xì)微的運算,盡量不用這類方法。 2.使用虛擬優(yōu)于使用接口假設(shè)你有一個HashMap對象,你可以聲明它是一個HashMap或則只是一個Map: Java代碼Map myMap1 = new HashMap(;   HashMap myMap2 = new HashMap(;  哪一個更好呢? 一般來說明智的做法是使用Map,因為它能夠允許你改變Map接口執(zhí)行上面的任何

42、東西,但是這種“明智”的方法只是適用于常規(guī)的編程,對于嵌入式系統(tǒng)并不適合。通過接口引用來調(diào)用會花費2倍以上的時間,相對于通過具體的引用進(jìn)行虛擬函數(shù)的調(diào)用。如果你選擇使用一個HashMap,因為它更適合于你的編程,那么使用Map會毫無價值。假定你有一個能重構(gòu)你代碼的集成編碼環(huán)境,那么調(diào)用Map沒有什么用處,即使你不確定你的程序從哪開頭。(同樣,public的API是一個例外,一個好的API的價值往往大于執(zhí)行效率上的那點損失)3.使用靜態(tài)優(yōu)于使用虛擬如果你沒有必要去訪問對象的外部,那么使你的方法成為靜態(tài)方法。它會被更快的調(diào)用,因為它不需要一個虛擬函數(shù)導(dǎo)向表。這同時也是一個很好的實踐,因為它告訴你如

43、何區(qū)分方法的性質(zhì)(signature),調(diào)用這個方法不會改變對象的狀態(tài)。4.盡可能避免使用內(nèi)在的Get、Set方法C+編程語言,通常會使用Get方法(例如 i = getCount(去取代直接訪問這個屬性(i=mCount)。 這在C+編程里面是一個很好的習(xí)慣,因為編譯器會把訪問方式設(shè)置為Inline,并且如果想約束或調(diào)試屬性訪問,你只需要在任何時候添加一些代碼。在Android編程中,這不是一個很不好的主意。虛方法的調(diào)用會產(chǎn)生很多代價,比實例屬性查詢的代價還要多。我們應(yīng)該在外部調(diào)用時使用Get和Set函數(shù),但是在內(nèi)部調(diào)用時,我們應(yīng)該直接調(diào)用。5.緩沖屬性調(diào)用Cache Field Looku

44、ps訪問對象屬性要比訪問本地變量慢得多。你不應(yīng)該這樣寫你的代碼: for (int i = 0; i < this.mCount; i+         dumpItem(this.mItemsi;  而是應(yīng)該這樣寫: int count = this.mCount;   Item items = this.mI

45、tems;     for (int i = 0; i < count; i+      dumpItems(itemsi;  (我們直接使用“this”表明這些是它的成員變量)一個相似的原則就是:決不在一個For語句中第二次調(diào)用一個類的方法。例如,下面的代碼就會一次又一次地執(zhí)行g(shù)etCount()方法,這是一個極大地浪費相比你把它直接隱藏到一個Int變量中。 for (int&#

46、160;i = 0; i < this.getCount(; i+       dumpItems(this.getItem(i;  當(dāng)你不止一次的調(diào)用某個實例時,直接本地化這個實例,把這個實例中的某些值賦給一個本地變量。例如:protected void drawHorizontalScrollBar(Canvas canvas, int width, int height

47、60;          if (isHorizontalScrollBarEnabled(                int size = mScrollBar.getSize(false;           &

48、#160;   if (size <= 0                    size = mScrollBarSize;                  &

49、#160;          mScrollBar.setBounds(0, height - size, width, height;              mScrollBar.setParams( computeHorizontalScrollRange(,  computeHo

50、rizontalScrollOffset(,              computeHorizontalScrollExtent(, false;    mScrollBar.draw(canvas;   這里有四次mScrollBar的屬性調(diào)用,把mScrollBar緩沖到一個堆棧變量之中,四次成員屬性的調(diào)用就會變成四次堆棧的訪問,這樣就會提高效率。對于方法同樣也可以像本地變量一樣具有相同的特點。6.聲

51、明Final常量我們可以看看下面一個類頂部的聲明: static int intVal = 42;   static String strVal = "Hello, world!"  當(dāng)一個類第一次使用時,編譯器會調(diào)用一個類初始化方法 ,這個方法將42存入變量intVal,并且為strVal在類文件字符串常量表中提取一個引用,當(dāng)這些值在后面引用時,就會直接屬性調(diào)用。我們可以用關(guān)鍵字“final”來改進(jìn)代碼: static

52、60;final int intVal = 42;  static final String strVal = "Hello, world!"  這個類將不會調(diào)用 方法,因為這些常量直接寫入了類文件靜態(tài)屬性初始化中,這個初始化直接由虛擬機來處理。代碼訪問intVal將會使用Integer類型的42,訪問strVal將使用相對節(jié)省的“字符串常量”來替代一個屬性調(diào)用。將一個類或者方法聲明為“final”并不會帶來任何的執(zhí)行上的好處,它能夠進(jìn)行一定的最

53、優(yōu)化處理。例如,如果編譯器知道一個Get方法不能被子類重載,那么它就把該函數(shù)設(shè)置成Inline。同時,你也可以把本地變量聲明為final變量。但是,這毫無意義。作為一個本地變量,使用final只能使代碼更加清晰(或者你不得不用,在匿名訪問內(nèi)聯(lián)類時)。 7.慎重使用增強型For循環(huán)語句增強型For循環(huán)(也就是常說的“For-each循環(huán)”)經(jīng)常用于Iterable接口的繼承收集接口上面。在這些對象里面,一個iterator被分配給對象去調(diào)用它的hasNext(和next(方法。在一個數(shù)組列表里面,你可以自己接的敷衍它,在其他的收集器里面,增強型的for循環(huán)將相當(dāng)于iterator的使用

54、。 盡管如此,下面的源代碼給出了一個可以接受的增強型for循環(huán)的例子: public class Foo        int mSplat;      static Foo mArray = new Foo27;        public static void zero(&

55、#160;          int sum = 0;           for (int i = 0; i < mArray.length; i+             

56、;  sum += mArrayi.mSplat;                        public static void one(            int sum =&#

57、160;0;           Foo localArray = mArray;           int len = localArray.length;             for (int

58、0;i = 0; i < len; i+                sum += localArrayi.mSplat;                      

59、60;    public static void two(           int sum = 0;           for (Foo a: mArray         

60、0;      sum += a.mSplat;                       zero( 函數(shù)在每一次的循環(huán)中重新得到靜態(tài)屬性兩次,獲得數(shù)組長度一次。 one( 函數(shù)把所有的東西都變?yōu)楸镜刈兞浚苊忸惒檎覍傩哉{(diào)用 。two( 函數(shù)使用Java語言的1.5版本中的for循環(huán)語句,編輯者

61、產(chǎn)生的源代碼考慮到了拷貝數(shù)組的引用和數(shù)組的長度到本地變量,是例遍數(shù)組比較好的方法,它在主循環(huán)中確實產(chǎn)生了一個額外的載入和儲存過程(顯然保存了“a”),相比函數(shù)one(來說,它有一點比特上的減慢和4字節(jié)的增長??偨Y(jié)之后,我們可以得到:增強的for循環(huán)在數(shù)組里面表現(xiàn)很好,但是當(dāng)和Iterable對象一起使用時要謹(jǐn)慎,因為這里多了一個對象的創(chuàng)建。 8.避免列舉類型Avoid Enums列舉類型非常好用,當(dāng)考慮到尺寸和速度的時候,就會顯得代價很高,例如: public class Foo       p

62、ublic enum Shrubbery  GROUND, CRAWLING, HANGING      這會轉(zhuǎn)變成為一個900字節(jié)的class文件(Foo$Shrubbery.class)。第一次使用時,類的初始化要在獨享上面調(diào)用方法去描述列舉的每一項,每一個對象都要有它自身的靜態(tài)空間,整個被儲存在一個數(shù)組里面(一個叫做“$VALUE”的靜態(tài)數(shù)組)。那是一大堆的代碼和數(shù)據(jù),僅僅是為了三個整數(shù)值。 Shrubbery shrub = Shrubb

63、ery.GROUND;  這會引起一個靜態(tài)屬性的調(diào)用,如果GROUND是一個靜態(tài)的Final變量,編譯器會把它當(dāng)做一個常數(shù)嵌套在代碼里面。9.通過內(nèi)聯(lián)類使用包空間我們看下面的類聲明 public class Foo        private int mValue;         public void run(    &#

64、160;       Inner in = new Inner(;           mValue = 27;           in.stuff(;           &#

65、160;    private void doStuff(int value                     private class Inner            void stuff

66、(                            這里我們要注意的是我們定義了一個內(nèi)聯(lián)類,它調(diào)用了外部類的私有方法和私有屬性。這是合法的調(diào)用,代碼應(yīng)該會顯示"Value is 27"。 問題是Foo$Inner在理論上(后臺運行上)是應(yīng)該是一個完全獨立的類,它違規(guī)的調(diào)用了Foo的私有成員。為了彌補這個缺陷,編譯

67、器產(chǎn)生了一對合成的方法: /*package*/  static int Foo.access$100(Foo foo        return foo.mValue;      /*package*/  static void Foo.access$200(Foo foo, int value        foo.doSt

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論