Android 丢帧原理以及办法

接近年底,想分享点儿东西给大家。

Android UI绘制过程

开发中的卡顿我想没跟人都遇到过,之前也是搜博客看看怎么个解决办法,没有认真研究过,今天我打算跟大家聊一聊。

先从View 说吧。相信大家应该都知道View的绘制过程,measure,layout,draw。丢帧一定是在16ms内没有把这些事儿干完就对了,这里我们简单的分一下,主要是计算时间,以及绘图时间。

计算时间:这里的measure,layout的过程,都是会向下递归计算的,学过数据结构的话,应该知道,深搜的代价是很大的。所以尽量让树的高度降低,这里就引出扁平化布局。

绘图时间:这里需要着重讲一下,因为有时候这才是我们UI卡顿的主要原因。在这里我们要把android的试图看成是三维的,就像photoshop的图层一样。android在绘制的时候就会一层一层的“粉刷”,好了,那么造成卡顿,也就是丢帧,说白了最后没有在16ms内做完。好了,让我们剖析一下:

1.invalidate():

我们知道invalidate 是用来请求View 重绘的,

Android 丢帧原理以及办法_第1张图片
invalidateInternal

这里可以看出来draw的过程其实就是拿到AttachInfo 里面包含着绘制信息,以及将绘制区域拿到,通过parent去绘制。让我们跟进去。

Android 丢帧原理以及办法_第2张图片
invalidateChildInParent

这里的dirty代表你绘制的这块区域是否透明。

Android 丢帧原理以及办法_第3张图片
invalidate

这里我们看到了个关键函数 scheduleTraversals ,为什么说神奇。我们看一下


Android 丢帧原理以及办法_第4张图片
scheduleTraversals

这里最重要的是Choreographer 这个,我们最终算出来的绘制信息都要通过它回调,开始他会注册一个广播用来接收时钟信息,然后他会在内部建立一个UI绘制队列:CallbackQueue,我们在外部CallBack的时候,会将我们的绘制信息作为CallbackRecord 然后会在接收到一个时钟信号的时候进行doFrame操作,并打印Traces信息,从而来绘制一帧。


Android 丢帧原理以及办法_第5张图片
CallbackQueue and CallbackRecord
Android 丢帧原理以及办法_第6张图片
postCallbackDelayedInternal

可以看到这里我们把我们的绘制内容扔到队列里,等待轮训。

Android 丢帧原理以及办法_第7张图片
FrameDisplayEventReceiver

接收时钟脉冲信号的广播,16ms一次,我们的目的就是在这个时钟脉冲里搞定整个 view

2.Android 动画

Animator,ScrollTo,offsetLeftAndRight,这里面我们先单列这几项,都是同一个原理。这里我们可以大胆的猜想,一定是频繁执行我们的 Choreographer.CallBack 来绘制,因为只要在16ms内绘制成功,那就是流畅的动画。下面我们验证一下

ScrollTo:

我们先看一下 View 中这个方法

Android 丢帧原理以及办法_第8张图片
scrollTo

很简单,我们都可以看懂,开始位置,结束位置,这里我们重点关注 postInvalidateOnAnimation()  这个方法


Android 丢帧原理以及办法_第9张图片
postInvalidateOnAnimation

我们可以看到,这里的动画过程绘制他还是扔到了ViewRootImpl 代理做这件事。

dispatchInvalidateOnAnimation

这里我们看到他开了个线程 mInvalidateOnAnimationRunnable 去添加我们这个将要绘制的 view,接下来我们继续庖丁解牛


Android 丢帧原理以及办法_第10张图片
Android 丢帧原理以及办法_第11张图片
InvalidateOnAnimationRunnable

终于,应了我们的猜想,ViewRootImpl 有一个专门执行动画绘制操作的线程,我们可以看到 run() 里面不断地CallBack,然后回收,当然里面有些线程锁啥的不涉及本文就不细说了。

3.ValueAnimator:

这里我们有个 AnimationHandler 来执行动画操作,这其中我们可以看到

Android 丢帧原理以及办法_第12张图片
doAnimationFrame

这里在不断循环我们所有的anim,并在不断执行 scheduleAnimation 方法

scheduleAnimation

剩下的大家自己翻阅源码把。

这里总结一下。我们所有界面上视图的变化都是都是 ViewRootImpl 把需要重绘的东西填充 Choreographer 中的 mCallbackQueues 队列,然后在时钟脉冲的广播下进行轮训执行。

既然提到队列,假如我们在16ms内大量的填充 AttachInfo 之类的绘制OBJ,就会导致无法再一次时钟脉冲内绘制完毕,就会在造成丢帧,UI阻塞。

避免 Android UI 卡顿解决办法

解决办法:分析了好多,这里说两个方法。

1.避免重绘,这里避免图层(View)迭代。这里我们可以去开发者模式中对“显示GPU视图更新”打钩


Android 丢帧原理以及办法_第13张图片
过度绘制


Android 丢帧原理以及办法_第14张图片
优化以后

这里引用 http://hukai.me/android-performance-render 这篇博客的作者,盗个图。

这里可以进行,选择制定画布绘制,而不是整个view去绘制。可以在onDraw中进行限制,去限制绘制区域,例如

canvas.clipRect(100,100,350,600, Region.Op.INTERSECT);

2.扁平化布局,归根结底也是减少 mCallbackQueues 队列大小。保证尽量在16ms内绘制完毕,再有就是可以减少视图 ViewTree 的高度,减少时间复杂度,从而优化计算过程


Android 丢帧原理以及办法_第15张图片
xml代码


Android 丢帧原理以及办法_第16张图片
优化后的xml代码

*附:


Android 丢帧原理以及办法_第17张图片
绘制层级

通过打开刚才说的开发者选项,来根据颜色来判断页面绘制情况。

距离回家还有8 个小时,17年希望可以发觉更多的东西给大家,并且希望大家可以积极执政文章中的错误。祝大家新年快乐!

你可能感兴趣的:(Android 丢帧原理以及办法)