詳解Android內存優化策略

前言

在開始之前需要先搞明白一個問題,為什麼要做內存優化?或者說做內存優化的目的是什麼?

一、內存優化策略

內存優化一般從兩個方向著手優化,一方面就是上篇博客寫的防止內存泄漏,避免不必要的內存資源浪費;另一方面就是APP中大對象的優化,減小大對象占用的內存。

二、具體優化的點

1.避免內存泄漏

這裡直接看上篇博客就行:
詳解Android內存泄露及優化方案

2.Bitmap等大對象的優化策略

圖片加載算是內存占用的罪魁禍首,而且也是最常見的,所以優化bitmap的占用內存是很關鍵的。
Bitmap的內存計算公式如下:

Bitmap占用內存 = 分辨率 * 單個像素點的內存

比如說一個 1920 * 1080 的圖片,它所占用的內存就是1920 * 1080 * 單個像素點內存。因此,對於Bitmap的優化就可以從分辨率和單個像素點兩個方面來進行優化。

(1) 優化Bitmap分辨率

通常APP加載一張圖片時候,ImageView的大小是確定的,比如一個ImageView的大小設置為 100 * 100 ,但是被加載的Bitmap的分辨率是 200 * 200,那麼就可以通過采樣壓縮將該 ‘Bitmap’ 的分辨率壓縮到 ‘100 * 100’。通過這一壓縮操作可以直接減少4倍的內存大小。代碼如下:

val options = BitmapFactory.Options()
options.inSampleSize = 2 // 設置采樣率為2,則會每兩個像素點采一個像素,最終分辨率寬高變為原來的 1/2
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.image, options)

(2) 優化單個像素點內存

計算機中的圖像一般都是由 紅、綠、藍 三個通道加上一個透明通道組成的,因此一個像素點也是由紅、綠、藍,以及一個透明通道組成,對應到內存就是通過byte來表示,比如用2個 byte 來存儲一個像素點,那麼每個通道就占用 4 bit 的內存,而如果用 4 個 byte 來存儲一個像素點,那麼每個通道就占用 1 個byte。4 字節的像素點,相比2字節的像素點可以表示的色彩會更加豐富,因此四字節的像素點組成的圖像質量也更加清晰。(一個Byte由8 bits組成,是數據存儲的基礎單位,1Byte又稱為一個字節)

在 Android 的 Bitmap 中單個像素點占用的內存與 Bitmap 的 inPreferredConfig 參數配置有關系,代碼設置如下:

     final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;//隻解析圖片邊沿,獲取寬高
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        BitmapFactory.decodeFile(filePath, options);
        // 計算縮放比
        options.inSampleSize = calculateInSampleSize(options, desWidth, desHeight);
        // 完整解析圖片返回bitmap
        options.inJustDecodeBounds = false;
        Bitmap bm = BitmapFactory.decodeFile(filePath, options);

options.inPreferredConfig = Bitmap.Config.RGB_565;設置的參數如下表:

Config設置 占用內存(byte) 備註
ALPH_8 1 隻包含一個透明通道,透明通道占用 8bit,即 1byte
RGB_565 2 包含R/G/B三個顏色通道,不包含透明通道,三個通道占用的內存分別為5bit/6bit/5bit
ARGB_4444 2 已廢棄,包含A/R/G/B四個顏色通道,每個通道占用4bit
ARGB_8888 4 24位真彩色,Android默認配置,每個通道占用 8bit
RGBA_F16 8 Android 8.0 新增,每個通道占用16bit,即兩個字節

在Android系統中 Bitmap 的默認色彩模式為 ARGB_8888, 即每個像素占用瞭4byte,那麼在默認情況下,一張分辨率為1920 * 1080 的圖片,加載到內存後占用的內存大小為1920 * 1080 * 4 = 7.91M

