比起前幾年,現在的 Android 裝置擁有更大的記憶體。但是,即使現在可以使用更多的記憶體,也是有一個上限的,具體大小和各個廠商的設定有關。如果記憶體使用不當,還是會影響到APP的性能。記憶體問題主要有兩類,一是記憶體溢出,二是記憶體洩漏。解決記憶體問題,主要靠借助工具檢測分析,然後做代碼優化。
一、Android 應用開發中的記憶體問題
1.1 單個程序可用記憶體限制
目前的 Android 裝置,動辄4G、6G甚至8G記憶體,但是,Android裝置的記憶體再大,每個應用可以使用的記憶體大小也是有限制的,具體可以使用的最大記憶體和各大廠商的出廠設定有關。
可以通過adb 檢視應用可以使用的最大記憶體:
adb shell cat /system/build.prop
// ...
dalvik.vm.heapstartsize=8m
dalvik.vm.heapgrowthlimit=256m
dalvik.vm.heapsize=512m
// ...
上面的裝置是 galaxy s7,可以使用的最大記憶體為512m,這需要在 manifest.xml 中 application 節點添加 android:largeHeap=”true”,否則,最多隻能使用256m。
也可以直接檢視它們的值:
adb shell getprop dalvik.vm.heapgrowthlimit
m
adb shell getprop dalvik.vm.heapsize
m
adb shell getprop dalvik.vm.heapstartsize
m
現在的應用,對于圖檔品質的要求越來越高,動辄幾百K,甚至上M一張圖檔,對于需要加載大量圖檔的應用來說,256m記憶體,分分鐘就上去了。256m說大不大,說小不小,但是使我們的應用盡量少用記憶體總不會錯。因為占用記憶體越大,gc頻率就可能越高,然後就會出現卡頓的現象。
1.2 記憶體溢出
記憶體溢出就是你要求配置設定的記憶體超出了系統能給你的,系統不能滿足需求,于是産生溢出,簡單來說就是,需要的記憶體超過了可以使用的最大記憶體值。一旦出現記憶體溢出,就會報 out of memory 的錯誤,app crash。 導緻記憶體溢出的原因有很多,這裡根據項目中曾經出現過的做一個總結:
-
直接加載原圖
需要浏覽大圖的時候,使用 ImageView 直接加載原圖;或者需要加載Bitmap時直接讀取了原圖大小。
-
使用圖檔背景
app的每個頁面都使用一張圖檔作為背景(表示對圖檔背景深惡痛絕),導緻記憶體暴增,然後OOM。
-
接口處理太多資料
有接口需要将檔案如圖檔等進行URLEncode為字元串,然後通過接口上傳到伺服器,然後一次處理多個檔案,導緻OOM。
-
記憶體洩露并堆積
Activity和對話框等記憶體洩露,得不到釋放,導緻OOM。
1.3 記憶體洩漏
記憶體洩漏是指程式在申請記憶體後,無法釋放已申請的記憶體空間。記憶體洩露和記憶體溢出是有差別的,記憶體溢出必然導緻OOM,而記憶體洩露則可能導緻記憶體溢出。記憶體洩露的原因有很多,下面是比較常見的幾個原因:
- 靜态變量導緻記憶體洩露
-
非靜态内部類導緻記憶體洩漏,如Handler等,持有Activity的引用無法釋放
Activity中非靜态内部類如Handler等,并不是總會引起記憶體洩漏,隻是可能。
- 線程未完成任務導緻Activity不能釋放,記憶體洩漏,如Thread、AsyncTask、Timer和TimerTask等。
- WebView導緻記憶體洩漏
- 錯誤使用單例模式(持有Activity的Context)
- 資源未關閉造成的記憶體洩漏(IO操作,資料庫操作)
- 屬性動畫造成記憶體洩露
- 未取消注冊或回調導緻記憶體洩露
二、記憶體優化方法
記憶體優化可以從四個方向進行:開源,節流,複用,回收。開源,即必要的時候使用largeHeap、多程序等,使應用可以使用更多的記憶體,需要慎重考慮,因為largeHeap和多程序引進的問題比它們帶來的優化更多。節流,減少不必要的記憶體配置設定,這個需要在有一個良好的編碼習慣,是每個開發者應該盡力做到的。複用,顧名思義,就是可以複用的對象就不要重複建立,如ListView 使用ViewHolder進行優化就是複用了View。
2.1 開源
讓應用使用最大記憶體
讓應用可以使用最大記憶體,隻需要在 manifest.xml 中設定 android:largeHeap=”true” 即可。這個設定效果還是很明顯的,如需要加載一張原圖,沒有添加這個屬性之前,使用Bitmap.setResources等方法時應用可能直接就 Crash 了,而增大應用可用記憶體後,加載一張原圖可能就不會導緻 crash 了(要加載大圖?傳送門Android 高清加載巨圖方案 拒絕壓縮圖檔)。
多程序
盡量不要使用多程序,因為多程序帶來的問題可能比其帶來的優化還要多,如果不了解,不熟悉,就不要使用。但是,當有需要的時候還是要考慮一下多程序,需要多程序時,就需要先了解下IPC。多程序如何實作,有什麼坑,這裡就不多做描述,畢竟總結起來比記憶體優化篇幅長多了。
2.2 節流
節流的含義是減少不必要的記憶體配置設定,從int還是long類型選擇,到Bitmap加載圖檔,到是否需要将某個屬性更新為成員變量,可以從各個方面去考慮、優化。下面列出幾個可以優化的點:
- 采用合适的資料類型
- 使用 BitmapFactory.Options 加載合适尺寸大小和合适位深的圖檔
- 避免使用圖檔背景,如果必要,看看是否可以使用顔色背景和更小的圖檔一起替換
- 使用 ViewStub 延遲加載那些可能用不到的View
- 避免使用Activity作為全局變量,如果有必要,看看是否可以使用Application的context代替
- 如果需要,使用StringBuilder代替字元串拼接
2.3 複用
- 使用ViewHolder優化ListView ( RecyclerView代替ListView是一個不錯的選擇 )
- 使用三級緩存的圖檔加載工具顯示圖檔,如Glide等
2.4 回收
不再需要的對象就要及時回收,一般來說GC會幫我們擦屁股,但是有些對象仍然需要主動釋放,或者一些不規範的代碼會導緻對象無法被GC回收。
- 及時回收不再使用的Bitmap
- 根據需要修改象的引用類型:strong reference,SoftReference,WeakReference,PhantomReference
- 注意 Handler、線程、Timer 等異步工具類的使用,防止記憶體洩漏
- 及時關閉 closeable,如 InputStream 和 OutputStream,資料庫的cursor等
三、記憶體問題檢測與定位
有很多工具可以檢測發現記憶體問題,靜态代碼分析工具 Lint,運作時發現代碼問題的 StrictMode,專門用于記憶體洩漏檢查的 LeakCanary。
3.1 Lint
Lint 是一個很好的代碼分析工具,在我們編碼階段就可以給予我們很“友好”的優化提示,把 bug 攔截在編譯之前。
Lint 是Android Studio 提供的 代碼掃描分析工具,它可以幫助我們發現代碼結構/品質問題,同時提供一些解決方案,而且這個過程不需要我們手寫測試用例。
除了發現代碼結構/品質問題外,Lint 還可以發現和删除沒有使用的資源,進而減少apk的體積。Lint 的詳細介紹和使用參考:Android 性能優化:使用 Lint 優化代碼、去除多餘資源
3.2 嚴格模式
StrictMode類是Android 2.3 (API 9)引入的一個工具類,可以用來幫助開發者發現代碼中的一些不規範的問題,以達到提升應用響應能力的目的。舉個例子來說,如果開發者在UI線程中進行了網絡操作或者檔案系統的操作,而這些緩慢的操作會嚴重影響應用的響應能力,甚至出現ANR對話框。為了在開發中發現這些容易忽略的問題,我們使用StrictMode,系統檢測出主線程違例的情況并做出相應的反應,最終幫助開發者優化和改善代碼邏輯。
嚴格模式的接入比較簡單,但對于Android的性能優化(記憶體洩漏和ANR)有非常大的幫助,詳情可以參考:Android嚴苛模式StrictMode使用詳解
3.3 LeakCanary
在實際的項目中,其實沒有用到LeakCanary,但是各種書,部落格都在介紹 LeakCanary,想必也是一個很好的工具。LeakCanary的接入也不難,詳情參考:LeakCanary 中文使用說明
3.4 Android Profiler 記憶體分析
Android Profiler 是 Android studio 3.0 版本代替原有的Android Monitor的性能分析工具。使用 Android Profiler分析記憶體洩漏請看手把手教你在Android Studio 3.0上分析記憶體洩漏,如果還在用以前版本看這裡Android性能優化第(二)篇—Memory Monitor檢測記憶體洩露
四、總結
導緻 OOM 的問題大多是記憶體洩漏的問題,特别是Activity無法釋放的問題尤為嚴重。通過各種工具分析定位,比較容易找到問題的代碼,進而改正它。而除了記憶體洩漏,其它一些問題則是比較難發現,因為這些問題隐藏在代碼間。要做到心中有數,就要有一個良好的編碼規範,該配置設定建立對象時建立,該複用的複用,該回收時回收。記憶體優化是一條很遠路,優化在每一行代碼之間,隻有時刻保持警覺,嚴格要求自己,才能寫出健壯的代碼。
參考
Android記憶體優化——常見記憶體洩露及優化方案
ANDROID記憶體優化(大彙總——中)
Andoird優化(二)記憶體優化點進來看看不會後悔的
Android 性能優化之記憶體洩漏檢測以及記憶體優化(中)
Android性能優化之利用LeakCanary檢測記憶體洩漏及解決辦法
Android 性能優化:使用 Lint 優化代碼、去除多餘資源
Android嚴苛模式StrictMode使用詳解
LeakCanary 中文使用說明
Android性能優化第(二)篇—Memory Monitor檢測記憶體洩露
手把手教你在Android Studio 3.0上分析記憶體洩漏