詳解Android內(nèi)存優(yōu)化策略
前言
在開始之前需要先搞明白一個問題,為什么要做內(nèi)存優(yōu)化?或者說做內(nèi)存優(yōu)化的目的是什么?
一、內(nèi)存優(yōu)化策略
內(nèi)存優(yōu)化一般從兩個方向著手優(yōu)化,一方面就是上篇博客寫的防止內(nèi)存泄漏,避免不必要的內(nèi)存資源浪費;另一方面就是APP中大對象的優(yōu)化,減小大對象占用的內(nèi)存。
二、具體優(yōu)化的點
1.避免內(nèi)存泄漏
這里直接看上篇博客就行:
詳解Android內(nèi)存泄露及優(yōu)化方案
2.Bitmap等大對象的優(yōu)化策略
圖片加載算是內(nèi)存占用的罪魁禍?zhǔn)?,而且也是最常見的,所以?yōu)化bitmap的占用內(nèi)存是很關(guān)鍵的。
Bitmap的內(nèi)存計算公式如下:
Bitmap占用內(nèi)存 = 分辨率 * 單個像素點的內(nèi)存
比如說一個 1920 * 1080 的圖片,它所占用的內(nèi)存就是1920 * 1080 * 單個像素點內(nèi)存。因此,對于Bitmap的優(yōu)化就可以從分辨率和單個像素點兩個方面來進行優(yōu)化。
(1) 優(yōu)化Bitmap分辨率
通常APP加載一張圖片時候,ImageView的大小是確定的,比如一個ImageView的大小設(shè)置為 100 * 100 ,但是被加載的Bitmap的分辨率是 200 * 200,那么就可以通過采樣壓縮將該 ‘Bitmap' 的分辨率壓縮到 ‘100 * 100'。通過這一壓縮操作可以直接減少4倍的內(nèi)存大小。代碼如下:
val options = BitmapFactory.Options() options.inSampleSize = 2 // 設(shè)置采樣率為2,則會每兩個像素點采一個像素,最終分辨率寬高變?yōu)樵瓉淼?1/2 val bitmap = BitmapFactory.decodeResource(resources, R.drawable.image, options)
(2) 優(yōu)化單個像素點內(nèi)存
計算機中的圖像一般都是由 紅、綠、藍 三個通道加上一個透明通道組成的,因此一個像素點也是由紅、綠、藍,以及一個透明通道組成,對應(yīng)到內(nèi)存就是通過byte來表示,比如用2個 byte 來存儲一個像素點,那么每個通道就占用 4 bit 的內(nèi)存,而如果用 4 個 byte 來存儲一個像素點,那么每個通道就占用 1 個byte。4 字節(jié)的像素點,相比2字節(jié)的像素點可以表示的色彩會更加豐富,因此四字節(jié)的像素點組成的圖像質(zhì)量也更加清晰。(一個Byte由8 bits組成,是數(shù)據(jù)存儲的基礎(chǔ)單位,1Byte又稱為一個字節(jié))
在 Android 的 Bitmap 中單個像素點占用的內(nèi)存與 Bitmap 的 inPreferredConfig 參數(shù)配置有關(guān)系,代碼設(shè)置如下:
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;設(shè)置的參數(shù)如下表:
Config設(shè)置 | 占用內(nèi)存(byte) | 備注 |
---|---|---|
ALPH_8 | 1 | 只包含一個透明通道,透明通道占用 8bit,即 1byte |
RGB_565 | 2 | 包含R/G/B三個顏色通道,不包含透明通道,三個通道占用的內(nèi)存分別為5bit/6bit/5bit |
ARGB_4444 | 2 | 已廢棄,包含A/R/G/B四個顏色通道,每個通道占用4bit |
ARGB_8888 | 4 | 24位真彩色,Android默認配置,每個通道占用 8bit |
RGBA_F16 | 8 | Android 8.0 新增,每個通道占用16bit,即兩個字節(jié) |
在Android系統(tǒng)中 Bitmap 的默認色彩模式為 ARGB_8888, 即每個像素占用了4byte,那么在默認情況下,一張分辨率為1920 * 1080 的圖片,加載到內(nèi)存后占用的內(nèi)存大小為1920 * 1080 * 4 = 7.91M
可以通過設(shè)置 inPreferredConfig 參數(shù)來設(shè)置對應(yīng)的色彩模式,例如,一個不包含透明通道的圖片,我們可以將其設(shè)置為RGB_565,即保證了圖片的質(zhì)量,又減少了內(nèi)存的占用。
此時,一張 1920 * 1080 的圖片加載到內(nèi)存后的內(nèi)存大小為 1920 * 1080 * 2 = 3.955M,比默認情況下的內(nèi)存占用減小了一半。
(3) Bitmap的緩存策略
通過緩存策略也可以一定程度上的優(yōu)化內(nèi)存占用問題,比如 Glide 框架中采用了三級本地緩存策略來實現(xiàn)Bitmap的優(yōu)化,通過設(shè)置活動緩存、LRU內(nèi)存緩存和本地緩存。對于相同參數(shù)的ImageView,在內(nèi)存中只保存一份,以此來減少內(nèi)存大小。
(4) drawable資源選擇合適的drawable文件夾存放
例如我們只在 hdpi 的目錄下放置了一張 100 * 100 的圖片,那么根據(jù)換算關(guān)系,分辨率匹配到 xxhdpi 的手機去引用這張圖片時就會被拉伸到 200*200。需要注意到在這種情況下,內(nèi)存占用是會顯著提高的。對于不希望被拉伸的圖片,需要放到 assets 或者 nodpi 的目錄下。
(5) 其他大對象的優(yōu)化
可以使用更加輕量級的數(shù)據(jù)結(jié)構(gòu)。例如,我們可以考慮使用 ArrayMap/SparseArray 而不是 HashMap 等傳統(tǒng)數(shù)據(jù)結(jié)構(gòu),相比起 Android 系統(tǒng)專門為移動操作系統(tǒng)編寫的 ArrayMap 容器,在大多數(shù)情況下,HashMap 都顯示效率低下,更占內(nèi)存。另外,SparseArray更加高效在于,避免了對key與value的自動裝箱,并且避免了裝箱后的解箱。
(6) 避免內(nèi)存抖動
內(nèi)存抖動是指在短時間內(nèi)突然創(chuàng)建大量的對象,頻繁的引發(fā)GC回收,造成內(nèi)存波動的情況。在開發(fā)中應(yīng)該避免頻繁的創(chuàng)建對象,來避免內(nèi)存抖動。因為內(nèi)存抖動會頻繁觸發(fā) GC,而GC又會引起 STW 問題,直接影響程序的性能。
比如在繪制自定義View的時候一定要避免在onDraw或者onMeasure中創(chuàng)建對象。
3.原生API回調(diào)釋放內(nèi)存
Android系統(tǒng)提供了一些回調(diào)來通知當(dāng)前應(yīng)用的內(nèi)存使用情況,比如下邊的兩個方法:
onLowMemory() 通常來說,當(dāng)所有的Background應(yīng)用都被kill掉的時候,forground應(yīng)用會收到onLowMemory()的回調(diào)。在這種情況下,需要盡快釋放當(dāng)前應(yīng)用的非必須的內(nèi)存資源,從而確保系統(tǒng)能夠繼續(xù)穩(wěn)定運行。尤其是要釋放Glide中緩存的Bitmap資源,通過調(diào)用Glide.onLowMemory方法進行資源回收。
onTrimMemory() Android系統(tǒng)從4.0開始還提供了onTrimMemory()的回調(diào),當(dāng)系統(tǒng)內(nèi)存達到某些條件的時候,所有正在運行的應(yīng)用都會收到這個回調(diào),同時在這個回調(diào)里面會傳遞以下的參數(shù),代表不同的內(nèi)存使用情況,收到onTrimMemory()回調(diào)的時候,需要根據(jù)傳遞的參數(shù)類型進行判斷,合理的選擇釋放自身的一些內(nèi)存占用,一方面可以提高系統(tǒng)的整體運行流暢度,另外也可以避免自己被系統(tǒng)判斷為優(yōu)先需要殺掉的應(yīng)用。例如調(diào)用Glide.onTrimMemory()來進行bitmap的回收。
4.內(nèi)存排查工具
(1)LeakCanary監(jiān)測內(nèi)存泄漏
在debug模式下會一直開著LeakCanary來檢測內(nèi)存泄漏問題,根據(jù)LeanCannary提供的引用連可以快速定位到內(nèi)存泄漏的位置。
(2)通過Proflier監(jiān)控內(nèi)存
在一個功能開發(fā)完成后可以通過Profiler來檢測APP的內(nèi)存使用情況。反復(fù)的打開關(guān)閉頁面,然后觸發(fā)GC,內(nèi)存是否能夠減少。
(3)通過MAT工具排查內(nèi)存泄漏
MAT提供了很強大的功能,可以查看對象的深堆、淺堆的內(nèi)存大小等。
總結(jié)
平時開發(fā)對于這塊的關(guān)注不是很多,可能在沒有出現(xiàn)內(nèi)存不足的問題前不會考慮這些,項目的要求沒有那么高,學(xué)習(xí)過這些點以后需要在開發(fā)中慢慢關(guān)注這些問題。
到此這篇關(guān)于詳解Android內(nèi)存優(yōu)化策略的文章就介紹到這了,更多相關(guān)Android內(nèi)存優(yōu)化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android組件ContextMenu實現(xiàn)長按事件
這篇文章主要為大家詳細介紹了Android組件ContextMenu實現(xiàn)長按事件,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-04-04使用Android的OkHttp包實現(xiàn)基于HTTP協(xié)議的文件上傳下載
OkHttp(GitHub主頁https://github.com/square/okhttp)是近來人氣攀升的一款安卓第三方HTTP包,這里我們來講解一下如何使用Android的OkHttp包實現(xiàn)基于HTTP協(xié)議的文件上傳下載:2016-07-07詳解Android應(yīng)用中使用TabHost組件進行布局的基本方法
這篇文章主要介紹了Android應(yīng)用中使用TabHost組件進行布局的基本方法,不繼承TabActivity并以最基本的布局文件方式進行布局,需要的朋友可以參考下2016-04-04Android?studio實現(xiàn)日期?、時間選擇器與進度條
這篇文章主要為大家詳細介紹了Android?studio實現(xiàn)日期、時間選擇器與進度條,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01Android實現(xiàn)簡單C/S聊天室應(yīng)用
這篇文章主要為大家詳細介紹了Android實現(xiàn)簡單C/S聊天室應(yīng)用,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-01-01Android開發(fā)之HTTP訪問網(wǎng)絡(luò)
這篇文章主要介紹了Android開發(fā)之HTTP訪問網(wǎng)絡(luò)的相關(guān)資料,需要的朋友可以參考下2016-07-07