可以通過設置 inPreferredConfig 參數來設置對應的色彩模式,例如,一個不包含透明通道的圖片,我們可以將其設置為RGB_565,即保證瞭圖片的質量,又減少瞭內存的占用。
此時,一張 1920 * 1080 的圖片加載到內存後的內存大小為 1920 * 1080 * 2 = 3.955M,比默認情況下的內存占用減小瞭一半。

(3) Bitmap的緩存策略

通過緩存策略也可以一定程度上的優化內存占用問題,比如 Glide 框架中采用瞭三級本地緩存策略來實現Bitmap的優化,通過設置活動緩存、LRU內存緩存和本地緩存。對於相同參數的ImageView,在內存中隻保存一份,以此來減少內存大小。

(4) drawable資源選擇合適的drawable文件夾存放

例如我們隻在 hdpi 的目錄下放置瞭一張 100 * 100 的圖片,那麼根據換算關系,分辨率匹配到 xxhdpi 的手機去引用這張圖片時就會被拉伸到 200*200。需要註意到在這種情況下,內存占用是會顯著提高的。對於不希望被拉伸的圖片,需要放到 assets 或者 nodpi 的目錄下。

(5) 其他大對象的優化

可以使用更加輕量級的數據結構。例如,我們可以考慮使用 ArrayMap/SparseArray 而不是 HashMap 等傳統數據結構,相比起 Android 系統專門為移動操作系統編寫的 ArrayMap 容器,在大多數情況下,HashMap 都顯示效率低下,更占內存。另外,SparseArray更加高效在於,避免瞭對key與value的自動裝箱,並且避免瞭裝箱後的解箱。

(6) 避免內存抖動

內存抖動是指在短時間內突然創建大量的對象,頻繁的引發GC回收,造成內存波動的情況。在開發中應該避免頻繁的創建對象,來避免內存抖動。因為內存抖動會頻繁觸發 GC,而GC又會引起 STW 問題,直接影響程序的性能。

比如在繪制自定義View的時候一定要避免在onDraw或者onMeasure中創建對象。

3.原生API回調釋放內存

Android系統提供瞭一些回調來通知當前應用的內存使用情況,比如下邊的兩個方法:

onLowMemory() 通常來說,當所有的Background應用都被kill掉的時候,forground應用會收到onLowMemory()的回調。在這種情況下,需要盡快釋放當前應用的非必須的內存資源,從而確保系統能夠繼續穩定運行。尤其是要釋放Glide中緩存的Bitmap資源,通過調用Glide.onLowMemory方法進行資源回收。

onTrimMemory() Android系統從4.0開始還提供瞭onTrimMemory()的回調,當系統內存達到某些條件的時候,所有正在運行的應用都會收到這個回調,同時在這個回調裡面會傳遞以下的參數,代表不同的內存使用情況,收到onTrimMemory()回調的時候,需要根據傳遞的參數類型進行判斷,合理的選擇釋放自身的一些內存占用,一方面可以提高系統的整體運行流暢度,另外也可以避免自己被系統判斷為優先需要殺掉的應用。例如調用Glide.onTrimMemory()來進行bitmap的回收。

4.內存排查工具

(1)LeakCanary監測內存泄漏

在debug模式下會一直開著LeakCanary來檢測內存泄漏問題,根據LeanCannary提供的引用連可以快速定位到內存泄漏的位置。

(2)通過Proflier監控內存

在一個功能開發完成後可以通過Profiler來檢測APP的內存使用情況。反復的打開關閉頁面,然後觸發GC,內存是否能夠減少。

(3)通過MAT工具排查內存泄漏

MAT提供瞭很強大的功能,可以查看對象的深堆、淺堆的內存大小等。

總結

平時開發對於這塊的關註不是很多,可能在沒有出現內存不足的問題前不會考慮這些,項目的要求沒有那麼高,學習過這些點以後需要在開發中慢慢關註這些問題。

到此這篇關於詳解Android內存優化策略的文章就介紹到這瞭,更多相關Android內存優化內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: