在app開發的各個階段中要考慮RAM的限制問題, 包括在設計階段(正式開發以前). 使用下面的不一樣的方法能夠達到很好的效果. 當您在設計和開發Android應用時用下面的方法可使內存運用最高效. html
若是你的應用須要使用 service 在後臺執行業務功能, 除非是一直在進行活動的工做(好比每隔幾秒向服務器端請求數據之類)不然不要讓它一直保持在後臺運行. 而且, 當你的 service 執行完成可是中止失敗時要當心 service 致使的內存泄露問題.java
當你啓動 service 時, 系統老是優先保持服務的運行. 這會致使內存應用效率很是低, 由於被該服務使用的內存不能作其它事情. 也會減小系統一直保持的LRU緩存處理數目, 使不一樣的app切換效率下降. 當前全部 service 的運行會致使內存不夠不能維持正常系統的運行時, 系統會發生卡頓的現象嚴重時能致使系統不斷重啓.android
最好的方式是使用 IntentSevice 控制 service 的生命週期, 當使用 intent 開始任務後, 該 service 執行完全部的工做時會自動中止.c++
在android應用中當不須要使用常駐 service 執行業務功能而去使用一個常駐 service 是最糟糕的內存管理方式之一. 因此不要貪婪的使用 service 使你的應用一直運行狀態. 這樣不只使你由於內存的限制提升了應用運行的風險, 也會致使用戶發現這些異常行爲後而卸載應用.git
當用戶跳轉到不一樣的應用而且你的視圖再也不顯示時, 你應該釋放應用視圖所佔的資源. 這時釋放所佔用的資源能顯著的提升系統的緩存處理容量, 而且對用戶的體驗質量有直接的影響.github
當實現當前 Activity 類的 onTrimMemory() 回調方法後, 用戶離開視圖時會獲得通知. 使用該方法能夠監聽 TRIM_MEMORY_UI_HIDDEN 級別, 當你的視圖元素從父視圖中處於隱藏狀態時釋放視圖所佔用的資源.shell
注意只有當你應用的全部視圖元素變爲隱藏狀態時你的應用才能收到 onTrimMemory() 回調方法的 TRIM_MEMORY_UI_HIDDEN . 這個和 onStop() 回調方法不一樣, 該方法只有當 Activity 的實例變爲隱藏狀態, 或者有用戶移動到應用中的另外的 activity 纔會引起. 因此說你雖然實現了 onStop() 去釋放 activity 的資源例如網絡鏈接或者未註冊的廣播接收者, 可是應該直到你收到 onTrimMemory(TRIM_MEMORY_UI_HIDDEN)纔去釋放視圖資源不然不該該釋放視圖所佔用的資源. 這裏能夠肯定的是若是用戶經過後退鍵從另外的 activity 進入到你的應用中, 視圖資源會一直處於可用的狀態能夠用來快速的恢復 activity.編程
在應用生命週期的任何階段 onTrimMemory() 回調方法均可以告訴你設備的內存愈來愈低的狀況, 你能夠根據該方法推送的內存緊張級別來釋放資源.api
應用處於運行狀態而且不會被殺掉, 設備使用的內存比較低, 系統級會殺掉一些其它的緩存應用.緩存
應用處於運行狀態而且不會被殺掉, 設備可使用的內存很是低, 能夠把不用的資源釋放一些提升性能(會直接影響程序的性能)
還有, 當你的應用被系統正緩存時, 經過 onTrimMemory() 回調方法能夠收到如下幾個內存級別:
系統處於低內存的運行狀態中而且你的應用處於緩存應用列表的初級階段. 雖然你的應用不會處於被殺的高風險中, 可是系統已經開始清除緩存列表中的其它應用, 因此你必須釋放資源使你的應用繼續存留在列表中以便用戶再次回到你的應用時能快速恢復進行使用.
系統處於低內存的運行狀態中而且你的應用處於緩存應用列表的中級階段. 若是系運行內存收到限制, 你的應用有被殺掉的風險.
系統處於低內存的運行狀態中若是系統如今沒有內存回收你的應用將會第一個被殺掉. 你必須釋放掉全部非關鍵的資源從而恢復應用的狀態.
由於 onTrimMemory() 是在級別14的android api中加入的, 因此低版本的要使用 onLowMemory() 方法, 該方法大體至關於 TRIM_MEMORY_COMPLETE 事件.
注意: 當系統開始清除緩存應用列表中的應用時, 雖然系統的主要工做機制是自下而上, 可是也會經過殺掉消費大內存的應用從而使系統得到更多的內存, 因此在緩存應用列表中消耗更少的內存將會有更大的機會留存下來以便用戶再次使用時進行快速恢復.
前面提到, 不一樣的android設備系統擁有的運行內存各自都不一樣, 從而不一樣的應用堆內存的限制大小也不同. 你能夠經過調用 ActivityManager 中的 getMemoryClass() 函數能夠經過以兆爲單位獲取當前應用可用的內存大小, 若是你想獲取超過最大限度的內存則會發生 OutOfMemoryError .
有一個特別的狀況, 能夠在 manifest 文件中的 <application> 標籤中設置 largeHeap 屬性的值爲 "true"時, 當前應用就能夠獲取到系統分配的最大堆內存. 若是你設置了該值, 能夠經過 ActivityManager 的 getLargeMemoryClass() 函數獲取最大的堆內存.
而後, 只有一小部分應用須要消耗大量堆內存(好比大照片編輯應用). 歷來不須要使用大量內存僅僅是由於你已經消耗了大量的內存而且必須快速修復它, 你必須使用它是由於你剛好知道全部的內存已經被分配完了而你必需要保留當前應用不會被清除掉. 甚至當你的應用須要消耗大量內存時, 你應該儘量的避免這種需求. 使用大量內存後, 當你切換不一樣的應用或者執行其它相似的操做時, 由於長時間的內存回收會致使系統的性能降低從而漸漸的會損害整個系統的用戶體驗.
另外, 大內存不是全部的設備都相同. 當跑在有運行內存限制的設備上時, 大內存和正常的堆內存是同樣的. 因此若是你須要大內存, 你就要調用 getMemoryClass() 函數查看正常的堆內存的大小而且儘量使內存使用狀況維護在正常堆內存之下.
當你加載 bitmap 時, 須要根據分辨率來保持它的內存時最大爲當前設備的分辨率, 若是下載下來的原圖爲高分辨率則要拉伸它. 要當心bitmap的分辨率增長後所佔用的內存也要進行相應的增長, 由於它是根據x和y的大小來增長內存佔用的.
注意: 在 Android 2.3.x(api level 10)如下, 不管圖片的分辨率多大 bitmap 對象在內存中始終顯示相同大小, 實際的像素數據被存儲在底層 native 的內存中(c++內存). 由於內存分析工具沒法跟蹤 native 的內存狀態全部調試 bitmap 內存分配變得很是困難. 然而, 從 Android 3.0(api level 11)開始, bitmap 對象的內存數據開始在應用程序所在Dalvik虛擬機堆內存中進行分配, 提升了回收機率和調試的可能性. 若是你在老版本中發現 bitmap 對象佔用的內存大小始終同樣時, 切換設備到系統3.0或以上來進行調試.
利用 Android 框架優化後的數據容器, 好比 SparseArray, SparseBooleanArray 和 LongSparseArray. 傳統的 HashMap 在內存上的實現十分的低效由於它須要爲 map 中每一項在內存中創建映射關係. 另外, SparseArray類很是高效由於它避免系統中須要自動封箱(autobox)的key和有些值.
在你設計應用各個階段都要很謹慎的考慮所使用的語言和庫帶來的內存上的成本和開銷. 一般狀況下, 表面上看起來無害的會帶來巨大的開銷, 下面在例子說明:
使用不少的沒必要要類和對象時, 增長了分析堆內存問題的複雜度.
一般來講, 使用簡單的抽象是一種好的編程習慣, 由於必定程度上的抽象能夠提供代碼的伸縮性和可維護性. 然而抽象會帶來很是顯著的開銷: 須要執行更多的代碼, 須要更長時間和更多的運行內存把代碼映射到內存中, 因此若是抽象沒有帶來顯著的效果就儘可能避免.
Protocol Buffers 是 Google 公司開發的一種數據描述語言,相似於XML可以將結構化數據序列化. 可是它更小, 更快, 更簡單. 若是你決定使用它做爲你的數據, 你必須在你的客戶端代碼中一直使用納米 protocol buffer, 由於正常的 protocol buffer 會產生極其冗餘的代碼, 在你的應用生會引發不少問題: 增長了使用的內存, 增長了apk文件的大小, 執行速度較慢以及會快速的把一些限定符號打入 dex 包中.
使用像 Guice 和 RoboGuice 依賴注入框架會有很大的吸引力, 由於它使咱們能夠寫一些更簡單的代碼和提供自適應的環境用來進行有用的測試和進行其它配置的更改. 然而這些框架經過註解的方式掃描你的代碼來執行一系列的初始化, 可是這些也會把一些咱們不須要的大量的代碼映射到內存中. 被映射後的數據會被分配到乾淨的內存中, 放入到內存中後很長一段時間都不會使用, 這樣形成了內存大量的浪費.
許多的外部依賴庫每每不是在移動環境下寫出來的, 這樣當在移動使用中使用這些庫時就會很是低效. 因此當你決定使用一個外部庫時, 你就要承擔爲優化爲移動應用外部庫帶來的移植問題和維護負擔. 在項目計劃前期就要分析該類庫的受權條件, 代碼量, 內存的佔用再來決定是否使用該庫.
甚至聽說專門設計用於 android 的庫也有潛在的風險, 由於每一個庫作的事情都不同. 例如, 一個庫可能使用的是 nano protobuf 另一個庫使用的是 micro protobuf, 如今在你的應用中有兩個不一樣 protobuf 的實現. 這將會有不一樣的日誌, 分析, 圖片加載框架, 緩存, 等全部你不可預知的事情的發生. Proguard 不會保存你的這些, 由於全部低級別的 api 依賴須要你依賴的庫裏所包含的特徵. 當你使用從外部庫繼承的 activity 時尤爲會成爲一個問題(由於這每每產生大量的依賴). 庫要使用反射(這是常見的由於你要花許多時間去調整ProGuard使它工做)等.
也要當心不要陷入使用幾十個依賴庫去實現一兩個特性的陷阱; 不要引入大量不須要使用的代碼. 一天結束時, 當你沒有發現符合你要求的實現時, 最好的方式是建立一個屬於本身的實現.
除了上述狀況外, 還能夠優化CPU的性能和用戶界面, 也會帶動內存的優化
代碼混淆工具 ProGuard 經過去除沒有用的代碼和經過語義模糊來重命名類, 字段和方法來縮小, 優化和混淆你的代碼. 使用它能使你的代碼更簡潔, 更少許的RAM映射頁.
若是構建apk後你沒有作後續的任何處理(包括根據你的證書進行簽名), 你必須運行 zipalign 工具爲你的apk從新簽名, 若是不這樣作會致使你的應用使用更多的內存, 由於像資源這樣的東西不會再從apk中進行映射(mmap).
注意:goole play store 不接受沒有簽名的apk
使用adb shell dumpsys meminfo +包名 等工具來分析你的應用在各個生命週期的內存使用狀況, 這個後續博文會有所體現.
一種更高級的技術能管理應用中的內存, 分離組件技術能把單進程內存劃分爲多進程內存. 該技術必定要謹慎的使用而且大多數的應用都不會跑多進程, 由於若是你操做不當反而會浪費更多的內存而不是減小內存. 它主要用於後臺和前臺能各自負責不一樣業務的應用程序
當你構建一個音樂播放器應用而且長時間從一個 service 中播放音樂時使用多進程處理對你的應用來講更恰當. 若是整個應用只有一個進程, 當前用戶卻在另一個應用或服務中控制播放時, 卻爲了播放音樂而運行着許多不相關的用戶界面會形成許多的內存浪費. 像這樣的應用能夠分隔爲兩個進程:一個進程負責 UI 工做, 另一個則在後臺服務中運行其它的工做.
在各個應用的 manifest 文件中爲各個組件申明 android:process 屬性就能夠分隔爲不一樣的進程.例如你能夠指定你一運行的服務從主進程中分隔成一個新的進程來並取名爲"background"(固然名字能夠任意取).
<service android:name=".PlaybackService"
android:process=":background" />
進程名字必須以冒號開頭":"以確保該進程屬於你應用中的私有進程.
在你決定建立一個新的進程以前必須理解對這樣作內存的影響. 爲了說明每一個進程的影響, 一個基本空進程會佔用大約1.4兆的內存, 下面的堆內存信息說明這一點
adb shell dumpsys meminfo com.example.android.apis:empty
** MEMINFO in pid 10172 [com.example.android.apis:empty] **
Pss Pss Shared Private Shared Private Heap Heap Heap
Total Clean Dirty Dirty Clean Clean Size Alloc Free
------ ------ ------ ------ ------ ------ ------ ------ ------
Native Heap 0 0 0 0 0 0 1864 1800 63
Dalvik Heap 764 0 5228 316 0 0 5584 5499 85
Dalvik Other 619 0 3784 448 0 0
Stack 28 0 8 28 0 0
Other dev 4 0 12 0 0 4
.so mmap 287 0 2840 212 972 0
.apk mmap 54 0 0 0 136 0
.dex mmap 250 148 0 0 3704 148
Other mmap 8 0 8 8 20 0
Unknown 403 0 600 380 0 0
TOTAL 2417 148 12480 1392 4832 152 7448 7299 148
注意: 上面關鍵的數據是 private dirty 和 private clean 兩項, 第一項主要使用了大約是1.4兆的非分頁內存(分佈在Dalvik heap, native分配, book-keeping, 和庫的加載), 另外執行業務代碼使用150kb的內存.
空進程的內存佔用是至關顯著的, 當你的應用加入了許多業務後會增加得更加迅速. 下面的例子是使用activity顯示一些文字, 當前進程的內存使用情況的分析.
** MEMINFO in pid 10226 [com.example.android.helloactivity] **
Pss Pss Shared Private Shared Private Heap Heap Heap
Total Clean Dirty Dirty Clean Clean Size Alloc Free
------ ------ ------ ------ ------ ------ ------ ------ ------
Native Heap 0 0 0 0 0 0 3000 2951 48
Dalvik Heap 1074 0 4928 776 0 0 5744 5658 86
Dalvik Other 802 0 3612 664 0 0
Stack 28 0 8 28 0 0
Ashmem 6 0 16 0 0 0
Other dev 108 0 24 104 0 4
.so mmap 2166 0 2824 1828 3756 0
.apk mmap 48 0 0 0 632 0
.ttf mmap 3 0 0 0 24 0
.dex mmap 292 4 0 0 5672 4
Other mmap 10 0 8 8 68 0
Unknown 632 0 412 624 0 0
TOTAL 5169 4 11832 4032 10152 8 8744 8609 134
這個比上面多花費了3倍的內存, 只是在 界面 上顯示一些簡單的文字, 用了大約4兆. 從這裏能夠得出一個很重要的結論:若是你的想在應用中使用多進程, 只能有一個進程來負責 UI 的工做, 在其它進程中不能出現任何 UI的工做, 不然會迅速提升內存的使用率(尤爲當你加載 bitmap 資源和其它資源時). 一旦加入了UI的繪製工做就不可能會減小內存的使用了.
另外, 當你的應用超過一個進程時, 保持代碼的緊湊很是重要, 由於如今由相同實現形成的沒必要要的內存開銷會複製到每個進程中, 會形成內存浪費更嚴重的情況出現. 例如, 你使用了枚舉, 不一樣的進程在內存中都會建立和初始化這些常量.而且你全部的抽象適配器和其它臨時的開銷也會和前面同樣被複制過來.
另外要關心的問題是多進程之間的依賴關係. 例如, 當應用中運行默認的進程須要爲UI進程提供內容, 後臺進程的代碼爲進程自己提供內容還要留在內存中爲UI運行提供支持, 若是你的目標是在一個擁有重量級的UI進程的應用裏擁有一個獨立運行的後臺進程, 那麼你在UI進程中則不能直接依賴它, 而要在UI進程使用 service 處理它.