原文:Developing for Android, II
The Rules: Memoryhtml
在決定應用的行爲,是否有好的用戶體驗以及總體的設備體驗來講,內存的使用多是獨立因素中最重要的。內存因素包括應用的內存佔用,以及內存攪動(致使的垃圾回收會對運行期間的性能有影響)。java
內存分配雖然不可避免,可是應儘量的避免,特別是在平凡的調用的代碼塊中。好比在繪製代碼中,由於每一幀的渲染都會執行該方法。android
避免在自定義View的onDraw方法中分配內存,由於動畫也許會調用它。使用緩存對象替換臨時對象能夠避免新的內存開銷。典型的例子就是在onDraw方法中分配了一個新的Paint對象,由於Canvas須要一個Paint對象。對於這種自定義View的實例只分配一個獨立的Paint對象然後在onDraw方法中臨時使用更好。數組
避免常量,臨時變量的內存分配。下面有一些可參考的策略,也許並不適用於傳統的java編碼,可是對於Android開發是推薦的。一般可使用工具幫助咱們去決定是否某一塊代碼須要優化。若是代碼的某一部分不多執行(好比用戶改變一些設置的操做),更簡潔和傳統的抽象層是不錯的選擇。可是若是分析代表某些代碼頻繁執行並致使了大量的內存攪動,考慮如下策略:緩存
對象緩存
重用對象在一些常量內存的再分配中頗有用,好比在內部循環中避免內存分配。好比,有些頻繁調用的方法中可能須要一個Rect對象存儲一些中間值,最好在把Rect做爲類級別的常量,只分配一次內存,甚至是靜態的,避免每次方法調用的時候都分配。關於單例的一些警示對於這種方式也是適用的,在Android上,靜態的常見缺陷就是它們對於某一個進程是靜態的,可是可能有多個活動在同一進程中。當心應用,這種技術在避免內存的再分配中是通用的安全
對象池
若是代碼臨時須要同一種類型的多個對象,考慮適用對象池而不是頻繁的分配內存。但對象池可能不容易管理。若是對象有狀態而且又是被任意線程訪問的狀況,要注意一些併發性的問題。在內存壓力方面也有問題(可使用LruCache策略),對象的增長存在着內存泄漏的風險,所以當你使用對象池的時候注意這些問題,考慮只在特定的狀況下使用它。若是這種策略在那些內存配置低低老版本或者設備上很是有用,你應該經過API版本或者isLowRamDevice()方法來檢測對象池的使用限制數據結構
Arrays
ArrayList是一種很便利的集合也不會形成太多的分配。它會再分配,而且會複製當前的數組添加到列表後面,可是設置一個合理的初始化容量能夠避免頻繁的分配內存。若是你的集合不須要動態的變化大小,考慮使用Array併發
Andorid集合類
除非你須要一個map去存儲大量的數據,不然考慮使用ArrayMap或者是SimpleArayMap做爲數據結構而不是HashMap。這些類是通過優化的,相對於HashMap會有更高的內存效率以及更少的GC壓力,這樣的數據結構在移動設備上可以更好的知足通用的使用情景(另外它們也支持實體的遍歷而不使用Iterator)。固然,考慮設置一個恰當的初始容量去避免自動擴容app
須要修改對象的方法
這種狀況你不該該不要返回一個新的對象,考慮將該對象做爲參數傳入進來,去修改該對象:框架
Rect getRect(int w, int h) |
getRect(Rect , w, h) |
明確的(List.iterator()
)或者不明確的(for(Object o : myObjects)
)使用Interator會致使一個Interator對象的內存分配。單獨一個內存分配不是什麼大事,可是應該儘可能避免在內部循環中分配內存。固然,直接的使用角標進行集合遍歷能夠不用分配任何的內存。
final count = myList.(); |
值得注意的是,Interator總會致使一個內存的分配,即便是空集合。所以,若是當你非要使用foreach的時候,應該在遍歷集合的以前能夠進行一次isEmpty的檢驗。
枚舉一般能夠用來表明常量,可是會比原始類型耗費更多,它涉及到代碼量的大小和枚舉對象內存的分配。
一個臨時的枚舉不會形成較大的內存消耗。可是Proguard會,在一些狀況下他會進行一些靜態的分析全部的代碼,將枚舉優化爲int值。當枚舉在整個應用中被被普遍的使用或者當一個library或某個API中的枚舉被其它不少應用使用的時候時就是問題了,甚至會很糟糕。
使用AndioStudio1.3版本中的@IntDef
註解可以保證你的代碼在build時期是類型安全的(當lint error開啓的時候)。所以使用int變量對於性能和代碼量都會更好。
有時會使用一些熟悉的java平臺的一些框架,好比註解依賴的Guice。可是它並無爲移動應用進行優化,使用它們將會致使一些問題。
若是你只是使用了某個Library中的一小部分,你能夠試着將那一部分抽取出來。即便Proguard在不少狀況下能夠跳過那些不用的代碼,可是在大的library的依賴圖可能會致使優化失敗(也會大大增長Proguard的build時間)。
有一些libraries雖然被引入到Android應用中,可是你不該該隨意使用,除非很熟悉它,知道它可能爲應用帶來的問題。
還有些問題就是使用那些非移動的框架和庫可能會增長內存的開銷。你能夠經過監視內存的使用和垃圾回收器的行爲來檢測它們致使的問題程度。
對於避免臨時的內存分配使用static對象頗有用。可是應該注意使用靜態變量去緩存對象時,它們實際上不該該一直存在整個進程的生命週期中。特別的,這些static的變量不該該和Activity的生命週期一致。好比,當屏幕方向改變的時候Activity會destroy並recreate,可是static變量持有Activity的引用,這樣會致使內存泄漏。Activities是很是耗資源的,這種內存泄漏很快會致使你的應用和系統OOM。
由於和java語言的席位差異,finalizers須要的不是一個垃圾回收器,而是兩個。這就意味着不只資源會被finalizer會被凍住直到兩個垃圾回收器都觸發的時候,並且系統中同時運行兩個垃圾回收器也會致使資源消耗和卡頓。有一種特殊狀況須要finalization,當你的對象持有一個本地的指針時。若是沒有這樣的狀況,就能夠徹底避免finalizers。
若是你確實須要finalizers,考慮實現AutoCloseable接口而且在你的代碼域內經過close方法釋放全部的資源。
在你的應用中一些重要的時間內(好比啓動的時候),過多的初始化可能致使性能的問題和較差的用戶體驗。能夠在你須要它的時候再去加載代碼。
從API 14,ComponentCallbacks2提供了onTrimMemory()回調方法容許你的app在較低內存壓力的狀況下釋放內存。更多詳情能夠參考Google I/O 2012的Video Doing More with Less,展現了一個如何LruCache處理bitmap的例子。
KitKat版本中出現的ActivityManager.isLowRamDevice()方法能夠幫助你檢測應用運行時的內存限制.當你的應用中有一些特性比較好內存的時候u,能夠經過這種方式去檢測內存是否能夠知足你的特性,而後決定是否開啓。
應用能夠經過在mainfest文件中設置application tag來開啓請求較大heap內存的功能,可是你不該該這麼作。請求一個大的Heap的行爲可能着該應用只考慮到本身的需求,可是對於整個設備度體驗來講是一個錯誤度決定。
請求大的Heap在不多的狀況下多是必要的,好比media內容的處理。可是應用使用該功能只是爲了更好的管理內存和資源而不是致使整個設備的用戶體驗變得更差。應用請求較大的heap將會致使設備上其它的進程擁有更少的內存,用戶在切換activity的時候就有可能致使其它應用被kill掉而後重啓。
每個進程在系統中都有一個資源的限制。若是你不須要service在後臺一致運行,就及時將它關閉。
Android提供了不少機制來確保組件只在特定的範圍內運行:
瘦身的應用會運行更快。加載的代碼量越少,用戶下載你的app的時間就會越少,你的應用也會更快的啓動和初始化。下面是一些建議: