Wednesday, November 16, 2011

重整你的函式(Method)排序

經過一段辛苦的開發後,
Method其實都被我們寫蠻亂的。
這時候就可以利用Eclipse的Sort Members功能排序Method。

我們針對需要排序的class點擊滑鼠右鍵

然後,就會發現函式被排序了。

這樣子找函式,
是不是更有效率了呢?

Monday, November 7, 2011

Memory Leak經驗分享-Drawable篇

之前在網路上找到一段程式碼,
可以輕鬆的在Logcat裡看到目前已使用的記憶體(heap memory)量︰

    Log.i("tag", where+"usedMemory: "+Debug.getNativeHeapSize()/ 1048576L);
 
開發時遇到有好幾個ListView在同一個Activity裡做切換時,
heap memory一直累計,
最後導致OOM(OutOfMemory)

今天總算是解決了這個問題。
從Romain Guy先前的presentation裡了解到︰
Android最直接影響heap memory的元件是︰
1)Bitmap
2)Drawable

所以,如果ListView裡有用到這3種元件的話,
請小心記憶體的使用,
並且要做好記憶體回收的管控。

底下這裡特別為Drawable做一點介紹︰

Romain Guy説Drawable是一個非常有趣的東西。
當我們呼叫它時,
他的背後其實有這層關係︰
當我們今天Activity被onDestroy時,
Drawable被回收了,但⋯

我們發現,
因為Context還在被Drawable的Callback參照(reference),
因此,Context無法被回收。
如果我們又再次開啟Activity,
此時記憶體就會變成︰
因此,造成了記憶體累加,
也就是所謂的Memory Leak。

解決方法就是將Drawable的Callback,
在Activity的onDestroy()中設為null。
    Drawable.setCallback(null);
 
以上是Romain Guy對於Memory Leak的介紹影片中提到的問題,
也剛好解決了手上案子遇到的難題,
在這邊跟遇到此問題的同好分享。

相關文章︰
1.使用MAT(記憶體分析)工具查看Memory Leak
2.何謂Memory Leak
3.記憶體超出OutOfMemoryError
4.Android官方布落格2011/03/24
5.Android官方文件

Wednesday, November 2, 2011

何謂Memory Leak(記憶體洩漏)

何謂Memory Leak?

我們常常會聽到程式開發人員嘴邊老喊著︰記憶體不足。
會發生這個問題,其中可能包含Memory Leak。


維基百科對Memory Leak(記憶體洩漏)做了以下的解釋︰

電腦科學中,內部記憶體泄漏指由於疏忽或錯誤造成程式未能釋放已經不再使用的內部記憶體的情況。
內部記憶體泄漏並非指內部記憶體在物理上的消失,而是應用程式分配某段內部記憶體後,由於設計錯誤,導致在釋放該段內部記憶體之前就失去了對該段內部記憶體的控制,從而造成了內部記憶體的浪費。
Android官方布落格提到︰

A "memory leak" in your code is when you keep a reference to an object that is no longer needed. Sometimes a single reference can prevent a large set of objects from being garbage collected.


以下文章引用自女工程師的美加生活手札的部落格,
裡面提到︰
在電腦世界裡,有一個performance的隱性敵人,它會造成記憶體的使用量隨著時間慢慢增加直到消耗殆盡,這個敵人俗稱Memory Leak (中文翻成記憶體漏洩)。

Memory Leak造成的原因是某個被配置(allocated)的記憶體無法在被參照(referenced),也無法被釋放(released);那塊被配置的 記憶體就有如記憶體孤兒般,無法被系統再使用,所以要看一個程式有否Memory Leak,很簡單的方法就是去看作業系統(For standalone application)或是Application Server(如果是Web Application)的實體記憶體使用圖,如果隨著時間增加,記憶體的使用量呈現明顯增加的趨勢,這個程式就極有可能有潛在的Memory Leak問題。
Memory Leak在C/C++語言中是很常見的人為過失,因為C/C++並沒有自動Garbage Collector (垃圾收集器)的機制,程式設計師必需在使用完資源後,人為釋放資源。雖然在Java、C#有自動Garbage Collector的機制,Memory Leak的機會大幅下降,但仍然不能完全倖免,只要程式設計師不小心仍然會造成Memory Leak。

有些人以為Java有Garbage Collector(GC)就不會有Memory Leak,其實是錯的。如果一個物件仍然有被參照,Garbage Collector是不會回收該物件的。

比如以下狀況:
假設你有一個Java程式,裡面你運用了一個宣告為static 的Array A來儲存已經開啟的視窗-JFrame instance。如果使用者開啟了一個視窗W1,W1會被存入Array A中,而當W1關閉後,你沒有把W1從Array A裡移除,這時,雖然W1已被指到null,Garbage Collector卻不會回收W1。
原因很簡單,因為Array A仍保有W1的參照,GC假設W1之後有被使用的機會,所以沒有回收W1。如果這個程式持續開關大量視窗,它所用的記憶體也會呈現增加的趨勢,而造成了典 型的Memory Leak狀況。
解決的方式很簡單,只要在W1被關閉時同時把W1從Array A裡移除即可。

相關文章︰
1.Romain Guy在Android官方討論裡提到的Memory Leak
2.Memory Leak經驗分享-Drawable篇
3.使用MAT(記憶體分析)工具查看Memory Leak
4.Android官方布落格2011/03/24
5.Android官方文件

使用MAT(記憶體分析)工具查看Memory Leak

通常我們都認為JAVA有Garbage回收機制,
因此不太去理會記憶體的問題。
但如果今天要開發的程式用到大量(大張)的圖片,
因為Android手機的記憶體有限,
程式設計師最好還是要知道一下Memory Leak(記憶體洩漏)。

JAVA有參照(reference)這個特性,
參照如果沒有小心使用,
像︰
Bitmap使用後沒有recycle()、
Drawable使用後沒有setCallback(null)⋯
都會容易出現OutOfMemoryException而導致程式出錯。

Romain Guy在2009年和2010年,
曾藉由數個presentation裡表述如何檢查Memory Leak和防止Memory Leak所造成的OutOfMemory問題。

Android提供的DDMS就是一個這樣的好工具,
可以監看或倒出Memory的使用狀況。

文章開始前,你需要︰
2.設定好Android的環境變數︰Ubuntu的設定方式/Mac的設定方式
3.Eclipse要安裝一個外掛︰
MAT,全名Memory Analyzer Tool
它能將Memory Leak以圖形化的介面方式表現出來。

文章開始︰
MAT,Memory Analyzer Tool,
一個Eclipse上好用的記憶體監看套件。

在MAT裡,
我們很容易看到兩個值︰
Shallow Heap和Retained Heap。
這兩個專業名詞,
淘寶JAVA中間件團隊成都心情的部落格裡,
可以看到詳細的說明。
就我的理解,
Shallow Heap指的是物件本身的記憶體使用量
而Retained Heap指物件本身+參照過來的物件們所使用的記憶體使用量。

為了讓大家能更了解如何使用MAT,
這裡提供一個範例

一、安裝範例
這個範例是一個zip檔,
請用Eclipse[File]-->[Import]將檔案匯入
  
匯入後,
先將TestBitmapLeakAcitivity.java裡onDestroy()裡的removeBitmap()註解掉,
目的是讓程式在離開時,不要回收Bitmap
然後在專案上點擊滑鼠右鍵,啟動本專案































啟動後,
會看到一個小訊息︰[onCreate() Memory used: 2MB]
那個小訊息是告訴我們程式剛啟動時,
目前所使用的記憶體量。

再看一下畫面,還發現畫面中有一個add bitmap按鈕。
我設計每點擊按鈕就會在畫面增加一張圖片(記憶體也會往上累積)。
大概點個5次左右,
會發現記憶體不斷的在累加。
按[實機的返回鍵]關閉程式再進來一次,
訊息顯示︰[onCreate() Memory used: 9MB]
記憶體完全沒有因為之前的程式關閉而釋放,
反而繼續累加中。
這時候我們幾乎可以斷定,
這隻程式發生Memory Leak(記憶體洩漏)了!
我們需要查看Memory Leak發生在什麼地方。


二、查看Memory Leak
1.我們開啟DDMS,
並照以下的步驟,將記憶體快照(snapshot)取出
然後存檔

2.編譯此檔讓MAT工具能夠辨識
指令是︰
$hprof-conv /路徑/com.testMemoryLeak.hprof /路徑/leak.hprof
如︰
$hprof-conv /Users/simon/Desktop/untitled\ folder/com.testMemoryLeak.hprof /Users/simon/Desktop/untitled\ folder/leak.hprof

就會在目錄裡看到編譯好的leak.hprof檔

註︰
1.建議最好將編譯好的leak.hprof檔存在一個目錄裡面,
因為之後MAT在開啟此檔時,會製造出一堆檔案,
造成你的桌面大亂
2.如果沒有將Android的環境變數設定好,
會無法使用hprof-conv工具!

3.使用MAT工具開啟leak.hprof檔

