編輯:關於Android編程
有不少朋友都問過我,怎樣才能寫出高性能的應用程序,如何避免程序出現OOM,或者當程序內存占用過高的時候該怎麼樣去排查。確實,一個優秀的應用程序,不僅僅要功能完成得好,性能問題也應該處理得恰到好處。為此,我也是閱讀了不少Android官方給出的高性能編程建議,那麼從本篇文章開始,我就准備開始寫一個全新系列的博文,來把這些建議進行整理和分析,幫助大家能夠寫出更加出色的應用程序。
注意本系列文章的內容基本源於Android Doc,如果想要閱讀更加詳細的關於性能方面的資料,可以直接去閱讀Android官方文檔。
內存(RAM)對於任何一個軟件開發環境都是種非常珍貴的資源,而對於移動操作系統來講的話,則會顯得更加珍貴,因為手機的硬件條件相對於PC畢竟是比較落後的。盡管Android系統的虛擬機擁有自動回收垃圾的機制,但這並不代表我們就可以忽視應該在什麼時候分配和釋放內存。
為了使垃圾回收器可以正常釋放程序所占用的內存,在編寫代碼的時候就一定要注意盡量避免出現內存洩漏的情況(通常都是由於全局成員變量持有對象引用所導致的),並且在適當的時候去釋放對象引用。對於大多數的應用程序而言,後面其它的事情就可以都交給垃圾回收器去完成了,如果一個對象的引用不再被其它對象所持有,那麼系統就會將這個對象所分配的內存進行回收。
我們在開發軟件的時候應當自始至終都把內存的問題充分考慮進去,這樣的話才能開發出更加高性能的軟件。而內存問題也並不是無規律可行的,Android系統給我們提出了很多內存優化的建議技巧,只要按照這些技巧來編寫程序,就可以讓我們的程序在內存性能發面表現得相當不錯,下面我們就來一一學習一下這些技巧。
如果應用程序當中需要使用Service來執行後台任務的話,請一定要注意只有當任務正在執行的時候才應該讓Service運行起來。另外,當任務執行完之後去停止Service的時候,要小心Service停止失敗導致內存洩漏的情況。
當我們啟動一個Service時,系統會傾向於將這個Service所依賴的進程進行保留,這樣就會導致這個進程變得非常消耗內存。並且,系統可以在LRU cache當中緩存的進程數量也會減少,導致切換應用程序的時候耗費更多性能。嚴重的話,甚至有可能會導致崩潰,因為系統在內存非常吃緊的時候可能已無法維護所有正在運行的Service所依賴的進程了。
為了能夠控制Service的生命周期,Android官方推薦的最佳解決方案就是使用IntentService,這種Service的最大特點就是當後台任務執行結束後會自動停止,從而極大程度上避免了Service內存洩漏的可能性。關於IntentService更加詳細的用法講解,可以參考《第一行代碼——Android》的9.5.2節。
讓一個Service在後台一直保持運行,即使它並不執行任何工作,這是編寫Android程序時最糟糕的做法之一。所以Android官方極度建議開發人員們不要過於貪婪,讓Service在後台一直運行,這不僅可能會導致手機和程序的性能非常低下,而且被用戶發現了之後也有可能直接導致我們的軟件被卸載(我個人就會這麼做)。
當用戶打開了另外一個程序,我們的程序界面已經不再可見的時候,我們應當將所有和界面相關的資源進行釋放。在這種場景下釋放資源可以讓系統緩存後台進程的能力顯著增加,因此也會讓用戶體驗變得更好。
那麼我們如何才能知道程序界面是不是已經不可見了呢?其實很簡單,只需要在Activity中重寫onTrimMemory()方法,然後在這個方法中監聽TRIM_MEMORY_UI_HIDDEN這個級別,一旦觸發了之後就說明用戶已經離開了我們的程序,那麼此時就可以進行資源釋放操作了,如下所示:
[java]view plaincopy以上是當我們的應用程序正在運行時的回調,那麼如果我們的程序目前是被緩存的,則會收到以下幾種類型的回調:
TRIM_MEMORY_BACKGROUND 表示手機目前內存已經很低了,系統准備開始根據LRU緩存來清理進程。這個時候我們的程序在LRU緩存列表的最近位置,是不太可能被清理掉的,但這時去釋放掉一些比較容易恢復的資源能夠讓手機的內存變得比較充足,從而讓我們的程序更長時間地保留在緩存當中,這樣當用戶返回我們的程序時會感覺非常順暢,而不是經歷了一次重新啟動的過程。TRIM_MEMORY_MODERATE 表示手機目前內存已經很低了,並且我們的程序處於LRU緩存列表的中間位置,如果手機內存還得不到進一步釋放的話,那麼我們的程序就有被系統殺掉的風險了。TRIM_MEMORY_COMPLETE 表示手機目前內存已經很低了,並且我們的程序處於LRU緩存列表的最邊緣位置,系統會最優先考慮殺掉我們的應用程序,在這個時候應當盡可能地把一切可以釋放的東西都進行釋放。當我們讀取一個Bitmap圖片的時候,有一點一定要注意,就是千萬不要去加載不需要的分辨率。在一個很小的ImageView上顯示一張高分辨率的圖片不會帶來任何視覺上的好處,但卻會占用我們相當多寶貴的內存。需要僅記的一點是,將一張圖片解析成一個Bitmap對象時所占用的內存並不是這個圖片在硬盤中的大小,可能一張圖片只有100k你覺得它並不大,但是讀取到內存當中是按照像素點來算的,比如這張圖片是1500*1000像素,使用的ARGB_8888顏色類型,那麼每個像素點就會占用4個字節,總內存就是1500*1000*4字節,也就是5.7M,這個數據看起來就比較恐怖了。
至於如何去壓縮圖片,以及更多在圖片方面節省內存的技術,大家可以去參考我之前寫的一篇博客Android高效加載大圖、多圖解決方案,有效避免程序OOM。
Android API當中提供了一些優化過後的數據集合工具類,如SparseArray,SparseBooleanArray,以及LongSparseArray等,使用這些API可以讓我們的程序更加高效。傳統Java API中提供的HashMap工具類會相對比較低效,因為它需要為每一個鍵值對都提供一個對象入口,而SparseArray就避免掉了基本數據類型轉換成對象數據類型的時間。
我們還應當清楚我們所使用語言的內存開支和消耗情況,並且在整個軟件的設計和開發當中都應該將這些信息考慮在內。可能有一些看起來無關痛癢的寫法,結果卻會導致很大一部分的內存開支,例如:
使用枚舉通常會比使用靜態常量要消耗兩倍以上的內存,在Android開發當中我們應當盡可能地不使用枚舉。任何一個Java類,包括內部類、匿名類,都要占用大概500字節的內存空間。任何一個類的實例要消耗12-16字節的內存開支,因此頻繁創建實例也是會一定程序上影響內存的。在使用HashMap時,即使你只設置了一個基本數據類型的鍵,比如說int,但是也會按照對象的大小來分配內存,大概是32字節,而不是4字節。因此最好的辦法就是像上面所說的一樣,使用優化過的數據集合。許多程序員都喜歡各種使用抽象來編程,認為這是一種很好的編程習慣。當然,這一點不可否認,因為的抽象的編程方法更加面向對象,而且在代碼的維護和可擴展性方面都會有所提高。但是,在Android上使用抽象會帶來額外的內存開支,因為抽象的編程方法需要編寫額外的代碼,雖然這些代碼根本執行不到,但是卻也要映射到內存當中,不僅占用了更多的內存,在執行效率方面也會有所降低。當然這裡我並不是提倡大家完全不使用抽象編程,而是謹慎使用抽象編程,不要認為這是一種很酷的編程方式而去肆意使用它,只在你認為有必要的情況下才去使用。
現在有很多人都喜歡在Android工程當中使用依賴注入框架,比如說像Guice或者RoboGuice等,因為它們可以簡化一些復雜的編碼操作,比如可以將下面的一段代碼:
[java]view plaincopy這裡指定的進程名是background,你也可以將它改成任意你喜歡的名字。需要注意的是,進程名的前面都應該加上一個冒號,表示該進程是一個當前應用程序的私有進程。
遵循以上的所有編程建議,我們就可以讓應用程序內存的使用變得更加合理化。但這只是第一步而已,為了要讓程序擁有最佳性能,我們要學習的東西還有很多,下篇文章當中將會介紹如何分析內存的使用情況
這篇將會介紹裝飾者模式(Decorator Pattern),裝飾者模式也稱為包裝模式(Wrapper Pattern),結構型模式之一,其使用一種對客戶端透明的方式來動
這裡記錄一下Canvas 相關API的使用,權當自己作筆記,以後需要好參考前面有一文Android應用程序窗口View的draw過程講到View的繪制過程,其中說到,Vi
概述: 我想我們在使用一些App的時候,應該不會出現一些“裸控件”的吧。除非是一些系統中的軟件,那是為了保持風格的一致性,做出的一些權衡。我這裡並
1.關於坑 好吧,在此之前先來說一下,之前開的坑,恩,確實是坑,前面開的兩個android開發教程的坑,對不起,實在是沒什麼動力了,不過源碼都有的,大家可以參照githu