本文大體分為四部分
- 內存優化
- 布局優化
- 編碼優化
- 網絡優化
內存優化
首先說一下內存泄漏和OOM:
- 內存泄漏,因為不恰當的引用導致本該被釋放的資源無法得到釋放。
- OOM,新分配的內存大小加上已經占用的內存大小,超出了限制的內存大小。
內存泄漏更多是因為我們的代碼寫的有問題,OOM更多是因為我們對我們應用內存的占用沒有很好的把控。內存泄漏是導致OOM的一大元兇。
內存優化分為5點來說:
- 減少對象內存占用
- 內存對象的復用
- 避免內存泄漏
- 合理的內存使用策略
- 內存優化輔助工具
一、減少對象內存占用
- 使用更加輕量的數據結構。比如:ArrayMap/SparseArray是替代HashMap的好幫手。關于ArrayMap和SparseArray的使用:
- 適合對象個數的數量級最好在千以內,因為他們的插入和刪除的效率不夠高。
- 查找和插入使用的是二分查找。
- key類型是int時,請使用SparseArray因為它避免了自動裝箱。
- 避免使用Enum。官方說法是,相對于靜態常量,枚舉會消耗兩倍以上的內存。并且在運行時還會產生額外的內存占用。在一個官方實例里,枚舉占用的內存是靜態常量的13倍,運行時內存占用是6倍。
- 減少Bitmap對象的內存占用。對于創建出來的Bitmap對象通常有兩個操作可以優化其內存占用。
- inSampleSize:在載入內存之前,計算一個合適的縮放比例。
- decode format:解碼格式,ARGB_8888(每個像素4個字節,最高精度,有透明通道)、RGB_565(每個像素兩個字節,無透明度)、ARGB_4444(deprecated in Api13)、Alpah_8,不同的解碼格式差別很大。根據情況選擇合適的會比較好。
- 使用更小的圖片。拿到美工給的圖,要留意大圖直接被XML引用有時會出現InflationException,該異常的根本原因就是OOM。
二、內存對象的復用
- 復用系統自帶的資源。系統自帶了很多顏色、動畫、樣式、布局、圖片。使用這些可以減少內存開銷,但需注意版本差異性。
- ListView/GridView/RecyclerView內重復子View的復用。
- Bitmap對象復用。使用inBitmap來復用已經存在的內存區域。3.0之后出現,重用的bitmap大小和解碼格式需要一致。4.4以后優化大小限制,只要小于或等于原bitmap的大小即可??梢跃S護一個有多種典型bitmap的對象池,使得后續bitmap創建都可以找到合適的復用模板。
- 在頻繁調用的方法外創建對象。如onDraw()方法會頻繁調用,在里面做創建對象的操作會迅速增加內存使用,很容易引起頻繁GC甚至是內存抖動。
- StringBuilder/StringBuffer。使用StringBuilder/StringBuffer來替代頻繁的字符串拼接操作。
三、避免內存泄漏
- Activity的泄漏。
- 內部類引用導致。典型的如Handler??紤]盡量使用靜態內部類,同時使用弱引用機制避免互相引用出現的泄漏。
- Activity Context被傳遞到其他實例中,可能導致自身被引用發生泄漏。盡量使用Application Context。除了和UI相關的,如顯示彈窗、啟動Activity、填充布局。參考Android Context。
- 臨時Bitmap對象的回收。臨時創建一個相對比較大的bitmap對象,在經過變換獲得新的bitmap對象之后,應盡快回收之前的bitmap。注意createBitmap()方法可能返回source bitmap,所以需要檢查返回值是否和source bitmap相等。不等才可以對source bitmap執行recycle方法。
- 監聽器的注銷。
- Cursor對象的及時關閉。
- 緩存容器的對象泄漏。如4.0之前,把drawable添加到緩存容器,因為drawable和view的強引用很容易導致activity發生泄漏。
- WebView的泄露。Android的WebView存在很大的兼容性問題,WebView因為不同系統版本不同廠商都粗乃很大的差異,甚至標準的WebView存在內存泄漏的問題(09年發現,13年修復)。根治方法:為WebView使用新進程,通過AIDL進行通信,WebView所在進程根據業務需要在合適時機進行銷毀。
- 慎用static對象,static的生命周期和應用的進程保持一致,使用不當很可能導致內存泄漏。
- 留意單例對象中不合理的引用。單例對象的生命周期和應用保持一致。
四、合理的內存使用策略
- 使用IntentService代替Service。
- 謹慎使用large heap。在清單文件的<application>節點設置largeHeap=true可以為應用生命一個更大的heap控件。會影響用戶體驗,并使GC運行時間更長,任務切換耗能增加。并且,在一些嚴格限制的機器上,largeHeap和通常的heap size大小一樣。你始終應該通過getMemoryClass()來檢查實際獲取到的heap大小。
- 合適的緩存大小。結合可用內存大小等因素設置。
- onLowMemory()和onTrimMemory()。后者從4.0開始提供,提供了更為詳細的系統內存占用級別??梢酝ㄟ^監測系統內存占用適當的釋放自身的一些內存占用。
- 選擇合適的文件夾存放資源文件。圖片會被拉伸以適應不同的設備。對于不希望被拉伸的圖片,放在assets或nodpi目錄下。
- 對大內存分配做Try...Catch...操作。比如給解析大圖時,使用try catch,catch到OOM后將采樣比例增加一倍再次嘗試解析。
- 慎用抽象編程。抽象需要同等量的代碼用于可執行,這些代碼會被mapping到內存中。
- 使用nano protobufs序列化數據。Google設計,類似XML,比XML更加輕量快速簡單。
- 慎用依賴注入。通過掃描你的代碼執行許多初始化操作,會導致你的代碼需要大量的內存空間來mapping代碼,而且mapped pages會長時間保留在內存中。
- 慎用多進程??梢詳U大應用的內存占用范圍,但使用不當會導致顯著增加內存。
- 使用ProGuard剔除不需要的代碼。
- 慎用第三方庫。很多功能會用不上。
五、內存優化輔助工具
- facebook開源的LeakCanary,可用來監測內存泄漏
- Android Monitor,可以查看內存占用,可以查看指向,可以手動觸發GC。分析內存泄漏的流程是
- 手動觸發GC
- 查看JavaHeap
- 點擊Analyzer Task即可進行內存泄漏的分析。
布局優化
分四個方面
- 選擇合適的根節點
- 重用布局文件
- 僅在需要時加載布局
- 避免過度繪制
一、選擇合適的根節點
Android在創建Activity時默認生成的布局為RelativeLayout,而新建布局時默認的根節點為LinearLayout。這是因為
- 在復雜的布局中使用RelativeLayout可以降低布局嵌套,使布局比較扁平。也更加靈活。
- 對于簡單的布局,LinearLayout在不用處理weight屬性的情況下,性能上是優于至少需要計算兩次的RelativeLayout的。
二、重用布局文件
- <include>標簽的使用,要注意如果需要使用layout屬性,必需先設置layout:width和layout:height
- <merge>標簽的使用可減少不必要的視圖嵌套 :
- 添加的子視圖不需要針對父視圖的屬性,只是要添加到父視圖上顯示。根節點可為<merge>。
- 比如在LinearLayout里include另外一個方向相同的LinearLayout,這個被include的視圖的根節點就可以改為merge.
三、僅在需要時加載布局
<ViewStub>
使用時調用inflate即可,也可以調用setVisibility(View.VISIBILITY)。
注意:不支持<merge>標簽的布局。
四、避免過度繪制
比如給根布局設置了圖片背景,但是用戶只能看到子View,根本就沒有看到最下面的背景。但是背景仍要被繪制。這就是過度繪制。
可以通過設置-開發者選項-顯示GPU過度繪制來觀察過度繪制。顏色越深的區域過度繪制越嚴重,藍色最好,紅色最差。
- 去除不必要的背景設置。
- 自定義view時,通過Canvas的clipRect()來繪制部分需要重繪的區域。
五、小知識點
- android:drawableXXX屬性。TextView控件可直接顯示圖片和文字。
- setCompoundDrawable(),代碼中通過該方法實現第一個效果。
- android:divider,使用自帶的分割線。
- space控件,可用于添加空白間隔,該控件不進行繪制。
- android:lineSpacingExtra="",android:text="aaa\nbbb\nccc"多行文字可使用TextView的行間距實現。
- Spannable,使用Spannable來為TextView設置強大的樣式。
編碼優化
幾個點
- 靜態方法。將一項通用的功能寫成靜態方法,調用速度會提升15%-20%,同時不需要創建對象來調用該方法,也不用擔心改變對象的狀態(靜態方法無法訪問非靜態字段)。
- 避免創建不必要的對象
- 靜態最終常量。對基本數據類型以及String常量使用static final修飾,會在dex文件的初始化器中進行初始化,會更快。
- 多使用增強型for循環,但ArrayList使用傳統循環方式。
- 使用系統封裝好的API。系統的API很多功能是通過底層匯編模式執行的,效率會比較高。如數組拷貝的功能,使用System.arrayCopy()會比使用循環一一賦值效率高9倍以上。
- 避免在內部使用getter/setter。內部使用時,字段查找比方法調用效率高。
網絡優化
工具:Android Studio有Network Monitor
主要有:
- 接口設置多樣化,便于App可以以較少的請求來完成業務需求。
- 使用Gzip壓縮request和response,減少數據傳輸大小
- 可以使用Protocol Buffer來代替json、XML等
- 合適的圖片。獲取圖片時告知服務器寬高質量等來獲取合適的圖片資源。
- 設置網絡緩存,來取消不必要的網絡請求。
- 在網絡良好的時候,對一些很有可能會進行操作的數據進行提前獲取。
- 弱網優化,Android Emulator可以設置網絡速度和延遲來做弱網測試。可采取的措施如:不自動加載圖片、先反饋后提交(如用戶點贊,先給提交成功的反饋,記錄下來之后提交)