目錄
一、APP 內存限制
二、內存的三大問題
2.1、內存抖動(Memory Churn)
2.1.1 頻繁創建短生命周期對象
2.1.2 系統API或第三方庫的不合理使用
2.1.3 Handler使用不當
2.2、內存泄漏(Memory Leak)
2.2.1 靜態變量持有Activity或Context引用
2.2.2 未取消的回調或監聽器
2.2.3 非靜態內部類持有外部類引用
2.2.4 Timer或Handler未正確取消
2.2.5 Bitmap未及時回收
2.2.6 資源文件未關閉
2.2.7 WebView
2.3、內存溢出(OutOfMemoryError)
2.3.1 為對象分配內存時達到進程的內存上限
2.3.2 沒有足夠大小的連續地址空間
2.3.3 創建線程失敗
2.3.4 內存泄漏積累
2.3.5 集合類對象未及時清理
三、內存問題解決方案
3.1 選擇合適的數據結構
3.2 避免使用枚舉
3.3 謹慎使用多進程
3.4 謹慎使用 Large Head
3.5 使用NDK
四、 圖片優化
4.1 如何對圖片進行緩存?
4.2 計算圖片占用內存的大小
4.3 如何計算圖片占用內存的大小?
4.4 圖片內存體積優化總結
五、內存優化5R法則
相關推薦
一、APP 內存限制
Android 給每個 App 分配一個 VM ,讓App運行在 dalvik 上,這樣即使 App 崩潰也不會影響到系統。系統給 VM 分配了一定的內存大小,App 可以申請使用的內存大小不能超過此硬性邏輯限制,就算物理內存富余,如果應用超出 VM 最大內存,就會出現內存溢出 crash。
由程序控制操作的內存空間在 heap 上,分 java heapsize 和 native heapsize。
Java申請的內存在 vm heap 上,所以如果 java 申請的內存大小超過 VM 的邏輯內存限制,就會出現內存溢出的異常。(如:-Xmx4096)
native 層內存申請不受其限制,native 層受 native process 對內存大小的限制。
Android 虛擬機申請內存最大內存是有限制的,不同設備申請的最大內存是不一樣的。
二、內存的三大問題
1、內存抖動:內存波動圖形呈 鋸齒張、GC導致卡頓。
2、內存泄漏:在當前應用周期內不再使用的對象被GC Roots引用,導致不能回收,使實際可使用內存變小。
3、內存溢出:即OOM,OOM時會導致程序異常。Android設備出廠以后,虛擬機對單個應用的最大內存分配就確定下來了,超出這個值就會OOM。
2.1、內存抖動(Memory Churn)
內存抖動是指內存頻繁分配和回收,導致內存曲線呈鋸齒狀波動,可能導致應用頁面卡頓或響應緩慢。常見的內存抖動場景及解決方案:
2.1.1 頻繁創建短生命周期對象
在循環或頻繁調用的方法中創建大量短生命周期的對象,如字符串拼接、對象頻繁創建等。
在自定義控件的onMeasure
、onLayout
、onDraw
等方法中創建對象,由于這些方法會頻繁調用,導致對象頻繁創建和回收。
解決方案:
- 避免在循環或頻繁調用的方法中創建對象。
- 使用StringBuilder等高效字符串拼接方式替代加號拼接。
- 在自定義控件的繪制方法中,盡量復用對象,避免頻繁創建。
- 對于需要頻繁創建和銷毀的對象,可以考慮使用對象池來復用對象。對象池能夠減少對象的創建和銷毀次數,從而降低內存抖動的發生概率。
2.1.2 系統API或第三方庫的不合理使用
調用系統API或第三方庫時,沒有合理使用其提供的對象復用機制,導致大量對象被創建。
解決方案:
- 深入了解系統API和第三方庫的工作原理,合理使用其提供的對象復用機制。
- 避免不必要的對象創建,如使用
Message.obtain()
方法獲取Message對象,而不是直接創建新的Message對象。
2.1.3 Handler使用不當
Handler發送大量消息,且消息處理不及時,導致消息對象堆積。
解決方案:
- 在Handler中處理消息時,確保及時處理并釋放消息對象。
- 對于延時消息,要確保在Activity或View生命周期結束前取消未處理的消息。
- 隊列優化=>重復消息過濾。
- 隊列優化=>互斥消息取消。
- 復用消息,使用
Message.obtain()
方法獲取Message對象。 - 使用消息空閑ldleHandle
2.2、內存泄漏(Memory Leak)
內存泄漏是指長生命周期的對象持有短生命周期對象的引用,應用程序中的對象在不再需要時仍然被引用,導致垃圾回收器(Garbage Collector,GC)無法回收這些對象所占用的內存。內存泄漏會導致可用內存逐漸減少,最終可能導致應用程序崩潰(OOM)或系統變得非常緩慢。常見的內存泄露場景及解決方案:
2.2.1 靜態變量持有Activity或Context引用
在靜態變量中持有Activity或Context的強引用,當Activity或Context不再需要時,由于靜態變量的生命周期與應用程序相同,導致這些對象無法被回收。
解決方案:
- 避免在靜態變量中持有Activity或Context的強引用。
- 如果確實需要持有Context,考慮使用Application Context或弱引用(WeakReference)。
2.2.2 未取消的回調或監聽器
在Activity或Fragment中注冊回調或監聽器,但未在適當的生命周期方法中取消注冊,導致Activity或Fragment被銷毀后,回調或監聽器仍持有其引用。
解決方案:
- 在Activity或Fragment的onDestroy或onDetach方法中取消所有回調和監聽器注冊。
- 使用View的觀察者模式時,確保在View不再需要時解除觀察。
2.2.3 非靜態內部類持有外部類引用
非靜態內部類默認持有其外部類的引用,如果非靜態內部類被長期持有(如作為靜態變量的成員),則外部類也無法被回收。
解決方案:
- 將內部類聲明為靜態內部類,并通過構造方法傳遞必要的Context或其他引用。
- 如果內部類需要訪問外部類的成員,考慮使用弱引用持有外部類的引用。
2.2.4 Timer或Handler未正確取消
使用Timer或Handler時,如果未正確取消定時任務或消息,當Activity或Fragment銷毀后,這些定時任務或消息仍可能持有其引用。
解決方案:
- 在Activity或Fragment的onDestroy或onDetach方法中取消所有Timer任務。
- 對于Handler,確保在Activity或View生命周期結束前處理完所有消息,并調用handler.removeCallbacksAndMessages(null)來取消所有回調和消息。
2.2.5 Bitmap未及時回收
在加載大圖片或處理圖片時,如果未及時回收Bitmap對象,可能導致內存泄露。
解決方案:
- 在不再需要Bitmap時,及時調用bitmap.recycle()方法回收Bitmap。
- 使用圖片加載庫(如Glide、Picasso)時,這些庫通常會自動管理Bitmap的回收,但仍需注意避免在Activity或Fragment銷毀后繼續加載圖片。
2.2.6 資源文件未關閉
在處理文件、數據庫連接等資源時,如果未正確關閉這些資源,也可能導致內存泄露。