android中的內存優化

RAM管理是開發者們從程序設計到上線運營都要頭疼的一塊,實際開發中,機佬們各取手段,不斷變換設計和實現方式,而這些方式的技術要點總結起來主要為以下十五招。

1.Service的合理使用

很多developer都會使用Service與android內存管理員拉鋸作戰,這是邪教武功,不宜長久。一個service被啟動后,系統在資源緊張釋放內存時,會一直保留Service所在的進程,使進程運行的代價提高,減少了系統能存放到LRU緩存當中的進程數量,進而影響app之間的切換效率,用戶就會發現,有幾款app常駐后臺,而現在手機已經很卡了,馬上就會考慮卸載它們。

一個清爽的app中,service的使用方式是IntentService,它會在處理完交給它的intent后盡快結束自己。

2.UI隱藏時釋放內存

當應用被切換并不可見后,釋放UI占用的資源,可以很大程度的提高系統緩存進程的能力,用戶體驗就來了。

需要實現Activity類中的 onTrimMemory( ) 回調方法,在這個方法中,監聽 TRIM_MEMORY_UI_HIDDEN級別的回調,通過這可以接收用戶離開UI的通知。

與onStop( ) 中釋放資源不一樣,onStop( ) 會在你的同一個app內某一個Activity跳到另一個Activity時調用,并釋放Activity里的資源,如網絡連接、unregister廣播接受者等。除非收到onTrimMemory( ) ,否則不應該釋放UI資源,這可以確保用戶從當前app內其它Activity切換回來時,Activity能迅速恢復。

3.內存緊張時釋放部分內存

app運行時,在生命周期的任何階段,都可以調用onTrimMemory( ) 獲取整個設備內存資源狀況。根據不同的狀況,靈活使用手段,可以提高app存活率和體驗效果。

TRIM_MEMORY_RUNNING_MODERATE
你的app正在運行并且不在死亡清單中。但是,系統處于低內存狀態,開始觸法殺死LRU Cache中的Process機制

TRIM_MEMORY_RUNNING_LOW
你的app正在運行并且不在死亡清單中。但是,系統處于低內存狀態,應該釋放不用的資源以提高系統性能(但是會直接影響你的app的性能)。

TRIM_MEMORY_RUNNING_CRITICAL
你的app仍在運行,但是,系統已經殺死LRU Cache中的大多數進程,如果系統回收不到足夠的RAM數量,將會清除所有LRU緩存中的進程,并且開始殺死之前判斷不應該殺死的進程,如:包換正在運行狀態的Service的進程。
此時應該釋放所有非必須的資源,維持系統生態的和諧。

當你的app正在被cached時,可能收到以下幾種狀態值:

TRIM_MEMORY_BACKGROUND
系統處于低內存狀態,你的app處于LRU緩存名單中最不容易殺掉的位置。

TRIM_MEMORY_MODERATE
系統處于低內存狀態,你的app處于LRU緩存名單中部位置。應該釋放不用的資源以提高系統性能(但是會直接影響你的app的性能)。

TRIM_MEMORY_COMPLETE
系統處于低內存狀態,你的app處于LRU緩存名單中最容易殺掉的位置。此時應該釋放掉任何不影響app恢復狀態的資源以保全自己。

4.擴展heap空間

不同的設備為app提供的不同的heap限制,可以使用getMemoryClass( ) 來獲取app的可用heap大小。如果你的app嘗試使用更多的內存,就會出現OOM。

特殊的應用需要使用更大heap時,可以在manifest的application標簽下添加 "largeHeap = true" 的屬性來聲明一個更大的空間,通過getLargeMemoryClass( ) 來獲取一個更大的heap size。

這個方法如非必要,不可輕易使用,否則造成資源冗余,你的app的存在會影響整個系統的體驗,每次GC運行時間更長,任務切換時系統性能也會降低,間接增加了app被卸載的危險。

5.bitmap合理使用

加載一個bitmap時,僅僅需要保留適配當前屏幕分辨率的數據即可。增加bitmap的尺寸會對內存呈現2次方的增加,因為XY都在增加。

圖片加載功能的實現,建議使用ImageLoader框架,如Picasso、Glide等成熟的框架。

6.使用優化的數據容器

利用Android Framework里優化過的容器類,如SparseArray、SparseBooleanArray、LongSparseArray。

通常的HashMap的實現方式更加消耗內存,因為它需要一個額外的實力對象來記錄Mapping操作。另外,sparseArray更加高效在于它們避免了對key、value的autobox,并避免了裝箱后的解箱。

7.時刻注意各種內存開銷

許多小細節都可能會導致大量的內存開銷,因此必須對語言與庫的開銷有所了解,內存開銷的控制應該貫穿始末。如:Enums的內存消耗通常是static constants的2倍,應該盡量避免在android中使用enums;Java中的每一個類(包括匿名內部類)都會使用大概 500 bytes;每一個類的實例產生的花銷是12~16bytes;往HashMap中添加一個entry需要額外占用的32 bytes的entry對象等。

8.注意代碼“抽象”

代碼抽象會提升代碼的靈活性和可維護性,但會導致一個顯著的開銷:通常它們需要同等量的代碼用于可執行,那些代碼會被map到內存中。因此,如果抽象沒有顯著提升效率,應該盡量避免它們。

9.為序列化的數據使用nano protobufs

通常的協議化操作會產生大量繁瑣的代碼,app就有很多隱藏的弊端:增加RAM使用量,APK太肥,執行速度緩慢,容易達到DEX字符限制等。

protocol buffers 是google為序列化結構數據而設計的,與語言、平臺等無關,類似xml,卻更輕量、快速、簡單。如果數據需要實現序列化,那么代碼中應該經常出現 nano protobufs。

10.避免使用依賴注入框架

現在有很多優秀的框架,能提升開發體驗、減輕開發量、讓代碼更優雅,但是這些框架會通過掃描你的代碼,執行很多初始化的操作,這回導致你的代碼需要大量的RAM來map代碼。但是,mapped pages會長時間的被保留在RAM中。

11.謹慎使用外部庫

很多library的代碼都不是為移動開發環境編寫的,因此,運用到移動開發時會影響app的效率。即使針對android而設計的library,也會因為幾個library所做的事不一樣,從而影響到你的app。如一個lib使用的是nano protobufs,另一個lib使用micro protobufs,那么,你的app中就會有兩種protobuf實現方式。這樣的沖突可能會發生在輸出日志、加載圖片、緩存等模塊上。

因此,如果不是需要大量使用這個庫來達到開發效果,只為了一個兩個功能而導入整個library,是非常不明智的,此時自己實現這個功能是最佳選擇。

12.整體性能的優化

谷歌官方列出了許多優化整個App性能的文章,諸如?Best Practices for Performance

還有些文章是講解如何優化App 的CPU使用效率,有些是講解如何優化App 的內存使用效率。

optimizing your UI是講解如何為layout進行優化。

13.使用ProGuard剔除冗余代碼

ProGuard可以通過剔除不需要的代碼,重命名類、域與方法等對代碼進行壓縮、優化與混淆。可以使用更少的mapped代碼所需要的RAM。

14.對最終的apk使用zipalign

zipalign可以對apk二次校準,減少app需要的RAM的。

15.使用多進程

特殊需要才可用這一招,因為大多數的app如果使用多進程,反而會增加內存的使用。當app需要在后臺運行與前臺一樣的大量的任務時,可以考慮使用這個技術。如音樂播放的app,如果整個app運行在一個進程中,后臺播放時,前臺的UI無法釋放,增加RAM負擔。這樣的app可以分成兩個進程,一個操作前臺UI,另一個用于后臺Service。

可以在manifest文件中聲明"android:process"屬性來實現某個組件運行在另外一個進程的操作:

<service android:name=".PlaybackService" android:process="background" />

以上十五招需合理使用,開發過程中時刻考慮內存狀況,各個階段都做好內存管理,這樣的app才會更健壯、更簡潔、更高效。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容