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