這時候出現一個精靈詢問視窗,
點擊Leak Suspects Report,
產生Leak報表。
報表出來後,
看見一個圓餅圖,
清楚的告訴我們哪裡產生Memory Leak。

我們看到有4個可疑點造成Memory Leak(灰色不算在內)
將畫面往下拉,發現Class蠻可疑的(因為圓餅圖的區塊最大)












我們點擊下方的Detail
出現了更詳細的表格說明
 
點擊連結,選擇List objects-->with incoming references

藉由這個動作,我們知道Android在Runtime時啟動的Class Loader,
有哪些Class被指派(reference)到。
這個圖表描述了這段期間內的記憶體快照,
TestBitmapLeakActivity有2組資料,
其中有一組還擁有大量的Retained Heap。
因此我們從這一組含大量Retained Heap的資料往內追蹤。


我們快速的將這個含Retained Heap=48的數據點擊兩下。
發現到每筆程式所產出的物件,
幾乎都有參照(reference)到mContext。
這在Garbage回收是沒有問題的。

所以我們必須把懷疑的方向往別的地方推進。
我們一筆一筆的點開來檢查。
發現到有3筆ImageView,當中都有一個系統產生的物件BitmapDrawable參照到mCallback。
因為這個參照,讓程式關閉時,
無法完全被回收!

這表示︰Memory Leak罪証成立!還有未被回收遺留的Context。

註︰關於mCallback為什麼會是我們判斷的依據,
請看這篇

因此,我們得知原來造成關閉的程式無法被回收的原因是︰
ImageView沒有完全釋放掉

怎麼釋放掉ImageView使用的資源?
啟動bitmap回收機制,
反註解剛才讓大家註解的removeBitmap(),
然後重新執行一次程式並新增數張bitmap。

接著,按下[實機的返回鍵],
再次進入程式,
我們發現小訊息[onCreate() Memory used]的值不像之前一樣繼續累積,
代表前一次開啟程式的相關物件已經被回收掉了。

後記︰

1.我們還可以用交叉比對的方式,
檢查出Memory Leak前後數據的不同。

我們將程式的removeBitmap()反註解(啟動回收機制),
做出一張圓餅圖表。
再將程式的removeBitmap()註解(不啟動回收機制),
做出另一張圖餅圖表。

從下面圖示可以清楚的看到,
Memory Leak和沒有Memory Leak的狀況之不同。

沒有回收-->造成Memory Leak︰

有回收︰


2.經由以上的操作,
我們發現︰
MAT工具不會直接告訴我們哪裡產生Memory Leak,
但會乖乖的列出Dalvik產出了多少物件實體,
藉由程式設計師的開發經驗,
我們因而能判斷出造成Memory Leak的地方。

判斷Memory Leak的方式和邏輯還有很多種(官方布落格也教一種),
大家可以多爬文做功課。

此篇文章是研究了數天Memory Leak的心得,
若有錯誤煩請指正,
謝謝大家。

最後附上Google I/O 2011的MAT教學,
裡面有提到另一種使用MAT檢查Memory Leak的方法。
相信看完後會更明白Memory Leak和MAT工具。


參考文章︰
1.J2ME GAME
2.何謂Memory Leak(記憶體洩漏)
2.Memory Leak經驗分享-Drawable篇
3.記憶體超出OutOfMemoryError
4.使用JDK工具检查运行系统是否存在内存泄露
5.Android官方布落格2011/03/24
6.Android官方文件
7.直接使用程式Dump hprof檔



Tuesday, November 1, 2011

用手機開發Android程式 - 透過WiFi內網

原標題︰ SHIT!! 開發時忘了帶USB線!!

不知道你是否跟我一樣,
開發過程中常常什麼都帶了,
就是忘了帶USB線?

繼上一篇用VirtualBox開啟Android模擬器
我們學到︰原來我們可以透過WiFi執行adb工具

但有以下幾個條件︰
1.你的手機要Root過(有安裝Superuser)
2.你的手機安裝了Terminal Emulator之類的終端機器

如果以上2個都具備了,
那麼在手機終端機裡輸入︰

$su  <--換成superUser權限
#setprop service.adb.tcp.port 5555 <--設定連接埠為5555
#stop adbd <--關閉adbd
#start adbd <--重啟adbd

完成後,查詢你的手機ip內網位置,指令是︰
#netcfg
查出了手機的IP位址是192.168.1.5

回到電腦終端機,打上︰
adb connect 192.168.1.5
電腦回應我們connected to 192.168.1.5:5555

看了一下Eclipse,
真的用WiFi連到手機了。


耶!沒有帶線也不用回家拿了!
好好的在咖啡廳使用WiFi環境開發你的案子吧。
XD