自定义View星空动画的内存占用/GPU渲染性能优化手记

这是一个关于星空的自定义动画Sample,源码请戳 https://github.com/wangfuda/nebula

Gif图:

nebula.gif

本文将重点讲解在本例自定义动画编程中,如何结合 Android Studio 的 Memory Monitor,GPU monitor 按步骤做内存优化,GPU渲染优化。
关于动画实现部分,源码已提交至github,请手动阅读理解,注释很详尽。

3.png

内存占用优化

内存占用优化 步骤一:移动图片资源至大分辨率目录下,比如xxxhdpi.

先来彪一张直接撸完代码无任何优化的情况下,内存的占用图:

monitor_1_img_hdpi.png

内存占用246M,不能忍。
问:为什么这么大?
答:因为资源图都是高清1K分辨率的图。
问:为什么这么大?
答:。。。
那么我们算一下这246M内存占用是怎么来的吧。
先来一条图片内存占用计算公式,公式溯源请自行去看源码:BitmapFactory.Java & BitmapFactory.cpp
scaledWidth = int( Width * targetDensity / density + 0.5)
scaledHeight = int( Height * targetDensity / density + 0.5)
memory = scaledWidth * scaledHeight * 4
其中参数定义如下:

Width:图片宽
Height:图片高
targetDensity:加载图片的目标手机的 density,这个值的来源是 DisplayMetrics 的 densityDpi,如果是小米note那么这个数值就是480,详见下图关于targetDensity的参数细节。
density:decodingBitmap 的 density,这个值跟这张图片的放置的目录有关(比如 hdpi 是240,xxhdpi 是480)
每像素字节数:ARGB8888格式的图片,每像素占用 4 Byte,而 RGB565则是 2 Byte。

targetDensity.png

对于一张1080x1920的图来说,放置在hdpi目录,并在小米note手机上(分辨率1080x1920,targetDensity为480),而且均默认以ARGB8888格式加载。
内存占用计算公式:
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,这个分辨率的图,我们res下一共有7张,还有其他几张小图。这回可以回答为什么占用245M的内存了。

那么内存占用的优化方案也就有了,我们尽量把图片资源放到大分辨率目录下,比如xxxhdpi(当然还有个前提,你的图片分辨率也确实符合大分辨率,否则会出现在大分辨率设备上,显示不全的问题)。

我们来看看把图片资源移动到xxxhdpi目录下后,内存占用情况:

monitor_2_img_move_to_xxxhdpi.png

把图片资源从hdpi移动到xxx-hdpi,从246M降低到56M,减少了190M,Bingo!

内存占用优化 步骤 二:压缩png图片大小(包体大小会减小,但与内存占用情况无关)

初始单张图片大小都接近2M,经过tinypng优化后,压缩率达到70-80%,非常完美,包体大小减小了,不过,经过我们步骤一的科学计算,这个优化并不会影响图片在内存中的占用。

内存占用优化 步骤 三:动画完成且不再循环展示的部分,相关bitmap释放
public void releaseBitmap {
  ...
  bitmap.recycle();
  ...
}

来看下bitmap释放后的memory monitor图:

monitor_3_release_bitmap.png

内存占用降低到36M,减少了20M

内存占用优化 步骤 四:无用对象释放,非透明背景图片采用RGB_565颜色格式,并且将图片的inSampleSize设置为2
public void releaseValueAnimator {
  ...
  valueAnimator = null;
  ...
}

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

来看下变更图片颜色格式及采样率后的memory monitor图:

monitor_5_bitmap_RGB_565_inSampleSize2.png

内存占用降低到28M,减少了8M(后来monitor截图只对背景图片做inPreferredConfig调整,内存占用变为30M,相比全部设置为2,增加了2M)

GPU渲染优化

接下来我们要专注于GPU渲染优化了。通过前面几张图,也能看到GPU monitor的状态,非常不乐观,完全达不到帧率刷新的要求:即每帧渲染不超过16ms,每秒可以渲染60帧。
先看GPU Monitor的各项指标含义:


gpu_monitor.png

Misc Time:表示在主线程执行了太多的任务,导致UI渲染跟不上vSync的信号而出现掉帧的情况;出现该线条的时候,可以在Log中看到这样的日志: Skipped xxx frames! The application may be doing too much work on its main thread

Swap Buffers:表示处理任务的时间,也可以说是CPU等待GPU完成任务的时间,线条越高,表示GPU做的事情越多;

Command Issue:表示执行任务的时间,这部分主要是Android进行2D渲染显示列表的时间,为了将内容绘制到屏幕上,Android需要使用Open GL ES的API接口来绘制显示列表,红色线条越高表示需要绘制的视图更多;

Sync:表示的是准备当前界面上有待绘制的图片所耗费的时间,为了减少该段区域的执行时间,我们可以减少屏幕上的图片数量或者是缩小图片的大小;

