DDMS的使用、内存溢出的调试和模拟器的启动命令参数
DDMS 的全稱是Dalvik Debug Monitor Service,它為我們提供例如:為測(cè)試設(shè)備截屏,針對(duì)特定的進(jìn)程查看正在運(yùn)行的線程以及堆信息、Logcat、廣播狀態(tài)信息、模擬電話呼叫、接收 SMS、虛擬地理坐標(biāo)等等。DDMS為IDE和emultor及真正的android設(shè)備架起來(lái)了一座橋梁。開(kāi)發(fā)人員可以通過(guò)DDMS看到目標(biāo)機(jī)器上運(yùn)行 的進(jìn)程/現(xiàn)成狀態(tài),可以android的屏幕到開(kāi)發(fā)機(jī)上,可以看進(jìn)程的heap信息,可以查看logcat信息,可以查看進(jìn)程分配內(nèi)存情況,可以像目標(biāo)機(jī) 發(fā)送短信以及打電話,可以像android開(kāi)發(fā)發(fā)送地理位置信息。可以像GDB一樣attach某一個(gè)進(jìn)程調(diào)試。 SDKàtools目錄下提供了ddms的完整版,直接雙擊ddms.bat運(yùn)行即可。下面以Eclipse的DDMS perspective為例簡(jiǎn)單介紹DDMS的功能。
????? 跟debug,java的perspective一樣,安裝好adt后會(huì)有一個(gè)DDMS得perspective,打開(kāi)即可。
????? 如果perspective里沒(méi)有顯示DDMS,剛按如下步驟執(zhí)行:
- ????? 點(diǎn)擊上圖中DDMS圖標(biāo)左邊的那個(gè)圖標(biāo),然后在下圖中如果有DDMS,剛選擇,如果沒(méi)有,剛選擇“其他”,然后在出現(xiàn)的窗口中雙擊“DDMS”即可。
????? 注意:DDMS對(duì)Emulator和外接測(cè)試機(jī)有同等效用。如果系統(tǒng)檢測(cè)到它們(VM)同時(shí)運(yùn)行,那么DDMS將會(huì)默認(rèn)指向 Emulator。以上2種啟動(dòng)后的操作有些不一樣,建議分別嘗試下。
????? DDMS?的工作原理
????? DDMS將搭建起IDE與測(cè)試終端(Emulator 或者connected device)的鏈接,它們應(yīng)用各自獨(dú)立的端口監(jiān)聽(tīng)調(diào)試器的信息,DDMS可以實(shí)時(shí)監(jiān)測(cè)到測(cè)試終端的連接情況。當(dāng)有新的測(cè)試終端連接后,DDMS將捕捉到 終端的ID,并通過(guò)adb建立調(diào)試器,從而實(shí)現(xiàn)發(fā)送指令到測(cè)試終端的目的。
????? DDMS監(jiān)聽(tīng)第一個(gè)終端App進(jìn)程的端口為8600,APP進(jìn)程將分配8601,如果有更多終端或者更多APP進(jìn)程將按照這個(gè)順序依次類推。DDMS通過(guò)8700端口(“base port”)接收所有終端的指令。
????? 打開(kāi)后的窗口為:
????? 下邊通過(guò)GUI詳細(xì)了解DDMS的一些功能
????? 在GUI的左上角可以看到標(biāo)簽為”Devices”的面板,這里可以查看到所有與DDMS連 接的終端的詳細(xì)信息,以及每個(gè)終端正在運(yùn)行的APP進(jìn)程,每個(gè)進(jìn)程最右邊相對(duì)應(yīng)的是與調(diào)試器鏈接的端口。因?yàn)锳ndroid是基于Linux內(nèi)核開(kāi)發(fā)的操 作平臺(tái),同時(shí)也保留了Linux中特有的進(jìn)程ID,它介于進(jìn)程名和端口號(hào)之間。
????? device窗口羅列模擬器中所有的進(jìn)程,右上角那一排按鈕分別為:調(diào)試某個(gè)進(jìn)程,更新某個(gè)進(jìn)程,更新進(jìn)程堆棧信息,停止某個(gè)進(jìn)程,最后一個(gè)圖片按鈕時(shí)抓取android目前的屏幕。
????? 當(dāng)你選中某個(gè)進(jìn)程,并按下調(diào)試進(jìn)程按鈕時(shí),如果eclipse中有這個(gè)進(jìn)程的代碼,那就可以進(jìn)行源代碼級(jí)別的調(diào)試。有點(diǎn)像GDB attach。圖片抓取按鈕可以把當(dāng)前android的顯示桌面抓到你的機(jī)器上,也是非常有用。
????? 右邊那個(gè)窗口中有threads, heap , file explorer選項(xiàng)卡。分別顯示線程統(tǒng)計(jì)信息,棧信息,以及android的文件系統(tǒng)。
????? file explorer非常有用,他可以把文件上傳到android手機(jī),或者從手機(jī)下載下來(lái),也可以進(jìn)行刪除操作。選中file explorer選項(xiàng)卡后,按下面三個(gè)按鈕便可實(shí)現(xiàn)對(duì)android手機(jī)文件系統(tǒng)的上傳,下載,刪除操作。
????? emulator control也是非常重要的,通過(guò)它可以像手機(jī)發(fā)送短信, 打電話,已經(jīng)更新手機(jī)位置信息。
??????????? Telephony Status: 通過(guò)選項(xiàng)模擬語(yǔ)音質(zhì)量以及信號(hào)連接模式。?
??????????? Telephony Actions: 模擬電話接聽(tīng)和發(fā)送SMS到測(cè)試終端。?
??????????? Location Control: 模擬地理坐標(biāo)或者模擬動(dòng)態(tài)的路線坐標(biāo)變化并顯示預(yù)設(shè)的地理標(biāo)識(shí),可以通過(guò)以下3種方式:
????????????????? · Manual: 手動(dòng)為終端發(fā)送二維經(jīng)緯坐標(biāo)。
????????????????? · GPX: 通過(guò)GPX文件導(dǎo)入序列動(dòng)態(tài)變化地理坐標(biāo),從而模擬行進(jìn)中GPS變化的數(shù)值。
????????????????? · KML: 通過(guò)KML文件導(dǎo)入獨(dú)特的地理標(biāo)識(shí),并以動(dòng)態(tài)形式根據(jù)變化的地理坐標(biāo)顯示在測(cè)試終端。
????? LogCat:顯示輸出的調(diào)試信息。
????? Console(控制臺(tái)):是Android模擬器輸出的信息,加載程序等信息;
??????總結(jié):
??????????? eclipse adt目前提供的的ddms功能只是真正ddms的一小部分,你 可以直接使用tools下面的ddms來(lái)使用所有功能。其中有一個(gè)查看進(jìn)程內(nèi)存分配的功能比較有用。
??????????? 另個(gè)要注意的是,在DDMS中模擬發(fā)送短信時(shí),中文顯示為亂碼,在未來(lái)的開(kāi)發(fā)中,我們必須要注意中文字符的問(wèn)題
?
?
?
Android?內(nèi)存泄漏調(diào)試
?
一、概述
????Java編程中經(jīng)常容易被忽視,但本身又十分重要的一個(gè)問(wèn)題就是內(nèi)存使用的問(wèn)題。Android應(yīng)用主要使用Java語(yǔ)言編寫,因此這個(gè)問(wèn)題也同 樣會(huì)在Android開(kāi)發(fā)中出現(xiàn)。本文不對(duì)Java編程問(wèn)題做探討,而是對(duì)于在Android中,特別是應(yīng)用開(kāi)發(fā)中的此類問(wèn)題進(jìn)行整理。
????由于作者接觸Android時(shí)間并不是很長(zhǎng),因此如有敘述不當(dāng)之處,歡迎指正。
?
二、Android(Java)中常見(jiàn)的容易引起內(nèi)存泄漏的不良代碼
?
????Android主要應(yīng)用在嵌入式設(shè)備當(dāng)中,而嵌入式設(shè)備由于一些眾所周知的條件限制,通常都不會(huì)有很高的配置,特別是內(nèi)存是比較有限的。如果我們 編寫的代碼當(dāng)中有太多的對(duì)內(nèi)存使用不當(dāng)?shù)牡胤?#xff0c;難免會(huì)使得我們的設(shè)備運(yùn)行緩慢,甚至是死機(jī)。為了能夠使得Android應(yīng)用程序安全且快速的運(yùn) 行,Android的每個(gè)應(yīng)用程序都會(huì)使用一個(gè)專有的Dalvik虛擬機(jī)實(shí)例來(lái)運(yùn)行,它是由Zygote服務(wù)進(jìn)程孵化出來(lái)的,也就是說(shuō)每個(gè)應(yīng)用程序都是在 屬于自己的進(jìn)程中運(yùn)行的。一方面,如果程序在運(yùn)行過(guò)程中出現(xiàn)了內(nèi)存泄漏的問(wèn)題,僅僅會(huì)使得自己的進(jìn)程被kill掉,而不會(huì)影響其他進(jìn)程(如果是 system_process等系統(tǒng)進(jìn)程出問(wèn)題的話,則會(huì)引起系統(tǒng)重啟)。另一方面Android為不同類型的進(jìn)程分配了不同的內(nèi)存使用上限,如果應(yīng)用進(jìn) 程使用的內(nèi)存超過(guò)了這個(gè)上限,則會(huì)被系統(tǒng)視為內(nèi)存泄漏,從而被kill掉。Android為應(yīng)用進(jìn)程分配的內(nèi)存上限如下所示:
位置:?/ANDROID_SOURCE/system/core/rootdir/init.rc?部分腳本
#?Define?the?oom_adj?values?for?the?classes?of?processes?that?can?be
#?killed?by?the?kernel.??These?are?used?in?ActivityManagerService.
????setprop?ro.FOREGROUND_APP_ADJ?0
????setprop?ro.VISIBLE_APP_ADJ?1
????setprop?ro.SECONDARY_SERVER_ADJ?2
????setprop?ro.BACKUP_APP_ADJ?2
????setprop?ro.HOME_APP_ADJ?4
????setprop?ro.HIDDEN_APP_MIN_ADJ?7
????setprop?ro.CONTENT_PROVIDER_ADJ?14
????setprop?ro.EMPTY_APP_ADJ?15
?
#?Define?the?memory?thresholds?at?which?the?above?process?classes?will
#?be?killed.??These?numbers?are?in?pages?(4k).
????setprop?ro.FOREGROUND_APP_MEM?1536
????setprop?ro.VISIBLE_APP_MEM?2048
????setprop?ro.SECONDARY_SERVER_MEM?4096
????setprop?ro.BACKUP_APP_MEM?4096
????setprop?ro.HOME_APP_MEM?4096
????setprop?ro.HIDDEN_APP_MEM?5120
????setprop?ro.CONTENT_PROVIDER_MEM?5632
????setprop?ro.EMPTY_APP_MEM?6144
?
#?Write?value?must?be?consistent?with?the?above?properties.
#?Note?that?the?driver?only?supports?6?slots,?so?we?have?HOME_APP?at?the
#?same?memory?level?as?services.
????write?/sys/module/lowmemorykiller/parameters/adj?0,1,2,7,14,15
?
????write?/proc/sys/vm/overcommit_memory?1
????write?/proc/sys/vm/min_free_order_shift?4
????write?/sys/module/lowmemorykiller/parameters/minfree?1536,2048,4096,5120,5632,6144
?
????#?Set?init?its?forked?children's?oom_adj.
????write?/proc/1/oom_adj?-16
?
????正因?yàn)槲覀兊膽?yīng)用程序能夠使用的內(nèi)存有限,所以在編寫代碼的時(shí)候需要特別注意內(nèi)存使用問(wèn)題。如下是一些常見(jiàn)的內(nèi)存使用不當(dāng)?shù)那闆r。
?
(一)?查詢數(shù)據(jù)庫(kù)沒(méi)有關(guān)閉游標(biāo)
描述:
????程序中經(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)。
?
示例代碼:
Cursor?cursor?=?getContentResolver().query(uri?...);
if?(cursor.moveToNext())?{
????...?...?
}
?
修正示例代碼:
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
????????}
????}
}?
?
(二)?構(gòu)造Adapter時(shí),沒(méi)有使用緩存的?convertView
?
描述:
????以構(gòu)造ListView的BaseAdapter為例,在BaseAdapter中提高了方法:
public?View?getView(int?position,?View?convertView,?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?-->?void?addScrapView(View?scrap)?方法。
?
示例代碼:
public?View?getView(int?position,?View?convertView,?ViewGroup?parent)?{
????View?view?=?new?Xxx(...);
????...?...
????return?view;
}
?
修正示例代碼:
public?View?getView(int?position,?View?convertView,?ViewGroup?parent)?{
????View?view?=?null;
????if?(convertView?!=?null)?{
????????view?=?convertView;
????????populate(view,?getItem(position));
????????...
????}?else?{
????????view?=?new?Xxx(...);
????????...
????}
????return?view;
}?
?
(三)?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)存,但這不是必須的,視情況而定。可以看一下代碼中的注釋:
????/**
?????*?Free?up?the?memory?associated?with?this?bitmap's?pixels,?and?mark?the
?????*?bitmap?as?"dead",?meaning?it?will?throw?an?exception?if?getPixels()?or
?????*?setPixels()?is?called,?and?will?draw?nothing.?This?operation?cannot?be
?????*?reversed,?so?it?should?only?be?called?if?you?are?sure?there?are?no
?????*?further?uses?for?the?bitmap.?This?is?an?advanced?call,?and?normally?need
?????*?not?be?called,?since?the?normal?GC?process?will?free?up?this?memory?when
?????*?there?are?no?more?references?to?this?bitmap.
?????*/
?
(四)?釋放對(duì)象的引用
?
描述:
????這種情況描述起來(lái)比較麻煩,舉兩個(gè)例子進(jìn)行說(shuō)明。
示例A:
假設(shè)有如下操作
public?class?DemoActivity?extends?Activity?{
????...?...
????private?Handler?mHandler?=?...
????private?Object?obj;
????public?void?operation()?{
?????obj?=?initObj();
?????...
?????[Mark]
?????mHandler.post(new?Runnable()?{
????????????public?void?run()?{
?????????????useObj(obj);
????????????}
?????});
????}
}
????我們有一個(gè)成員變量?obj,在operation()中我們希望能夠?qū)⑻幚韔bj實(shí)例的操作post到某個(gè)線程的MessageQueue中。 在以上的代碼中,即便是mHandler所在的線程使用完了obj所引用的對(duì)象,但這個(gè)對(duì)象仍然不會(huì)被垃圾回收掉,因?yàn)镈emoActivity.obj 還保有這個(gè)對(duì)象的引用。所以如果在DemoActivity中不再使用這個(gè)對(duì)象了,可以在[Mark]的位置釋放對(duì)象的引用,而代碼可以修改為:
...?...
public?void?operation()?{
????obj?=?initObj();
????...
????final?Object?o?=?obj;
????obj?=?null;
????mHandler.post(new?Runnable()?{
????????public?void?run()?{
????????????useObj(o);
????????}
????}
}
...?...
?
示例B:
????假設(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)程掛掉。
????總之當(dāng)一個(gè)生命周期較短的對(duì)象A,被一個(gè)生命周期較長(zhǎng)的對(duì)象B保有其引用的情況下,在A的生命周期結(jié)束時(shí),要在B中清除掉對(duì)A的引用。
?
(五)?其他
?
????Android應(yīng)用程序中最典型的需要注意釋放資源的情況是在Activity的生命周期中,在onPause()、onStop()、 onDestroy()方法中需要適當(dāng)?shù)尼尫刨Y源的情況。由于此情況很基礎(chǔ),在此不詳細(xì)說(shuō)明,具體可以查看官方文檔對(duì)Activity生命周期的介紹,以 明確何時(shí)應(yīng)該釋放哪些資源。
?
?
三、內(nèi)存監(jiān)測(cè)工具?DDMS?-->?Heap
?
????無(wú)論怎么小心,想完全避免bad?code是不可能的,此時(shí)就需要一些工具來(lái)幫助我們檢查代碼中是否存在會(huì)造成內(nèi)存泄漏的地方。 Android?tools中的DDMS就帶有一個(gè)很不錯(cuò)的內(nèi)存監(jiān)測(cè)工具Heap(這里我使用eclipse的ADT插件,并以真機(jī)為例,在模擬器中的情 況類似)。用Heap監(jiān)測(cè)應(yīng)用進(jìn)程使用內(nèi)存情況的步驟如下:
1.?啟動(dòng)eclipse后,切換到DDMS透視圖,并確認(rèn)Devices視圖、Heap視圖都是打開(kāi)的;
2.?將手機(jī)通過(guò)USB鏈接至電腦,鏈接時(shí)需要確認(rèn)手機(jī)是處于“USB調(diào)試”模式,而不是作為“Mass?Storage”;
3.?鏈接成功后,在DDMS的Devices視圖中將會(huì)顯示手機(jī)設(shè)備的序列號(hào),以及設(shè)備中正在運(yùn)行的部分進(jìn)程信息;
4.?點(diǎn)擊選中想要監(jiān)測(cè)的進(jìn)程,比如system_process進(jìn)程;
5.?點(diǎn)擊選中Devices視圖界面中最上方一排圖標(biāo)中的“Update?Heap”圖標(biāo);
6.?點(diǎn)擊Heap視圖中的“Cause?GC”按鈕;
7.?此時(shí)在Heap視圖中就會(huì)看到當(dāng)前選中的進(jìn)程的內(nèi)存使用量的詳細(xì)情況[如圖所示]。
?
?
說(shuō)明:
a)?點(diǎn)擊“Cause?GC”按鈕相當(dāng)于向虛擬機(jī)請(qǐng)求了一次gc操作;
b)?當(dāng)內(nèi)存使用信息第一次顯示以后,無(wú)須再不斷的點(diǎn)擊“Cause?GC”,Heap視圖界面會(huì)定時(shí)刷新,在對(duì)應(yīng)用的不斷的操作過(guò)程中就可以看到內(nèi)存使用的變化;
c)?內(nèi)存使用信息的各項(xiàng)參數(shù)根據(jù)名稱即可知道其意思,在此不再贅述。
?
????如何才能知道我們的程序是否有內(nèi)存泄漏的可能性呢。這里需要注意一個(gè)值:Heap視圖中部有一個(gè)Type叫做data?object,即數(shù)據(jù)對(duì) 象,也就是我們的程序中大量存在的類類型的對(duì)象。在data?object一行中有一列是“Total?Size”,其值就是當(dāng)前進(jìn)程中所有Java數(shù)據(jù) 對(duì)象的內(nèi)存總量,一般情況下,這個(gè)值的大小決定了是否會(huì)有內(nèi)存泄漏。可以這樣判斷:
a)?不斷的操作當(dāng)前應(yīng)用,同時(shí)注意觀察data?object的Total?Size值;
b)?正常情況下Total?Size值都會(huì)穩(wěn)定在一個(gè)有限的范圍內(nèi),也就是說(shuō)由于程序中的的代碼良好,沒(méi)有造成對(duì)象不被垃圾回收的情況,所以說(shuō)雖然我們 不斷的操作會(huì)不斷的生成很多對(duì)象,而在虛擬機(jī)不斷的進(jìn)行GC的過(guò)程中,這些對(duì)象都被回收了,內(nèi)存占用量會(huì)會(huì)落到一個(gè)穩(wěn)定的水平;
c)?反之如果代碼中存在沒(méi)有釋放對(duì)象引用的情況,則data?object的Total?Size值在每次GC后不會(huì)有明顯的回落,隨著操作次數(shù)的增多Total?Size的值會(huì)越來(lái)越大,
????直到到達(dá)一個(gè)上限后導(dǎo)致進(jìn)程被kill掉。
d)?此處已system_process進(jìn)程為例,在我的測(cè)試環(huán)境中system_process進(jìn)程所占用的內(nèi)存的data?object的Total?Size正常情況下會(huì)穩(wěn)定在2.2~2.8之間,而當(dāng)其值超過(guò)3.55后進(jìn)程就會(huì)被kill。
?
????總之,使用DDMS的Heap視圖工具可以很方便的確認(rèn)我們的程序是否存在內(nèi)存泄漏的可能性。
?
四、內(nèi)存分析工具?MAT(Memory?Analyzer?Tool)
????如果使用DDMS確實(shí)發(fā)現(xiàn)了我們的程序中存在內(nèi)存泄漏,那又如何定位到具體出現(xiàn)問(wèn)題的代碼片段,最終找到問(wèn)題所在呢?如果從頭到尾的分析代碼邏 輯,那肯定會(huì)把人逼瘋,特別是在維護(hù)別人寫的代碼的時(shí)候。這里介紹一個(gè)極好的內(nèi)存分析工具?--?Memory?Analyzer?Tool(MAT)。
????MAT是一個(gè)Eclipse插件,同時(shí)也有單獨(dú)的RCP客戶端。官方下載地址、MAT介紹和詳細(xì)的使用教程請(qǐng)參 見(jiàn):www.eclipse.org/mat,在此不進(jìn)行說(shuō)明了。另外在MAT安裝后的幫助文檔里也有完備的使用教程。在此僅舉例說(shuō)明其使用方法。我自己 使用的是MAT的eclipse插件,使用插件要比RCP稍微方便一些。
?
????使用MAT進(jìn)行內(nèi)存分析需要幾個(gè)步驟,包括:生成.hprof文件、打開(kāi)MAT并導(dǎo)入.hprof文件、使用MAT的視圖工具分析內(nèi)存。以下詳細(xì)介紹。
?
(一)?生成.hprof文件
?
????生成.hprof文件的方法有很多,而且Android的不同版本中生成.hprof的方式也稍有差別,我使用的版本的是2.1,各個(gè)版本中生成.prof文件的方法請(qǐng)參考:
http://android.git.kernel.org/?p=platform/dalvik.git;a=blob_plain;f=docs/heap-profiling.html;hb=HEAD。
1.?打開(kāi)eclipse并切換到DDMS透視圖,同時(shí)確認(rèn)Devices、Heap和logcat視圖已經(jīng)打開(kāi)了;
2.?將手機(jī)設(shè)備鏈接到電腦,并確保使用“USB?調(diào)試”模式鏈接,而不是“Mass?Storage“模式;
3.?鏈接成功后在Devices視圖中就會(huì)看到設(shè)備的序列號(hào),和設(shè)備中正在運(yùn)行的部分進(jìn)程;
4.?點(diǎn)擊選中想要分析的應(yīng)用的進(jìn)程,在Devices視圖上方的一行圖標(biāo)按鈕中,同時(shí)選中“Update?Heap”和“Dump?HPROF?file”兩個(gè)按鈕;
5.?這是DDMS工具將會(huì)自動(dòng)生成當(dāng)前選中進(jìn)程的.hprof文件,并將其進(jìn)行轉(zhuǎn)換后存放在sdcard當(dāng)中,如果你已經(jīng)安裝了MAT插件,那么此時(shí)MAT將會(huì)自動(dòng)被啟用,并開(kāi)始對(duì).hprof文件進(jìn)行分析;
????注意:第4步和第5步能夠正常使用前提是我們需要有sdcard,并且當(dāng)前進(jìn)程有向sdcard中寫入的權(quán)限(WRITE_EXTERNAL_STORAGE),否則.hprof文件不會(huì)被生成,在logcat中會(huì)顯示諸如
?????ERROR/dalvikvm(8574):?hprof:?can't?open?/sdcard/com.xxx.hprof-hptemp:?Permission?denied.?
????的信息。
???
????如果我們沒(méi)有sdcard,或者當(dāng)前進(jìn)程沒(méi)有向sdcard寫入的權(quán)限(如system_process),那我們可以這樣做:
6.?在當(dāng)前程序中,例如framework中某些代碼中,可以使用android.os.Debug中的:
???public?static?void?dumpHprofData(String?fileName)?throws?IOException
???方法,手動(dòng)的指定.hprof文件的生成位置。例如:
???xxxButton.setOnClickListener(new?View.OnClickListener()?{
???????public?void?onClick(View?view)?{
??????????android.os.Debug.dumpHprofData("/data/temp/myapp.hprof");
??????????...?...
???????}
???}
????上述代碼意圖是希望在xxxButton被點(diǎn)擊的時(shí)候開(kāi)始抓取內(nèi)存使用信息,并保存在我們指定的位置:/data/temp /myapp.hprof,這樣就沒(méi)有權(quán)限的限制了,而且也無(wú)須用sdcard。但要保證/data/temp目錄是存在的。這個(gè)路徑可以自己定義,當(dāng)然 也可以寫成sdcard當(dāng)中的某個(gè)路徑。
?
(二)?使用MAT導(dǎo)入.hprof文件
?
1.?如果是eclipse自動(dòng)生成的.hprof文件,可以使用MAT插件直接打開(kāi)(可能是比較新的ADT才支持);
2.?如果eclipse自動(dòng)生成的.hprof文件不能被MAT直接打開(kāi),或者是使用android.os.Debug.dumpHprofData()方法手動(dòng)生成的.hprof文件,則需要將.hprof文件進(jìn)行轉(zhuǎn)換,轉(zhuǎn)換的方法:
????例如我將.hprof文件拷貝到PC上的/ANDROID_SDK/tools目錄下,并輸入命令hprof- conv?xxx.hprof?yyy.hprof,其中xxx.hprof為原始文件,yyy.hprof為轉(zhuǎn)換過(guò)后的文件。轉(zhuǎn)換過(guò)后的文件自動(dòng)放在 /ANDROID_SDK/tools目錄下。OK,到此為止,.hprof文件處理完畢,可以用來(lái)分析內(nèi)存泄露情況了。
3.?在Eclipse中點(diǎn)擊 Windows->Open?Perspective->Other->Memory?Analyzer,或者打 Memory?Analyzer?Tool的RCP。在MAT中點(diǎn)擊File->Open?File,瀏覽并導(dǎo)入剛剛轉(zhuǎn)換而得到的.hprof文 件。
?
(三)?使用MAT的視圖工具分析內(nèi)存
?
????導(dǎo)入.hprof文件以后,MAT會(huì)自動(dòng)解析并生成報(bào)告,點(diǎn)擊Dominator?Tree,并按Package分組,選擇自己所定義的 Package類點(diǎn)右鍵,在彈出菜單中選擇List?objects->With?incoming?references。這時(shí)會(huì)列出所有可疑 類,右鍵點(diǎn)擊某一項(xiàng),并選擇Path?to?GC?Roots?->?exclude?weak/soft?references,會(huì)進(jìn)一步篩選出 跟程序相關(guān)的所有有內(nèi)存泄露的類。據(jù)此,可以追蹤到代碼中的某一個(gè)產(chǎn)生泄露的類。
????MAT的界面如下圖所示。
?
?
????具體的分析方法在此不做說(shuō)明了,因?yàn)樵贛AT的官方網(wǎng)站和客戶端的幫助文檔中有十分詳盡的介紹。
????了解MAT中各個(gè)視圖的作用很重要,例如www.eclipse.org/mat/about/screenshots.php中介紹的。
??
????總之使用MAT分析內(nèi)存查找內(nèi)存泄漏的根本思路,就是找到哪個(gè)類的對(duì)象的引用沒(méi)有被釋放,找到?jīng)]有被釋放的原因,也就可以很容易定位代碼中的哪些片段的邏輯有問(wèn)題了
?
?
用 Heap監(jiān)測(cè)應(yīng)用進(jìn)程使用內(nèi)存情況的步驟如下:1. 啟動(dòng)eclipse后,切換到DDMS透視圖,并確認(rèn)Devices視圖、Heap視圖都是打開(kāi)的;
2. 將手機(jī)通過(guò)USB鏈接至電腦,鏈接時(shí)需要確認(rèn)手機(jī)是處于“USB調(diào)試”模式,而不是作為“Mass Storage”;
3. 鏈接成功后,在DDMS的Devices視圖中將會(huì)顯示手機(jī)設(shè)備的序列號(hào),以及設(shè)備中正在運(yùn)行的部分進(jìn)程信息;
4. 點(diǎn)擊選中想要監(jiān)測(cè)的進(jìn)程,比如system_process進(jìn)程;
5. 點(diǎn)擊選中Devices視圖界面中最上方一排圖標(biāo)中的“Update Heap”圖標(biāo);
6. 點(diǎn)擊Heap視圖中的“Cause GC”按鈕;
7. 此時(shí)在Heap視圖中就會(huì)看到當(dāng)前選中的進(jìn)程的內(nèi)存使用量的詳細(xì)情況。
說(shuō)明:
a) 點(diǎn)擊“Cause GC”按鈕相當(dāng)于向虛擬機(jī)請(qǐng)求了一次gc操作;
b) 當(dāng)內(nèi)存使用信息第一次顯示以后,無(wú)須再不斷的點(diǎn)擊“Cause GC”,Heap視圖界面會(huì)定時(shí)刷新,在對(duì)應(yīng)用的不斷的操作過(guò)程中就可以看到內(nèi)存使用的變化;
c) 內(nèi)存使用信息的各項(xiàng)參數(shù)根據(jù)名稱即可知道其意思,在此不再贅述。
??如何才能知道我們的程序是否有內(nèi)存泄漏的可能性呢。這里需要注意一個(gè)值:Heap視圖中部有一個(gè)Type叫做data object,即數(shù)據(jù)對(duì)象,也就是我們的程序中大量存在的類類型的對(duì)象。在data object一行中有一列是“Total Size”,其值就是當(dāng)前進(jìn)程中所有Java數(shù)據(jù)對(duì)象的內(nèi)存總量,一般情況下,這個(gè)值的大小決定了是否會(huì)有內(nèi)存泄漏。可以這樣判斷:
a) 不斷的操作當(dāng)前應(yīng)用,同時(shí)注意觀察data object的Total Size值;
b) 正常情況下Total Size值都會(huì)穩(wěn)定在一個(gè)有限的范圍內(nèi),也就是說(shuō)由于程序中的的代碼良好,沒(méi)有造成對(duì)象不被垃圾回收的情況,所以說(shuō)雖然我們不斷的操作會(huì)不斷的生成很多對(duì) 象,而在虛擬機(jī)不斷的進(jìn)行GC的過(guò)程中,這些對(duì)象都被回收了,內(nèi)存占用量會(huì)會(huì)落到一個(gè)穩(wěn)定的水平;
c) 反之如果代碼中存在沒(méi)有釋放對(duì)象引用的情況,則data object的Total Size值在每次GC后不會(huì)有明顯的回落,隨著操作次數(shù)的增多Total Size的值會(huì)越來(lái)越大,
??直到到達(dá)一個(gè)上限后導(dǎo)致進(jìn)程被kill掉。
d) 此處已system_process進(jìn)程為例,在我的測(cè)試環(huán)境中system_process進(jìn)程所占用的內(nèi)存的data object的Total Size正常情況下會(huì)穩(wěn)定在2.2~2.8之間,而當(dāng)其值超過(guò)3.55后進(jìn)程就會(huì)被kill。
?
來(lái)自:?http://apps.hi.baidu.com/share/detail/32190286
?
?
?
?
在DDMS里檢查heap的使用情況
?
Dalvik Debug Monitor Server(DDMS)是主要的Android調(diào)試工具之一,也是ADT Eclipse plug-in?的一部分,獨(dú)立的程序版本也可以在Android SDK的根目錄下的tools/下面找到。關(guān)于DDMS更多的信息,請(qǐng)參考使用DDMS?。
?
我們來(lái)使用DDMS檢查這個(gè)應(yīng)用的heap使用情況。你可以使用下面的兩種方法啟動(dòng)DDMS:
- from Eclipse: click?Window > Open Perspective > Other... > DDMS
- or from the command line: run?ddms?(or?./ddms?on Mac/Linux) in the?tools/?directory
?
在左邊的面板選擇進(jìn)程com.example.android.hcgallery,然后在 工具條上邊點(diǎn)擊Show heap updates按鈕。這個(gè)時(shí)候切換到DDMS的VM Heap分頁(yè)。它會(huì)顯示每次gc后heap內(nèi)存的一些基本數(shù)據(jù)。要看第一次gc后的數(shù)據(jù)內(nèi)容,點(diǎn)擊Cause GC按鈕:
?
?
我們可以看到現(xiàn)在的值(Allocated列)是有一些超過(guò)8MB。現(xiàn)在滑動(dòng)相片,這時(shí)看到 數(shù)據(jù)在增大。因?yàn)橹挥袃H僅13個(gè)相片在程序里邊,所以泄露的內(nèi)存只有這么大。在某種程度上來(lái)說(shuō),這時(shí)最壞的一種內(nèi)存泄露,因?yàn)槲覀儧](méi)法得到 OutOfMemoryError來(lái)提醒我們說(shuō)現(xiàn)在內(nèi)存溢出了。
?
生成heap dump
?
我們現(xiàn)在使用heap dump來(lái)追蹤這個(gè)問(wèn)題。點(diǎn)擊DDMS工具條上面的Dump HPROF文件按鈕,選擇文件存儲(chǔ)位置,然后在運(yùn)行hprof-conv。在這個(gè)例子里我們使用獨(dú)立的MAT版本(版本1.0.1),從MAT站點(diǎn)下載?。
?
如果你使用ADT(它包含DDMS的插件)同時(shí)也在eclipse里面安裝了MAT,點(diǎn)擊“dump HPROF”按鈕將會(huì)自動(dòng)地做轉(zhuǎn)換(用hprof-conv)同時(shí)會(huì)在eclipse里面打開(kāi)轉(zhuǎn)換后的hprof文件(它其實(shí)用MAT打開(kāi))。
?
用MAT分析heap dumps
啟動(dòng)MAT然后加載剛才我們生成的HPROF文件。MAT是一個(gè)強(qiáng)大的工具,講述它所有的特性超出了本文的范圍,所以我只想演示一種你可以用來(lái)檢測(cè) 泄露的方法:直方圖(Histogram)視圖。它顯示了一個(gè)可以排序的類實(shí)例的列表,內(nèi)容包括:shallow heap(所有實(shí)例的內(nèi)存使用總和),或者retained heap(所有類實(shí)例被分配的內(nèi)存總和,里面也包括他們所有引用的對(duì)象)。
?
如果我們按照shallow heap排序,我們可以看到byte[]實(shí)例在頂端。自從Android3.0(Honeycomb),Bitmap的像素?cái)?shù)據(jù)被存儲(chǔ)在byte數(shù)組里 (之前是被存儲(chǔ)在Dalvik的heap里),所以基于這個(gè)對(duì)象的大小來(lái)判斷,不用說(shuō)它一定是我們泄露掉的bitmap。
?
右擊byte[]類然后選擇List Objects > with incoming references。它會(huì)生成一個(gè)heap上的所有byte數(shù)組的列表,在列表里,我們可以按照Shallow Heap的使用情況來(lái)排序。
?
選擇并展開(kāi)一個(gè)比較大的對(duì)象,它將展示從根到這個(gè)對(duì)象的路徑--就是一條保證對(duì)象有效的鏈條。注意看,這個(gè)就是我們的bitmap緩存!
?
MAT不會(huì)明確告訴我們這就是泄露,因?yàn)樗膊恢肋@個(gè)東西是不是程序還需要的,只有程序員知道。在這個(gè)案例里面,緩存使用的大量的內(nèi)存會(huì)影響到后面的應(yīng)用程序,所以我們可以考慮限制緩存的大小。
?
使用MAT比較heap dumps
?
調(diào)試內(nèi)存泄露時(shí),有時(shí)候適時(shí)比較2個(gè)地方的heap狀態(tài)是很有用的。這時(shí)你就需要生成2個(gè)單獨(dú)的HPROF文件(不要忘了轉(zhuǎn)換格式)。下面是一些關(guān)于如何在MAT里比較2個(gè)heap dumps的內(nèi)容(有一點(diǎn)復(fù)雜):
?
- Android模擬器命令列啟動(dòng)模式
在android-sdk-windows-1.1\tools執(zhí)行emulator以執(zhí)行模擬器
加上-skin參數(shù),指定顯示模式為HVGA-L,則可轉(zhuǎn)為橫向
emulator - skin HVGA-L (480*320,水平顯示)
emulator - skin HVGA-L (320*480,垂直顯示,模擬器預(yù)設(shè)模式)
emulator - skin HVGA-L (320*240,水平顯示)
emulator - skin HVGA-L (240*320,垂直顯示) - 使用mksdcard指令模擬1GB的記憶卡
mksdcard 1024M sacard.img - 模擬插入 SD 卡的模擬器
emulator - sdcard sdcard.img - 使用 adb+push 上載檔案到SD記憶卡
adb push 001.jpg /sdcard (複製檔案到 /sdcard 目錄下)
adb push pictures /sdcard (複製 picture 照片目錄到 /sdcard 目錄下)
adb push mp3 /sdcard (複製 mp3 音樂(lè)目錄到 /sdcard 目錄下)
adb shell (Android 模擬器啟動(dòng)命令列模式)
#cd /sdcard (進(jìn)入 /sdcard 目錄)
#ls (查看 SD 記憶卡中的檔案)? - 使用 adb+pull 從 SD 記憶卡下載檔案
adb pull /sdcard/001.jpg . (下載 /sdcard 目錄下的檔案)
adb pull /sdcard/pictures . (下載 sdcard 目錄下的 pictures 目錄) - 刪除 SD 卡裡面的檔案
adb shell
#ced /sdcard
#rm 001.jpg (刪除 SD 記憶卡裡的檔案)
#rm -r * (刪除 SD 記憶卡裡所有檔案與目錄) - Android模擬器影片播放方法
mksdcard 4096M video.img (製作一個(gè)影像檔的 SD 記憶卡)
adb push video.avi /sdcard (從電腦複製影像檔到 SD 卡中)
emulator -sdcard video.img (啟動(dòng)模擬器並載入 SD 卡)
下載免費(fèi)的影片播放軟體,ex: Meridian Video Player (iiivpa.apk)
http://sites.google.com/site/eternalsandbox/Home/meridian-video-player
adb install iiivpa.apk (安裝Meridian Video Player)
接下來(lái)就可以用裝上去的player播放.mp4、3gp與.wmv三種檔案格式 - 安裝 APK 應(yīng)用程式
adb install filename.apk (安裝filename.apk)
adb install -r filename.apk (保留已設(shè)定資料,重新安裝filename.apk)
adb -s emulator-5554 install filename.apk (指定安裝 APK 套件在 5554 的 Android 模擬器中) - 移除 APK 應(yīng)用程式
adb uninstall package
adb uninstall -k package (移除程式時(shí),保留資料)
此package名稱不是安裝APK套裝時(shí)的檔名或顯示在模擬器中的應(yīng)用程式名稱
可以先到/data/data或data/app目錄下,查詢想移除的package名稱
adb shell
ls /data/data 或 /data/app (查詢 Package 名稱)
exit
adb uninstall package (移除查詢到的 Package) - ADB 系統(tǒng)除錯(cuò)與連結(jié)工具
$adb devices (顯示目前有多少個(gè)模擬器正在執(zhí)行)?
$adb -s <serialNumber> <command> (指定模擬器來(lái)操作)
adb -s emulator-5554 install email.apk
$adb install apkfile (安裝 APK 應(yīng)用程式套件)
adb install email.apk
$adb uninstall package (移除 APK 應(yīng)用程式套件)
adb uninstall com.android.email
$adb shell (進(jìn)入 Android 系統(tǒng)指令列模式)
$ls
$dmesg (查看 Android Linux Kernel 運(yùn)作訊息)
ls - 顯示檔案目錄
cd - 進(jìn)入目錄
rm - 刪除檔案
mv - 移動(dòng)檔案
mkdir - 產(chǎn)生目錄
rmdir - 刪除目錄
$adb push <file/dir> (複製檔案到 SD 卡)
adb push mp3 /sdcard
$adb pull <file/dir> . (從 Android 系統(tǒng)下載檔案)
adb pull /data/app/com.android.email
$adb logcat (監(jiān)控模擬器運(yùn)作紀(jì)錄,以Ctrl + c 離開(kāi)監(jiān)控模式)
$adb bugreport (產(chǎn)生 adb 除錯(cuò)報(bào)告)
$adb get-state (獲得 adb 伺服器運(yùn)作狀態(tài))
$adb start-server (啟動(dòng) adb 伺服器)
$adb kill-server (關(guān)掉 adb 伺服器)
$adb forward tcp:6100 tcp:7100 (更改模擬器網(wǎng)路 TCP 通訊埠)
$adb shell ps -x (顯示 Android 上所有正在執(zhí)行的行程)
$adb version (顯示 adb 版本)
$adb help (顯示 adb 指令參數(shù)) - Emulator 命令列啟動(dòng)參數(shù)
emulator -timezone Asia/Taipei (指定時(shí)區(qū))
emulator -no-boo-anim (省略開(kāi)機(jī)小機(jī)器人動(dòng)畫畫面)
emulator -scale auto (調(diào)整模擬器視窗大小)
emulator - scale factor (factor: 0.1-3.0)
emulator -dpi-device 300 (更改模擬器的解析度,default為 165dpi)
emulator -skin <skinID> (更改模擬器顯示模式)?
emulator -help-keys (顯示鍵盤快速鍵說(shuō)明)
emulator -shell (相當(dāng)於adb shell 功能)
emulator -data data.img (使 /data 目錄使用 data.img 的檔案空間)
emulator -sdcard sdcard.img (使 /sdcard 目錄使用 sdcard.img 的檔案空間)
emulator -cache cache.img (瀏覽器暫存檔儲(chǔ)存空間)
emulator -wipe-data (使模擬器恢復(fù)到原廠設(shè)定)
emulator -help (顯示 emulator 指令參數(shù))
轉(zhuǎn)載于:https://blog.51cto.com/shaojun168/1087143
總結(jié)
以上是生活随笔為你收集整理的DDMS的使用、内存溢出的调试和模拟器的启动命令参数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Reading papers_15(Gr
- 下一篇: 多种分布式文件系统简介