




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
1、內存管理與內存溢出防范目錄內存管理與內存溢出防范 1一內存分配跟蹤工具DDMS >Allocation tracker 使用 2二內存監(jiān)測工具DDMS->Heap 2三內存分析工具MAT(MemoryAnalyzerTool 31.生成.hprof文件 42.使用MAT導入.hprof文件 53.使用MAT的視圖工具分析內存 5四MAT使用實例 51.生成heap dump 72.用MAT分析heap dumps 93.使用MAT比較heap dumps 11五防范不良代碼 111查詢數(shù)據(jù)庫沒有關閉游標 112緩存 convertView 123Bitmap對象釋放內存 134釋放
2、對象的引用 135Context的使用 146線程 177其他 20六優(yōu)化代碼 201.使用自身方法(Use Native Methods) 202.使用虛擬優(yōu)于使用接口 203.使用靜態(tài)優(yōu)于使用虛擬 204.盡可能避免使用內在的Get、Set方法 205.緩沖屬性調用Cache Field Lookups 216.聲明Final常量 217.慎重使用增強型For循環(huán)語句 228.避免列舉類型Avoid Enums 239.通過內聯(lián)類使用包空間 2310.避免浮點類型的使用 2411.一些標準操作的時間比較 2412.為響應靈敏性設計 25一內存分配跟蹤工具DDMS >Allocatio
3、n tracker 使用運行DDMS,只需簡單的選擇應用進程并單擊Allocation tracker標簽,就會打開一個新的窗口,單擊“Start Tracing”按鈕;然后,讓應用運行你想分析的代碼。運行完畢后,單擊“Get Allocations”按鈕,一個已分配對象的列表就會出現(xiàn)第一個表格中。單擊第一個表格中的任何一項,在表格二中就會出現(xiàn)導致該內存分配的棧跟蹤信息。通過allocation tracker,不僅知道分配了哪類對象,還可以知道在哪個線程、哪個類、哪個文件的哪一行。盡管在性能關鍵的代碼路徑上移除所有的內存分配操作不是必須的,甚至有時候是不可能的,但allocation tra
4、cker可以幫你識別代碼中的一些重要問題。舉例來說,許多應用中發(fā)現(xiàn)的一個普遍錯誤:每次進行繪制都創(chuàng)建一個新的Paint對象。將Paint的創(chuàng)建移到一個實例區(qū)域里,是一個能極大提高程序性能的簡單舉措。二內存監(jiān)測工具DDMS->Heap無論怎么小心,想完全避免badcode是不可能的,此時就需要一些工具來幫助我們檢查代碼中是否存在會造成內存泄漏的地方。Androidtools中的DDMS就帶有一個很不錯的內存監(jiān)測工具Heap(這里我使eclipse的ADT插件,并以真機為例,在模擬器中的情況類似。用Heap監(jiān)測應用進程使用內存情況的步驟如下:1.啟動eclipse后,切換到DDMS透視圖,并
5、確認Devices視圖、Heap視圖都是打開的;2.將手機通過USB鏈接至電腦,鏈接時需要確認手機是處于“USB調試”模式,而不是作為“MassStorage”;3.鏈接成功后,在DDMS的Devices視圖中將會顯示手機設備的序列號,以及設備中正在運行的部分進程信息;4.點擊選中想要監(jiān)測的進程,比如system_process進程;5.點擊選中Devices視圖界面中最上方一排圖標中的“UpdateHeap”圖標;6.點擊Heap視圖中的“CauseGC”按鈕;7.此時在Heap視圖中就會看到當前選中的進程的內存使用量的詳細情況a點擊“CauseGC”按鈕相當于向虛擬機請求了一次gc操作;b
6、當內存使用信息第一次顯示以后,無須再不斷的點擊“CauseGC”,Heap視圖界面會定時刷新,在對應用的不斷的操作過程中就可以看到內存使用的變化;c內存使用信息的各項參數(shù)根據(jù)名稱即可知道其意思,在此不再贅述。如何才能知道我們的程序是否有內存泄漏的可能性呢。這里需要注意一個值:Heap視圖中部有一個Type叫做dataobject,即數(shù)據(jù)對象,也就是我們的程序中大量存在的類類型的對象。在dataobject一行中有一列是“TotalSize”,其值就是當前進程中所有Java數(shù)據(jù)對象的內存總量,一般情況下,這個值的大小決定了是否會有內存泄漏??梢赃@樣判斷:a不斷的操作當前應用,同時注意觀察data
7、object的TotalSize值;b正常情況下TotalSize值都會穩(wěn)定在一個有限的范圍內,也就是說由于程序中的的代碼良好,沒有造成對象不被垃圾回收的情況,所以說雖然我們不斷的操作會不斷的生成很多對象,而在虛擬機不斷的進行GC的過程中,這些對象都被回收了,內存占用量會會落到一個穩(wěn)定的水平;c反之如果代碼中存在沒有釋放對象引用的情況,則dataobject的TotalSize值在每次GC后不會有明顯的回落,隨著操作次數(shù)的增多TotalSize的值會越來越大,直到到達一個上限后導致進程被kill掉。d此處已system_process進程為例,在我的測試環(huán)境中system_process進程所
8、占用的內存的dataobject的TotalSize正常情況下會穩(wěn)定在2.22.8之間,而當其值超過3.55后進程就會被kill。三內存分析工具MAT(MemoryAnalyzerTool如果使用DDMS確實發(fā)現(xiàn)了我們的程序中存在內存泄漏,那又如何定位到具體出現(xiàn)問題的代碼片段,最終找到問題所在呢?如果從頭到尾的分析代碼邏輯,那肯定會把人逼瘋,特別是在維護別人寫的代碼的時候。這里介紹一個極好的內存分析工具MemoryAnalyzerTool(MAT。MAT是一個Eclipse插件,同時也有單獨的RCP客戶端。官方下載地址、MAT介紹和詳細的使用教程請參見:,在此不進行說明了。另外在MAT安裝后的
9、幫助文檔里也有完備的使用教程。在此僅舉例說明其使用方法。我自己使用的是MAT的eclipse插件,使用插件要比RCP稍微方便一些。使用MAT進行內存分析需要幾個步驟,包括:生成.hprof文件、打開MAT并導入.hprof文件、使用MAT的視圖工具分析內存。以下詳細介紹。1.生成.hprof文件a 打開eclipse并切換到DDMS透視圖,同時確認Devices、Heap和logcat視圖已經(jīng)打開了;b 將手機設備鏈接到電腦,并確保使用“USB調試”模式鏈接,而不是“Mass Storage“模式;c 鏈接成功后在Devices視圖中就會看到設備的序列號,和設備中正在運行的部分進程;d 點擊選
10、中想要分析的應用的進程,在Devices視圖上方的一行圖標按鈕中,同時選中“Update Heap”和“Dump HPROF file”兩個按鈕;e 這是DDMS工具將會自動生成當前選中進程的.hprof文件,并將其進行轉換后存放在sdcard當中,如果你已經(jīng)安裝了MAT插件,那么此時MAT將會自動被啟用,并開始對.hprof文件進行分析;注意:第4步和第5步能夠正常使用前提是我們需要有sdcard,并且當前進程有向sdcard中寫入的權限(WRITE_EXTERNAL_STORAGE,否則.hprof文件不會被生成,在logcat中會顯示諸如的信息。如果我們沒有sdcard,或者當前進程沒有
11、向sdcard寫入的權限(如system_process),那我們可以這樣做:在當前程序中,例如framework中某些代碼中,可以使用中的public static void dumpHprofData(String fileName throws IOException方法,手動的指定.hprof文件的生成位置。例如:xxxButton.setOnClickListener(newView.OnClickListener(publicvoidonClick(Viewview.上述代碼意圖是希望在xxxButton被點擊的時候開始抓取內存使用信息,并保存在我們指定的位置:/data/temp
12、/myapp.hprof,這樣就沒有權限的限制了,而且也無須用sdcard。但要保證/data/temp目錄是存在的。這個路徑可以自己定義,當然也可以寫成sdcard當中的某個路徑。2.使用MAT導入.hprof文件a 如果是eclipse自動生成的.hprof文件,可以使用MAT插件直接打開(可能是比較新的ADT才支持);b 如果eclipse自動生成的.hprof文件不能被MAT直接打開,或者是使用方法手動生成的.hprof文件,則需要將.hprof文件進行轉換,轉換的方法:將.hprof文件拷貝到PC上的/ANDROID_SDK/tools目錄下,并輸入命令hprof-conv xxx.
13、hprof yyy.hprof,其中xxx.hprof為原始文件,yyy.hprof為轉換過后的文件。轉換過后的文件自動放在/ANDROID_SDK/tools目錄下。OK,到此為止,.hprof文件處理完畢,可以用來分析內存泄露情況了。c 在Eclipse中點擊Windows->OpenPerspective->Other->MemoryAnalyzer,或者打MemoryAnalyzerTool的RCP。在MAT中點擊File->OpenFile,瀏覽并導入剛剛轉換而得到的.hprof文件。3.使用MAT的視圖工具分析內存導入.hprof文件以后,MAT會自動解析并
14、生成報告,點擊DominatorTree,并按Package分組,選擇自己所定義的Package類點右鍵,在彈出菜單中選擇Listobjects->Withincomingreferences。這時會列出所有可疑類,右鍵點擊某一項,并選擇PathtoGCRoots->excludeweak/softreferences,會進一步篩選出跟程序相關的所有有內存泄露的類。據(jù)此,可以追蹤到代碼中的某一個產(chǎn)生泄露的類。具體的分析方法在MAT的官方網(wǎng)站和客戶端的幫助文檔中有十分詳盡,使用MAT分析內存查找內存泄漏的根本思路,就是找到哪個類的對象的引用沒有被釋放,找到?jīng)]有被釋放的原因,也就可以很
15、容易定位代碼中的哪些片段的邏輯有問題了。四MAT使用實例使用DDMS檢查這個應用的heap使用情況。你可以使用下面的方法啟動DDMS:From Eclipse : click Window > Open Perspective>Other.>DDMS在左邊的面板選擇進程com.founder.android.,然后在工具條上邊點擊heap updates按鈕。這個時候切換到DDMS的VMHeap分頁。它會顯示每次gc后heap內存的一些基本數(shù)據(jù)。要看第一次gc后的數(shù)據(jù)內容,點擊CauseGC按鈕:1.生成heap dump我們現(xiàn)在使用heap dump來追蹤這個問題。點擊DD
16、MS工具條上面的Dump HPROF文件按鈕,選擇文件存儲位置,然后在運行hprof-conv。如果你使用ADT(它包含DDMS的插件)同時也在eclipse里面安裝了MAT,點擊“dump HPROF”按鈕將會自動地做轉換(用hprof-conv)同時會在eclipse里面打開轉換后的hprof文件(它其實用MAT打開)。將生成的.hprof文件導入到MAT中,選擇Leak Suspects Report ,得到下圖:2.用MAT分析heap dumps啟動MAT然后加載剛才我們生成的HPROF文件。MAT是一個強大的工具,講述它所有的特性超出了本文的范圍,所以我只想演示一種你可以用來檢測泄
17、露的方法:直方圖(Histogram)視圖。它顯示了一個可以排序的類實例的列表,內容包括:shallow heap(所有實例的內存使用總和),或者retained heap(所有類實例被分配的內存總和,里面也包括他們所有引用的對象)。如果我們按照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不會明確告訴我們這就是泄露,因為它也不知道這個東西是不是程序還需要的,只有程序員知道。在這個案例里面,緩存使用的大量的內存會影響到后面的應用程序,所以我們可以考慮限制緩存的大小。3.使用MAT比較heap dumps調試內存泄露時,有時候適時比較2個地方的heap狀態(tài)是很有用的。這時你就需要生成2個單獨的HPROF文件(不要忘了轉換格式)。下面是一些關于如何在MAT里比較2個heapdumps的內容(有一點復雜):a 第一個HPROF文件(usi
19、ngFile>OpenHeapDump.b 打開Histogram view.c 在Navigation Historyview里(如果看不到就從Window>NavigationHistory找.右擊histogram然后選擇AddtoCompareBasket.d 打開第二個HPROF文件然后重做步驟2和3.e 切換到CompareBasketview,然后點擊ComparetheResults(視圖右上角的紅色"!"圖標。五防范不良代碼1查詢數(shù)據(jù)庫沒有關閉游標程序中經(jīng)常會進行查詢數(shù)據(jù)庫的操作,但是經(jīng)常會有使用完畢Cursor后沒有關閉的情況。如果我們的查詢
20、結果集比較小,對內存的消耗不容易被發(fā)現(xiàn),只有在常時間大量操作的情況下才會復現(xiàn)內存問題,這樣就會給以后的測試和問題排查帶來困難和風險。示例代碼: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以構造ListView的BaseAdapter為例,在BaseAdapter中提高了方法:public View getView(int position,Viewconvert View, ViewGroup parent來向ListView提供每一個item所需要的view對象。初始時ListView會從BaseAdapter中根據(jù)當前的屏幕布局實例化一定數(shù)量的view對象,同時ListView會將這些view對象緩存起來。當向上滾動ListView時,原先位于最上面的listitem的view對象會被回收,然后被用來構造新出現(xiàn)的最下面的
22、listitem。這個構造過程就是由getView(方法完成的,getView(的第二個形參View convertView就是被緩存起來的listitem的view對象(初始化時緩存中沒有view對象則convertView是null。由此可以看出,如果我們不去使用convertView,而是每次都在getView(中重新實例化一個View對象的話,即浪費資源也浪費時間,也會使得內存占用越來越大。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對象釋放內存有時我們會手工的操作Bitmap對象,如果一個
24、Bitmap對象比較占內存,當它不在被使用的時候,可以調用Bitmap.recycle(方法回收此對象的像素所占用的內存,但這不是必須的,視情況而定。4釋放對象的引用這種情況描述起來比較麻煩,舉兩個例子進行說明。示例A:假設有如下操作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(中我們希望能夠將處理obj實例的操作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:假設我們希望在鎖屏界面(LockScreen中,監(jiān)聽系統(tǒng)中的電話服務以獲取一些信息(如信號強度等,則可以在LockScreen中定義一個PhoneStateListener的對象,同時將它注冊到TelephonyManager服務中。對于LockScreen對象,當需要顯示鎖屏界面的時候就會創(chuàng)建一個LockScreen對象,而當鎖屏界面消失的時候LockScreen對象就會被釋放掉。但是如果在釋放LockScreen對象的時候忘記取消我們之前注冊的PhoneStateListener對象,則會導致LockScreen無法被垃
27、圾回收。如果不斷的使鎖屏界面顯示和消失,則最終會由于大量的LockScreen對象沒有辦法被回收而引起OutOfMemory,使得system_process進程掛掉。總之當一個生命周期較短的對象A,被一個生命周期較長的對象B保有其引用的情況下,在A的生命周期結束時,要在B中清除掉對A的引用。5Context的使用Android應用程序堆最大為16MB,至少在G1之前是這樣,即便沒有將這些內存用完的打算,開發(fā)者也應盡量減少內存開銷以便其他應用能夠在后臺運行而不會被強制關閉。這樣的話,Android在內存中保存的應用越多,用戶在應用間的切換就越快。Android應用程序的內存泄露問題,大部分時間
28、里,這些問題都是源自同一個錯誤:對Context(上下文環(huán)境)的長時間引用。在Android上,Context用于多種操作,但最多的還是用來加載和訪問資源。這也是為什么所有的Widges在其構造函數(shù)中都有一個Context參數(shù)。常規(guī)的Android應用中,有兩類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(垃圾回收)操作),就泄露了很多內存空間。如果不小心,泄露整個Activity是非常容易的事。在進行屏幕方向改變的時候,系統(tǒng)默認做法是保持狀態(tài)不變的情況下,銷毀當前Activity并重新創(chuàng)建一個新的Activity。這樣做,Android會從資源文件
30、中重新裝載當前應用的UI?,F(xià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í)行的快,同時也很有問題:在進行第一次屏幕方向改變的時候泄露了第一個Activity所占的內存空間。當一個Drawable連接到一個視圖上時,視圖被設置為Drawable上的一個回調,在上面的代碼片段中,這就意味著Drawable引用了TextView,而TextView又引用了ActivityContext,ActivityContext又進一步引用了更多的東西(依賴與你的代碼)。上面這段示例是最簡單的泄露Act
32、ivityContext的情況,你可以到HomeScreen'sSourceCode(查看unbindDrawables(方法)中看看我們是如何通過在Acitivity銷毀時將存儲Drawable的回調置為null來解決該問題的。如果再有興趣的話,某些情況下會產(chǎn)生一個由泄露的Context形成的鏈,這很糟糕,會很快使得內存耗盡。有兩種方法可以避免ActivityContext相關的內存泄露:最明顯的一種是避免在ActivityContext自身的范圍之外對其進行引用。上面這段示例展示了靜態(tài)引用的情況,但對內部類和外部類的隱式引用同樣都是危險的。第二種解決方法是用ApplicationC
33、ontext,因為該Context與應用的生命周期一樣長,并不依賴Activity的生命周期。如果想擁有一個生命期足夠長的object(對象),但卻需要給其一個必須的Context的話,別忘了ApplicationObject。獲取ApplicationContext的方法很簡單:執(zhí)行Context.getApplicationContext(或Activity.getApplication(??傊瑸榱吮苊釧ctivityContext相關的內存泄露,記住下面幾條:a 不要長時間引用一個ActivityContext(引用周期應與Acitivity的生命周期一樣長)b 嘗試使用Applica
34、tionContext代替AcitivityContextc 在Activity中,避免使用你無法控制其生命周期的非靜態(tài)的內部類,使用靜態(tài)的內部類,并對Activity內部進行弱引用。就是在靜態(tài)的內部類中對外部類進行弱引用,就如在ViewRoot及其W內部類中的做法那樣d 垃圾回收(GC)無法保證內存泄露e 使用WeakReference代替強引用。比如可以使用WeakReference mContextRef;該部分的詳細內容也可以參考Android文檔中Article部分。6線程線程也是造成內存泄露的一個重要的源頭。線程產(chǎn)生內存泄露的主要原因在于線程生命周期的不可控。我們來考慮下面一段代碼
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)常使用的形式。我們思考一個問題:假設MyThread的run函數(shù)是一個很費時的操作,當我們開啟該線程后,將設備的橫屏變?yōu)?/p>
36、了豎屏,一般情況下當屏幕轉換時會重新創(chuàng)建Activity,按照我們的想法,老的Activity應該會被銷毀才對,然而事實上并非如此。由于我們的線程是Activity的內部類,所以MyThread中保存了Activity的一個引用,當MyThread的run函數(shù)沒有結束時,MyThread是不會被銷毀的,因此它所引用的老的Activity也不會被銷毀,因此就出現(xiàn)了內存泄露的問題。有些人喜歡用Android提供的AsyncTask,但事實上AsyncTask的問題更加嚴重,Thread只有在run函數(shù)不結束時才出現(xiàn)這種內存泄露問題,然而AsyncTask內部的實現(xiàn)機制是運用了ThreadPoolE
37、xcutor,該類產(chǎn)生的Thread對象的生命周期是不確定的,是應用程序無法控制的,因此如果AsyncTask作為Activity的內部類,就更容易出現(xiàn)內存泄露的問題。這種線程導致的內存泄露問題應該如何解決呢?第一、將線程的內部類,改為靜態(tà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應用程序中最典型的需要注意釋放資源的情況是在Activity的生命周期中,在onPause(、onStop(、onDestroy(方法中需要適當?shù)尼尫刨Y源的情況。六優(yōu)化代碼1.使用自身方法(Use Native Methods)當處理字符串的時候,不要猶豫,盡可能多的使用諸如String.indexOf(、String.lastIndexOf(這樣對象自身帶有的方法。因為這些方法使用C/C+來實現(xiàn)的,要比在一個java循環(huán)中做同樣的事情快10-100倍。還
41、有一點要補充說明的是,這些自身方法使用的代價要比那些解釋過的方法高很多,因而,對于細微的運算,盡量不用這類方法。 2.使用虛擬優(yōu)于使用接口假設你有一個HashMap對象,你可以聲明它是一個HashMap或則只是一個Map: Java代碼Map myMap1 = new HashMap(; HashMap myMap2 = new HashMap(; 哪一個更好呢? 一般來說明智的做法是使用Map,因為它能夠允許你改變Map接口執(zhí)行上面的任何
42、東西,但是這種“明智”的方法只是適用于常規(guī)的編程,對于嵌入式系統(tǒng)并不適合。通過接口引用來調用會花費2倍以上的時間,相對于通過具體的引用進行虛擬函數(shù)的調用。如果你選擇使用一個HashMap,因為它更適合于你的編程,那么使用Map會毫無價值。假定你有一個能重構你代碼的集成編碼環(huán)境,那么調用Map沒有什么用處,即使你不確定你的程序從哪開頭。(同樣,public的API是一個例外,一個好的API的價值往往大于執(zhí)行效率上的那點損失)3.使用靜態(tài)優(yōu)于使用虛擬如果你沒有必要去訪問對象的外部,那么使你的方法成為靜態(tài)方法。它會被更快的調用,因為它不需要一個虛擬函數(shù)導向表。這同時也是一個很好的實踐,因為它告訴你如
43、何區(qū)分方法的性質(signature),調用這個方法不會改變對象的狀態(tài)。4.盡可能避免使用內在的Get、Set方法C+編程語言,通常會使用Get方法(例如 i = getCount(去取代直接訪問這個屬性(i=mCount)。 這在C+編程里面是一個很好的習慣,因為編譯器會把訪問方式設置為Inline,并且如果想約束或調試屬性訪問,你只需要在任何時候添加一些代碼。在Android編程中,這不是一個很不好的主意。虛方法的調用會產(chǎn)生很多代價,比實例屬性查詢的代價還要多。我們應該在外部調用時使用Get和Set函數(shù),但是在內部調用時,我們應該直接調用。5.緩沖屬性調用Cache Field Looku
44、ps訪問對象屬性要比訪問本地變量慢得多。你不應該這樣寫你的代碼: for (int i = 0; i < this.mCount; i+ dumpItem(this.mItemsi; 而是應該這樣寫: int count = this.mCount; Item items = this.mI
45、tems; for (int i = 0; i < count; i+ dumpItems(itemsi; (我們直接使用“this”表明這些是它的成員變量)一個相似的原則就是:決不在一個For語句中第二次調用一個類的方法。例如,下面的代碼就會一次又一次地執(zhí)行getCount()方法,這是一個極大地浪費相比你把它直接隱藏到一個Int變量中。 for (int
46、160;i = 0; i < this.getCount(; i+ dumpItems(this.getItem(i; 當你不止一次的調用某個實例時,直接本地化這個實例,把這個實例中的某些值賦給一個本地變量。例如: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的屬性調用,把mScrollBar緩沖到一個堆棧變量之中,四次成員屬性的調用就會變成四次堆棧的訪問,這樣就會提高效率。對于方法同樣也可以像本地變量一樣具有相同的特點。6.聲
51、明Final常量我們可以看看下面一個類頂部的聲明: static int intVal = 42; static String strVal = "Hello, world!" 當一個類第一次使用時,編譯器會調用一個類初始化方法 ,這個方法將42存入變量intVal,并且為strVal在類文件字符串常量表中提取一個引用,當這些值在后面引用時,就會直接屬性調用。我們可以用關鍵字“final”來改進代碼: static
52、60;final int intVal = 42; static final String strVal = "Hello, world!" 這個類將不會調用 方法,因為這些常量直接寫入了類文件靜態(tài)屬性初始化中,這個初始化直接由虛擬機來處理。代碼訪問intVal將會使用Integer類型的42,訪問strVal將使用相對節(jié)省的“字符串常量”來替代一個屬性調用。將一個類或者方法聲明為“final”并不會帶來任何的執(zhí)行上的好處,它能夠進行一定的最
53、優(yōu)化處理。例如,如果編譯器知道一個Get方法不能被子類重載,那么它就把該函數(shù)設置成Inline。同時,你也可以把本地變量聲明為final變量。但是,這毫無意義。作為一個本地變量,使用final只能使代碼更加清晰(或者你不得不用,在匿名訪問內聯(lián)類時)。 7.慎重使用增強型For循環(huán)語句增強型For循環(huán)(也就是常說的“For-each循環(huán)”)經(jīng)常用于Iterable接口的繼承收集接口上面。在這些對象里面,一個iterator被分配給對象去調用它的hasNext(和next(方法。在一個數(shù)組列表里面,你可以自己接的敷衍它,在其他的收集器里面,增強型的for循環(huán)將相當于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)楸镜刈兞浚苊忸惒檎覍傩哉{用 。two( 函數(shù)使用Java語言的1.5版本中的for循環(huán)語句,編輯者
61、產(chǎn)生的源代碼考慮到了拷貝數(shù)組的引用和數(shù)組的長度到本地變量,是例遍數(shù)組比較好的方法,它在主循環(huán)中確實產(chǎn)生了一個額外的載入和儲存過程(顯然保存了“a”),相比函數(shù)one(來說,它有一點比特上的減慢和4字節(jié)的增長。總結之后,我們可以得到:增強的for循環(huán)在數(shù)組里面表現(xiàn)很好,但是當和Iterable對象一起使用時要謹慎,因為這里多了一個對象的創(chuàng)建。 8.避免列舉類型Avoid Enums列舉類型非常好用,當考慮到尺寸和速度的時候,就會顯得代價很高,例如: public class Foo p
62、ublic enum Shrubbery GROUND, CRAWLING, HANGING 這會轉變成為一個900字節(jié)的class文件(Foo$Shrubbery.class)。第一次使用時,類的初始化要在獨享上面調用方法去描述列舉的每一項,每一個對象都要有它自身的靜態(tài)空間,整個被儲存在一個數(shù)組里面(一個叫做“$VALUE”的靜態(tài)數(shù)組)。那是一大堆的代碼和數(shù)據(jù),僅僅是為了三個整數(shù)值。 Shrubbery shrub = Shrubb
63、ery.GROUND; 這會引起一個靜態(tài)屬性的調用,如果GROUND是一個靜態(tài)的Final變量,編譯器會把它當做一個常數(shù)嵌套在代碼里面。9.通過內聯(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、( 這里我們要注意的是我們定義了一個內聯(lián)類,它調用了外部類的私有方法和私有屬性。這是合法的調用,代碼應該會顯示"Value is 27"。 問題是Foo$Inner在理論上(后臺運行上)是應該是一個完全獨立的類,它違規(guī)的調用了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)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 國潮漢服美食節(jié)活動方案
- 周邊飲品活動策劃方案
- 國慶酬賓活動方案
- 員工烤肉活動策劃方案
- 國畫特長展示活動方案
- 咖啡策劃活動方案
- 國學教育推廣活動方案
- 商場滿就送活動方案
- 員工讀書會策劃活動方案
- 車位開盤定價方案
- 2024年中國三回程烘干機市場調查研究報告
- 國開(北京)2024年秋《財務案例分析》形考作業(yè)答案
- DB52T 1512-2020 水利水電工程隧洞施工超前地質預報技術規(guī)程
- GB/T 44831-2024皮膚芯片通用技術要求
- 精神科火災演練腳本
- 汽輪發(fā)電機組設備運行記錄日報表(正面) A2
- 15J403-1-樓梯欄桿欄板(一)
- 2024年婦幼健康“三基”培訓考試復習題庫-下(多選、判斷題)
- 子癇的搶救和護理
- 2025年高考政治一輪復習:統(tǒng)編版必修3《政治與法治》必背考點知識講義
評論
0/150
提交評論