前言html
本文翻譯自Android開發者官網的一篇文檔,主要用於介紹管理應用內存方面的知識。java
中國版官網原文地址爲:https://developer.android.google.cn/topic/performance/memory。android
路徑爲:Android Developers > Docs > 指南 > Best practies > Performance > Manager Your App`s Memorygit
正文github
隨機可存取內存(RAM)在任何軟件開發環境中都是有價值的資源,可是在移動操做系統中是更加有價值的,由於在移動操做系統中物理內存常常是受到限制的。雖然Android Runtime(ART)和Dalvik虛擬機執行常規的垃圾回收,但這並不意味着您能夠忽略應用於什麼時候何處分配和釋放內存。您仍然須要避免引入內存泄漏以及在合適的時間釋放全部由生命週期回調定義的引用對象,這些內存泄漏常常是由在靜態成員變量中持有對象引用引發的。編程
本頁闡述了您能夠如何積極主動地減小應用的內存使用。關於Android操做系統如何管理內存的信息,請查閱【Android內存管理概述】。緩存
監視可用內存和內存使用數據結構
在您能夠修復應用中內存使用問題以前,您首先應該找到它們。Android Studio中的【Memory Profiler】能夠經過以下的方式幫您找到並診斷內存問題:app
1. 查看隨着時間的推移應用是如何分配內存的。【Memory Profiler】顯示了一份實時圖像,該圖像包含了應用正在使用多少內存,被分配的Java對象數量,以及何時發生垃圾收集。框架
2. 當應用運行時,發起垃圾收集事件而且抓取Java堆快照。
3. 記錄應用的內存分配,而後檢查全部分配的對象,查看每個分配的棧追蹤,而且跳轉到Android Studio編輯器中相應的代碼處。
在事件響應中釋放內存
正如【Android內存管理概述】中所描述的,Android會以多種方式從應用中收回內存,或者若是有必要的話,爲了極重要的任務甚至會殺死整個應用。爲了進一步地幫助平衡系統內存以及避免系統殺死您應用進程的需求,您能夠在Activity類中實現ComponentCallbacks2接口。提供的onTrimMemory()回調方法容許應用不管在前臺仍是後臺都能監聽內存相關的事件,而後釋放對象以響應應用生命週期或者指示須要收回內存的系統事件。
例如,您能夠實現onTrimMemory()回調來響應不一樣的內存相關事件,正以下面所展現的:
1 import android.content.ComponentCallbacks2; 2 // Other import statements ... 3 4 public class MainActivity extends AppCompatActivity 5 implements ComponentCallbacks2 { 6 7 // Other activity code ... 8 9 /** 10 * 當UI隱藏或者系統資源變得不足時,釋放內存. 11 * @param level 引起的內存相關事件. 12 */ 13 public void onTrimMemory(int level) { 14 15 // 判斷引起了哪一個生命週期或系統事件. 16 switch (level) { 17 18 case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: 19 20 /* 21 釋放全部當前持有內存的UI對象. 22 23 用戶接口已經轉移到了後臺。 24 */ 25 26 break; 27 28 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: 29 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: 30 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: 31 32 /* 33 釋放全部您的應用再也不須要運行的內存。 34 35 當應用正在運行時,設備內存不足。 36 被引起的事件指示了內存相關事件的嚴重性。 37 若是引起的事件是TRIM_MEMORY_RUNNING_CRITICAL, 而後系統將會開始殺死背景進程。*/ 38 39 break; 40 41 case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: 42 case ComponentCallbacks2.TRIM_MEMORY_MODERATE: 43 case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: 44 45 /* 46 進程儘量多地釋放內存。 47 48 應用在LRU(譯者注:Least Recently Used,最近最少使用)列表中,而且系統內存不足。 49 該引起的事件指示了應用在LRU列表中的位置。 50 若是該事件是TRIM_MEMORY_COMPLETE,該進程將會成爲首先被終止的進程之一。*/ 51 52 break; 53 54 default: 55 /* 56 釋放全部不是特別重要的數據結構。 57 58 應用收到了一個來自系統的不被識別的內存等級值。把它當成是一個通常的低內存消息。*/ 59 break; 60 } 61 } 62 }
onTrimMemory()回調方法是在Android4.0(API等級爲14)中新增的。對於更早的版本,您可使用onLowMemory(),它大體等同於TRIM_MEMORY_COMPLETE事件。
檢查您應該使用多少內存
爲了容許多個進程同時運行,Android爲每個應用的堆大小分配設置了一個硬性限制。確切的堆大小限制因設備整體可用RAM的多少不一樣而不一樣。若是應用已經達到了堆的容量,而且試圖分配更多內存,系統就會拋出OutOfMemoryError。
爲了不運行時內存溢出,您能夠查詢系統來肯定在當前設備上有多少堆空間可使用。您能夠經過調用getMemoryInfo()查詢系統的這份數據。該方法會返回一個ActivityManager.MemoryInfo對象,該對象會提供關於設備當前的內存狀態,包括可用內存,總內存,以及內存閾值——系統開始殺死進程的內存水平。ActivityManager.MemoryInfo對象也提供了一個簡單的boolean型變量lowMemory,它會告訴您是否設備內存不足。
下面的代碼片斷顯示了一個示例,用於展現如何在應用中使用getMemoryInfo()方法。
1 public void doSomethingMemoryIntensive() { 2 3 // 在作須要申請大量內存的事情以前,檢查看看是否設備處於內存不足狀態。 4 ActivityManager.MemoryInfo memoryInfo = getAvailableMemory(); 5 6 if (!memoryInfo.lowMemory) { 7 // 處理內存密集型的工做 ... 8 } 9 } 10 11 // 爲設備當前的內存狀態獲取MemoryInfo對象. 12 private ActivityManager.MemoryInfo getAvailableMemory() { 13 ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE); 14 ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); 15 activityManager.getMemoryInfo(memoryInfo); 16 return memoryInfo; 17 }
使用更節省內存的代碼結構
一些Android特性,Java類,以及代碼結果趨向於比其它的使用更多內存。您能夠經過在代碼中選擇更高效的替代者來最小化應用使用的內存數量。
謹慎使用service
當不須要使用service時仍讓它保持運行是Android應用可能犯的最嚴重的內存管理錯誤之一。若是應用須要service在後臺執行工做,不要保持它一直運行,除非須要運行一項做業。當service完成任務後,記得中止它。不然,您可能在不經意間引入內存泄漏。
當您啓動一個service,系統偏向於始終保持該service的進程運行。這個行爲讓service進程消耗很大,由於被service使用的RAM不能被其它進程使用。這減小了系統保留在LRU緩存的緩存進程的數量,使得應用的切換變得更加低效。當內存緊張而且系統不會維持足夠的進程來承載全部正在運行的service時,這甚至可能致使系統震盪。
通常來講,您應該避免使用持久性的service,由於它們對可用內存的持續需求。相反,咱們建議您使用一個替代的實現方案,好比【JobScheduler】。關於怎樣使用【JobScheduler】來調度後臺進程的信息,請查【後臺優化】。
若是您必須使用service,限制service壽命最好的辦法就是使用【IntentService】,當處理完啓動它的intent,它會盡快結束本身。更多的信息,請查閱【運行後臺service】。
使用優化過的數據容器
有一些由編程語言提供的類在移動設備上的使用沒有被優化。例如,通常的HashMap實現可能內存效率很低,由於每個映射都須要一個單獨的入口對象。
Android框架包含了若干優化後的數據容器,包括SparseArray,SparseBooleanArray,以及LongSparseArray。例如,SparseArray類更有效,由於它們避免了系統對鍵或值裝箱(譯者注:原始類型到對象類的自動轉換,如int到Integer)的需求(這會爲每一個條目建立一個或兩個對象)。
若是有必要,您能夠始終切換到原始隊列來得到真正精簡的數據結構。
注意代碼抽象
開發者常用抽象簡單地做爲一個良好的編程實踐,由於抽象能夠改善代碼靈活性和可維護性。但是,抽象帶來了顯著的代價:一般它們須要跟多的代碼來執行,須要更多時間和更多RAM來將這些代碼映射到內存。因此,若是您的抽象沒有提供顯著的好處,您應該避免它們。
爲序列化數據使用納米協議緩存
【協議緩存】是一個由Google爲序列化結構化數據設計的語言無關,平臺無關,可繼承的機制——相似於XML,可是更小,更快,更簡單。若是您決定爲您的數據使用協議緩存,您應該始終在客戶端代碼中使用納米協議緩存。常規的協議緩存生成了極其冗餘的代碼,它們可能致使應用中各類類型的問題,好比RAM使用的增加,顯著的APK大小增加,以及執行更慢。
更多信息,請查閱【protobuf readme】中「Nano 版本」部分。
避免內存攪動
正如前面提到的,垃圾收集事件通常不會影響應用的性能。可是,許多很短期內發生的垃圾收集事件可能很快佔用您的幀時間。系統花費在垃圾收集上的時間越多,那麼花費在處理渲染或者流式音頻等事件上的時間就越少。
一般,內存攪動會致使大量的垃圾收集事件發生。實際上,內存攪動描述了發生在給定的時間內分配的臨時對象的數量。
例如,您可能在for循環中分配多個臨時對象。或者您可能在一個視圖的onDraw()方法中建立新的Paint或者Bitmap對象。在這兩種狀況下,應用迅速以高容量建立了許多對象。這些可能快速地消耗全部「年輕代」中可用的內存,強制發生垃圾收集事件。
固然,在您能夠修復它們以前,您須要在代碼中找到這些內存攪動高地方。爲了實現這些,您應用使用Android Studio中的【Memory Profiler】。
一旦您確認了您代碼中發生問題的區域,就嘗試下降性能重要區域內的分配數量。考慮將事物移出內部循環,或者可能把它們移動到一個基於分配結構的【工廠】。
移除內存密集型的資源和庫
您代碼中的一些資源和庫可能在您絕不知情的狀況下吞噬掉內存。您的APK的整體大小,包括第三方庫或者嵌入的資源,可能影響到您應用消耗了多少內存。您能夠經過從您代碼中移除冗餘的、沒必要要的、臃腫的組件、資源、或者庫來改善應用內存消耗。
減小整體APK大小
經過減小應用的整體大小,您能夠顯著地下降應用的內存使用。Bitmap大小、資源、動畫幀、以及第三方庫都有可能影響APK的大小。Android Studio和Android SDK提供了多種工具來幫您縮小資源的大小和外部依賴。
更多關於如何下降總體應用大小的信息,請查閱【縮減應用大小】。
爲依賴注入使用Dagger2
依賴注入框架能夠簡化您寫的代碼,而且提供一個能夠適應的環境,該環境對測試和其它配置的改變是有用的。
若是您意圖在應用中使用依賴注入框架,考慮使用【Dagger2】。Dagger沒有使用反射來掃描應用的代碼。Dagger的靜態編譯時實現意味着它能夠應用於Android應用,而不用在乎沒必要要的運行時消耗或者內存使用。
其它的使用反射的依賴注入框架趨向於經過掃描註解代碼來初始化進程。該進程可能須要明顯多的CPU週期和RAM,而且當應用啓動時可能致使引入注目的滯後。
謹慎使用外部庫
外部庫代碼常常不是爲移動環境編寫的,而且在移動客戶端使用時多是無效的。當您決定使用外部庫時,您可能須要爲移動設備優化那個庫。在全然決定使用它前,爲當前的工做作好計劃,而且依據代碼大小和RAM足跡分析這個庫。
即便是一些爲移動優化庫也可能致使問題,由於不一樣的實現方式。例如,一個庫可能使用納米協議緩存,然而其它應用卻使用微型協議緩存,這致使了在您應用中有兩種不一樣的協議緩存實現。這可能發生於不一樣的日誌、分析、圖片加載框架、緩存以及您沒法預料的許多其餘事情的實現方式中。
雖然【ProGuard】能夠幫您移除具備正確標記的API和資源,但它沒法移除庫中大型的內部依賴項。這些庫中您想要的特性可能須要較低級別的依賴項。當您使用庫中Activity子類時(它將趨向於擁有普遍的依賴鏈),當庫使用反射時(這是很廣泛的而且意味着您須要花費不少時間手動調整ProGuard來讓它工做),等等,這將變得尤其有問題。
同時避免爲了幾十個特性中的一兩個而使用共享庫。您不想導入大量的您甚至不使用的代碼和開銷。當您考慮是否使用庫時,尋找一種和您所需的高度匹配的實現方案。否者,您可能要決定建立您本身的實現方式。
結語
本文最大限度保持原文的意思,因爲筆者水平有限,如有翻譯不許確或不穩當的地方,請指正,謝謝!