Draw:表示测量和绘制视图列表所需要的时间,蓝色线条越高表示每一帧需要更新很多视图,或者View的onDraw方法中做了耗时操作;

Measure/Layout:表示布局的onMeasure与onLayout所花费的时间,一旦时间过长,就需要仔细检查自己的布局是不是存在严重的性能问题;

Animation:表示计算执行动画所需要花费的时间,包含的动画有ObjectAnimator,ViewPropertyAnimator,Transition等等。一旦这里的执行时间过长,就需要检查是不是使用了非官方的动画工具或者是检查动画执行的过程中是不是触发了读写操作等等;

Input Handling:表示系统处理输入事件所耗费的时间,粗略等于对事件处理方法所执行的时间。一旦执行时间过长,意味着在处理用户的输入事件的地方执行了复杂的操作;
Vsync Delay:见Misc Time

GPU渲染优化 步骤一:优化内存占用

可以回过头去看看内存优化过程演进中的Monitor图,随着内存占用的降低,GPU渲染的性能改善也是随之渐进的,所以GPU渲染性能优化,首选就是内存优化

GPU渲染优化 步骤二:能在初始化中做的事,坚决不在onDraw中搞。

在sample代码中重构了onDraw中的画笔的属性设置,绘制区域的创建等代码,这些代码都重构到初始化中。而在onDraw中仅做参数值的动态调整。

Tips.本例Sample最初始未经过任何优化的代码及最终优化版本代码均在github上可以查看到commit记录,这里不再写详细代码对比,请移动github阅读源码。

我们看下重构前后的对比图monitor_5_bitmap_RGB_565_inSampleSize2.png Vs monitor_6_gpu_optimize_object_and_paint_create.png,对比发现,GPU渲染耗时明显降低。

我们暂时先对比看GPU Monitor的 0s ~ 9s 部分的性能改善,目前GPU优化主要在这里体现,因为后半部分各位爷看到了,GPU渲染耗时飙升,掉帧严重,那部分的优化在后续步骤会提到。

重构前 GPU Monitor:


monitor_5_bitmap_RGB_565_inSampleSize2.png

重构后 GPU Monitor:

monitor_6_gpu_optimize_object_and_paint_create.png
GPU渲染优化 步骤三:能用硬件加速,就别关闭它。

我们都看到了,在GPU Monitor中显示,9s后的GPU渲染,每帧耗时突然飙升,每帧渲染都是超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.
每帧超时,掉帧严重。到底是什么原因呢?经过反复的排查,终于找到原因,我们来聊聊这个飙升的来龙去脉。
由于在后半部分的动画中,绘制小星星的光晕效果时,使用的画笔设置了模糊属性,为了给小星星加个光晕效果。

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

然后google官方文档都说了,硬件加速不支持的UI特效API之一就有它。也就是你要用硬件加速,这个模糊效果就失效。所以我一门心思的为了给小星星加光晕... 光晕...晕...,最后我选择了关闭硬件加速

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

呵呵,为了小星星,闯祸了。因为本例动画中,各种对画布的旋转,缩放,变换,透明度动态变化,在非硬件加速情况下,不停的重新绘制,是GPU渲染耗时飙升的唯一原因。
我们来看看开启硬件加速的情况下,GPU Monitor的指标监控:

monitor_7_gpu_optimize_with_hardware_acce.png

恢复硬件加速后,渲染耗时立刻恢复到绿线以下,即每帧渲染不超过16ms,达到渲染标准。而且,CPU消耗也明显下降。
那么为什么硬件加速有如此神奇之功效?
使用硬件加速在对一些view的属性改变上有更高的效率,因为不需要view的invalidate和redrawn。而我们动画中正式大量使用了对属性的改变。属性如:

透明度:alpha
移动:x, y, translationX, translationY
缩放:scaleX, scaleY
旋转:rotation, rotationX, rotationY
坐标:pivotX, pivotY

注:
1)使用硬件加速,对于渲染性能的提示是显著的,API>14后,硬件加速是默认开启的。
2)硬件加速还不支持所有的2D绘图命令,开启后可能会影响自定义View和绘图操作。

GPU渲染优化 步骤四:优化算数运算,并尽量从ondraw中移除算数运算

涉及计算任务,能不在ondraw中执行的,就坚决移走,即使只是一个a*b或a/b.
因为我们继续重构了onDraw方法,将可能优化的运算代码均做了优化,能放到初始化做的就移到初始化,能提炼共用的运算公式就共用,能不重复算的就绝对不算第二遍。
我们来看看优化算数运算后,GPU Monitor的指标监控:

monitor_8_gpu_optimize_precalc.png

相比步骤三中的GPU Monitor指标有了进一步降低,虽然降低幅度很小,但是还是对GPU渲染性能提升有效果的,而且观察发现,优化后,CPU和GPU的指标看起来更平稳。

最后附上一张coding过程中的草图以及nebula自定义动画的截图

craft.jpg
1.png

2.png
3.png
4.png
5.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容