自定義View星空動(dòng)畫的內(nèi)存占用/GPU渲染性能優(yōu)化手記

這是一個(gè)關(guān)于星空的自定義動(dòng)畫Sample,源碼請(qǐng)戳 https://github.com/wangfuda/nebula

Gif圖:

nebula.gif

本文將重點(diǎn)講解在本例自定義動(dòng)畫編程中,如何結(jié)合 Android Studio 的 Memory Monitor,GPU monitor 按步驟做內(nèi)存優(yōu)化,GPU渲染優(yōu)化。
關(guān)于動(dòng)畫實(shí)現(xiàn)部分,源碼已提交至github,請(qǐng)手動(dòng)閱讀理解,注釋很詳盡。

3.png

內(nèi)存占用優(yōu)化

內(nèi)存占用優(yōu)化 步驟一:移動(dòng)圖片資源至大分辨率目錄下,比如xxxhdpi.

先來(lái)彪一張直接擼完代碼無(wú)任何優(yōu)化的情況下,內(nèi)存的占用圖:

monitor_1_img_hdpi.png

內(nèi)存占用246M,不能忍。
問:為什么這么大?
答:因?yàn)橘Y源圖都是高清1K分辨率的圖。
問:為什么這么大?
答:。。。
那么我們算一下這246M內(nèi)存占用是怎么來(lái)的吧。
先來(lái)一條圖片內(nèi)存占用計(jì)算公式,公式溯源請(qǐng)自行去看源碼:BitmapFactory.Java & BitmapFactory.cpp
scaledWidth = int( Width * targetDensity / density + 0.5)
scaledHeight = int( Height * targetDensity / density + 0.5)
memory = scaledWidth * scaledHeight * 4
其中參數(shù)定義如下:

Width:圖片寬
Height:圖片高
targetDensity:加載圖片的目標(biāo)手機(jī)的 density,這個(gè)值的來(lái)源是 DisplayMetrics 的 densityDpi,如果是小米note那么這個(gè)數(shù)值就是480,詳見下圖關(guān)于targetDensity的參數(shù)細(xì)節(jié)。
density:decodingBitmap 的 density,這個(gè)值跟這張圖片的放置的目錄有關(guān)(比如 hdpi 是240,xxhdpi 是480)
每像素字節(jié)數(shù):ARGB8888格式的圖片,每像素占用 4 Byte,而 RGB565則是 2 Byte。

targetDensity.png

對(duì)于一張1080x1920的圖來(lái)說(shuō),放置在hdpi目錄,并在小米note手機(jī)上(分辨率1080x1920,targetDensity為480),而且均默認(rèn)以ARGB8888格式加載。
內(nèi)存占用計(jì)算公式:
scaledWidth = int( 1080* 480/ 240+ 0.5) =2160
scaledHeight = int( 1920* 480/ 240+ 0.5)=3840
memory = scaledWidth * scaledHeight * 4=216038404 = 33177600 = 33.17M

一張背景圖就占用33M,這個(gè)分辨率的圖,我們r(jià)es下一共有7張,還有其他幾張小圖。這回可以回答為什么占用245M的內(nèi)存了。

那么內(nèi)存占用的優(yōu)化方案也就有了,我們盡量把圖片資源放到大分辨率目錄下,比如xxxhdpi(當(dāng)然還有個(gè)前提,你的圖片分辨率也確實(shí)符合大分辨率,否則會(huì)出現(xiàn)在大分辨率設(shè)備上,顯示不全的問題)。

我們來(lái)看看把圖片資源移動(dòng)到xxxhdpi目錄下后,內(nèi)存占用情況:

monitor_2_img_move_to_xxxhdpi.png

把圖片資源從hdpi移動(dòng)到xxx-hdpi,從246M降低到56M,減少了190M,Bingo!

內(nèi)存占用優(yōu)化 步驟 二:壓縮png圖片大小(包體大小會(huì)減小,但與內(nèi)存占用情況無(wú)關(guān))

初始單張圖片大小都接近2M,經(jīng)過(guò)tinypng優(yōu)化后,壓縮率達(dá)到70-80%,非常完美,包體大小減小了,不過(guò),經(jīng)過(guò)我們步驟一的科學(xué)計(jì)算,這個(gè)優(yōu)化并不會(huì)影響圖片在內(nèi)存中的占用。

內(nèi)存占用優(yōu)化 步驟 三:動(dòng)畫完成且不再循環(huán)展示的部分,相關(guān)bitmap釋放
public void releaseBitmap {
  ...
  bitmap.recycle();
  ...
}

來(lái)看下bitmap釋放后的memory monitor圖:

monitor_3_release_bitmap.png

內(nèi)存占用降低到36M,減少了20M

內(nèi)存占用優(yōu)化 步驟 四:無(wú)用對(duì)象釋放,非透明背景圖片采用RGB_565顏色格式,并且將圖片的inSampleSize設(shè)置為2
public void releaseValueAnimator {
  ...
  valueAnimator = null;
  ...
}

public void initBitmap {
  ...
  localOptions.inSampleSize = 2;
  localOptions.inPreferredConfig = Bitmap.Config.RGB_565;
  ...
}

來(lái)看下變更圖片顏色格式及采樣率后的memory monitor圖:

monitor_5_bitmap_RGB_565_inSampleSize2.png

內(nèi)存占用降低到28M,減少了8M(后來(lái)monitor截圖只對(duì)背景圖片做inPreferredConfig調(diào)整,內(nèi)存占用變?yōu)?0M,相比全部設(shè)置為2,增加了2M)

GPU渲染優(yōu)化

接下來(lái)我們要專注于GPU渲染優(yōu)化了。通過(guò)前面幾張圖,也能看到GPU monitor的狀態(tài),非常不樂觀,完全達(dá)不到幀率刷新的要求:即每幀渲染不超過(guò)16ms,每秒可以渲染60幀。
先看GPU Monitor的各項(xiàng)指標(biāo)含義:


gpu_monitor.png

Misc Time:表示在主線程執(zhí)行了太多的任務(wù),導(dǎo)致UI渲染跟不上vSync的信號(hào)而出現(xiàn)掉幀的情況;出現(xiàn)該線條的時(shí)候,可以在Log中看到這樣的日志: Skipped xxx frames! The application may be doing too much work on its main thread

Swap Buffers:表示處理任務(wù)的時(shí)間,也可以說(shuō)是CPU等待GPU完成任務(wù)的時(shí)間,線條越高,表示GPU做的事情越多;

Command Issue:表示執(zhí)行任務(wù)的時(shí)間,這部分主要是Android進(jìn)行2D渲染顯示列表的時(shí)間,為了將內(nèi)容繪制到屏幕上,Android需要使用Open GL ES的API接口來(lái)繪制顯示列表,紅色線條越高表示需要繪制的視圖更多;

Sync:表示的是準(zhǔn)備當(dāng)前界面上有待繪制的圖片所耗費(fèi)的時(shí)間,為了減少該段區(qū)域的執(zhí)行時(shí)間,我們可以減少屏幕上的圖片數(shù)量或者是縮小圖片的大小;

Draw:表示測(cè)量和繪制視圖列表所需要的時(shí)間,藍(lán)色線條越高表示每一幀需要更新很多視圖,或者View的onDraw方法中做了耗時(shí)操作;

Measure/Layout:表示布局的onMeasure與onLayout所花費(fèi)的時(shí)間,一旦時(shí)間過(guò)長(zhǎng),就需要仔細(xì)檢查自己的布局是不是存在嚴(yán)重的性能問題;

Animation:表示計(jì)算執(zhí)行動(dòng)畫所需要花費(fèi)的時(shí)間,包含的動(dòng)畫有ObjectAnimator,ViewPropertyAnimator,Transition等等。一旦這里的執(zhí)行時(shí)間過(guò)長(zhǎng),就需要檢查是不是使用了非官方的動(dòng)畫工具或者是檢查動(dòng)畫執(zhí)行的過(guò)程中是不是觸發(fā)了讀寫操作等等;

Input Handling:表示系統(tǒng)處理輸入事件所耗費(fèi)的時(shí)間,粗略等于對(duì)事件處理方法所執(zhí)行的時(shí)間。一旦執(zhí)行時(shí)間過(guò)長(zhǎng),意味著在處理用戶的輸入事件的地方執(zhí)行了復(fù)雜的操作;
Vsync Delay:見Misc Time

GPU渲染優(yōu)化 步驟一:優(yōu)化內(nèi)存占用

可以回過(guò)頭去看看內(nèi)存優(yōu)化過(guò)程演進(jìn)中的Monitor圖,隨著內(nèi)存占用的降低,GPU渲染的性能改善也是隨之漸進(jìn)的,所以GPU渲染性能優(yōu)化,首選就是內(nèi)存優(yōu)化

GPU渲染優(yōu)化 步驟二:能在初始化中做的事,堅(jiān)決不在onDraw中搞。

在sample代碼中重構(gòu)了onDraw中的畫筆的屬性設(shè)置,繪制區(qū)域的創(chuàng)建等代碼,這些代碼都重構(gòu)到初始化中。而在onDraw中僅做參數(shù)值的動(dòng)態(tài)調(diào)整。

Tips.本例Sample最初始未經(jīng)過(guò)任何優(yōu)化的代碼及最終優(yōu)化版本代碼均在github上可以查看到commit記錄,這里不再寫詳細(xì)代碼對(duì)比,請(qǐng)移動(dòng)github閱讀源碼。

我們看下重構(gòu)前后的對(duì)比圖monitor_5_bitmap_RGB_565_inSampleSize2.png Vs monitor_6_gpu_optimize_object_and_paint_create.png,對(duì)比發(fā)現(xiàn),GPU渲染耗時(shí)明顯降低。

我們暫時(shí)先對(duì)比看GPU Monitor的 0s ~ 9s 部分的性能改善,目前GPU優(yōu)化主要在這里體現(xiàn),因?yàn)楹蟀氩糠指魑粻斂吹搅耍珿PU渲染耗時(shí)飆升,掉幀嚴(yán)重,那部分的優(yōu)化在后續(xù)步驟會(huì)提到。

重構(gòu)前 GPU Monitor:


monitor_5_bitmap_RGB_565_inSampleSize2.png

重構(gòu)后 GPU Monitor:

monitor_6_gpu_optimize_object_and_paint_create.png
GPU渲染優(yōu)化 步驟三:能用硬件加速,就別關(guān)閉它。

我們都看到了,在GPU Monitor中顯示,9s后的GPU渲染,每幀耗時(shí)突然飆升,每幀渲染都是超60ms,
Draw上升至172ms,Vsync上升至148ms.
而且在logcat中也看到日志:
07-09 11:17:57.135 15980-15980/com.osan.nebula I/Choreographer: Skipped 31 frames! The application may be doing too much work on its main thread.
每幀超時(shí),掉幀嚴(yán)重。到底是什么原因呢?經(jīng)過(guò)反復(fù)的排查,終于找到原因,我們來(lái)聊聊這個(gè)飆升的來(lái)龍去脈。
由于在后半部分的動(dòng)畫中,繪制小星星的光暈效果時(shí),使用的畫筆設(shè)置了模糊屬性,為了給小星星加個(gè)光暈效果。

paintCircleStar.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.SOLID));

然后google官方文檔都說(shuō)了,硬件加速不支持的UI特效API之一就有它。也就是你要用硬件加速,這個(gè)模糊效果就失效。所以我一門心思的為了給小星星加光暈... 光暈...暈...,最后我選擇了關(guān)閉硬件加速

private void drawState7(Canvas canvas) {
    ...
    canvas.save();
    setLayerType(LAYER_TYPE_SOFTWARE, null);//關(guān)閉硬件加速
    canvas.translate(halfWidth, halfHeight);
    canvas.scale(scale, scale);
    canvas.rotate(30f * mValue7);
    ...
}

呵呵,為了小星星,闖禍了。因?yàn)楸纠齽?dòng)畫中,各種對(duì)畫布的旋轉(zhuǎn),縮放,變換,透明度動(dòng)態(tài)變化,在非硬件加速情況下,不停的重新繪制,是GPU渲染耗時(shí)飆升的唯一原因。
我們來(lái)看看開啟硬件加速的情況下,GPU Monitor的指標(biāo)監(jiān)控:

monitor_7_gpu_optimize_with_hardware_acce.png

恢復(fù)硬件加速后,渲染耗時(shí)立刻恢復(fù)到綠線以下,即每幀渲染不超過(guò)16ms,達(dá)到渲染標(biāo)準(zhǔn)。而且,CPU消耗也明顯下降。
那么為什么硬件加速有如此神奇之功效?
使用硬件加速在對(duì)一些view的屬性改變上有更高的效率,因?yàn)椴恍枰獀iew的invalidate和redrawn。而我們動(dòng)畫中正式大量使用了對(duì)屬性的改變。屬性如:

透明度:alpha
移動(dòng):x, y, translationX, translationY
縮放:scaleX, scaleY
旋轉(zhuǎn):rotation, rotationX, rotationY
坐標(biāo):pivotX, pivotY

注:
1)使用硬件加速,對(duì)于渲染性能的提示是顯著的,API>14后,硬件加速是默認(rèn)開啟的。
2)硬件加速還不支持所有的2D繪圖命令,開啟后可能會(huì)影響自定義View和繪圖操作。

GPU渲染優(yōu)化 步驟四:優(yōu)化算數(shù)運(yùn)算,并盡量從ondraw中移除算數(shù)運(yùn)算

涉及計(jì)算任務(wù),能不在ondraw中執(zhí)行的,就堅(jiān)決移走,即使只是一個(gè)a*b或a/b.
因?yàn)槲覀兝^續(xù)重構(gòu)了onDraw方法,將可能優(yōu)化的運(yùn)算代碼均做了優(yōu)化,能放到初始化做的就移到初始化,能提煉共用的運(yùn)算公式就共用,能不重復(fù)算的就絕對(duì)不算第二遍。
我們來(lái)看看優(yōu)化算數(shù)運(yùn)算后,GPU Monitor的指標(biāo)監(jiān)控:

monitor_8_gpu_optimize_precalc.png

相比步驟三中的GPU Monitor指標(biāo)有了進(jìn)一步降低,雖然降低幅度很小,但是還是對(duì)GPU渲染性能提升有效果的,而且觀察發(fā)現(xiàn),優(yōu)化后,CPU和GPU的指標(biāo)看起來(lái)更平穩(wěn)。

最后附上一張coding過(guò)程中的草圖以及nebula自定義動(dòng)畫的截圖

craft.jpg
1.png

2.png
3.png
4.png
5.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,143評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,416評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,940評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,170評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,709評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,597評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評(píng)論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,029評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,403評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,746評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容