android系统刷新机制

版权声明:本文为博主原创文章,转载请标明地址。 https://blog.csdn.net/u013034413/article/details/79661644

整理一下android系统的刷新机制

参考:
Android 屏幕刷新机制:
https://www.jianshu.com/p/0d00cb85fdf3
Android应用层View绘制流程与源码分析
https://blog.csdn.net/yanbober/article/details/46128379

一般刷新包括View.postInvalidate到最后都是走到View.invalidate(),然后一直向上回到ViewRootImpl根据需要进行performMeasure,performLayout,performDraw,刷新完成。

->invalidate
->invalidateInternal
->invalidateChild
->invalidateChildInParent
->ViewRootImpl.invalidateChildInParent(int[],Rect);
->ViewRootImpl.invalidateRectOnScreen(Rect);
->ViewRootImpl.scheduleTraversals();
->ViewRoot.doTraversals();  
->performTraversals();
    ->performMeasure()
    ->measure()
    ->onMeasure()
    ->performLayout()
    ->layout
    ->onLayout()
    ->performDraw()
    ->draw

再往下进一步,invalidate之后立即刷新吗?具体的请查看https://www.jianshu.com/p/0d00cb85fdf3
这里只记一下结论,以前没研究过。

android底层系统每16.6ms发出一个VSync信号来切换一帧画面,我们的app想要接受到这个信号,就要注册。那么如何注册呢?就是调用invalidate注册,具体底层如何实现,原文作者并没有说明,不过大体思路就是这样。
注册之后,只有接受到下一个刷新的信号,才开始我们的熟悉的刷新工作。而不是一调用invalidate就开始刷新。而且这期间重新invalidate没有意义,相当于只调用了invalidate一次。
另外,CPU只负责计算,GPU负责渲染,底层决定什么时候显示。也就是接收到第一个刷新信号,开始计算,渲染,然后就取消了注册,就没CPU,GPU什么事情了,CPU与GPU这一次的刷新任务已经完成。
底层每16.6ms切换一帧画面,如果有新的渲染好的画面,就显示,没有的话就还显示原来的(这里有一个疑问,就是如果画面没有发生变化,那么屏幕是直接没有切换帧画面还是切成了跟原来一样的帧画面),然后发出下一个信号(这里的发出信号与切换帧画面的先后顺序并不确定,大体逻辑是这样),app如果又需要invalidate,那么接着计算渲染,一直循环。

这样的话invalidate走到ViewRootImpl.scheduleTraversals和performTraversals之间就是这样,这里直接复制原文的内容

    scheduleTraversals() 会先过滤掉同一帧内的重复调用,在同一帧内只需要
    安排一次遍历绘制 View 树的任务即可,这个任务会在下一个屏幕刷新信号到来
    时调用 performTraversals() 遍历 View 树,
    遍历过程中会将所有需要刷新的 View 进行重绘;
    接着 scheduleTraversals() 会往主线程的消息队列中发送一个同步屏障,拦
    截这个时刻之后所有的同步消息的执行,但不会拦截异步消息,以此来尽可能的保
    证当接收到屏幕刷新信号时可以尽可能第一时间处理遍历绘制 View 树的工作;
    发完同步屏障后 scheduleTraversals() 才会开始安排一个遍历绘制 View 
    树的操作,作法是把 performTraversals() 封装到 Runnable 里面,然后
    调用 Choreographer 的 postCallback() 方法;
    postCallback() 方法会先将这个 Runnable 任务以当前时间戳放进一个待执
    行的队列里,然后如果当前是在主线程就会直接调用一个native 层方法,如果不
    是在主线程,会发一个最高优先级的 message 到主线程,让主线程第一时间调
    用这个 native 层的方法;
    native 层的这个方法是用来向底层注册监听下一个屏幕刷新信号,当下一个屏幕
    刷新信号发出时,底层就会回调 Choreographer 的onVsync() 方法来通知上层 app;
    onVsync() 方法被回调时,会往主线程的消息队列中发送一个执行 doFrame() 
    方法的消息,这个消息是异步消息,所以不会被同步屏障拦截住;
    doFrame() 方法会去取出之前放进待执行队列里的任务来执行,取出来的这个任
    务实际上是 ViewRootImpl 的 doTraversal() 操作;
    上述第4步到第8步涉及到的消息都手动设置成了异步消息,所以不会受到同步屏障的拦截

一些引发刷新的操作:

invalidate(请求重绘)
requestLayout(重新布局)
requestFocus(请求焦点)
startActivity(打开新界面)
onRestart(重新打开界面)
KeyEvent(遥控器事件,本质上是焦点导致的刷新)
Animation(各种动画,本质上是请求重绘导致的刷新)
RecyclerView滑动(页面滑动,本质上是动画导致的刷新)
setAdapter(各种adapter的更新)

另一篇博客提到的刷新

直接调用invalidate方法.请求重新draw,但只会绘制调用者本身。
触发setSelection方法。请求重新draw,但只会绘制调用者本身。
触发setVisibility方法。 当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态在INVISIBLE\VISIBLE 转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过程以及draw过程,同样只绘制需要“重新绘制”的视图。
触发setEnabled方法。请求重新draw,但不会重新绘制任何View包括该调用者本身。

上面的作者还有一篇文章:
【Andorid源码解析】View.post() 到底干了啥:
https://www.jianshu.com/p/85fc4decc947
这篇文章跟刷新机制也有点关系,一起记录下。这里只贴下结论,用作自己记录,具体请查看原文。

View.post(Runnable) 内部会自动分两种情况处理,当 View 还没 attachedToWindow 时,会先将这些 Runnable 操作缓存下来;否则就直接通过 mAttachInfo.mHandler 将这些 Runnable 操作 post 到主线程的 MessageQueue 中等待执行。

如果 View.post(Runnable) 的 Runnable 操作被缓存下来了,那么这些操作将会在 dispatchAttachedToWindow() 被回调时,通过 mAttachInfo.mHandler.post() 发送到主线程的 MessageQueue 中等待执行。

mAttachInfo 是 ViewRootImpl 的成员变量,在构造函数中初始化,Activity View 树里所有的子 View 中的 mAttachInfo 都是 ViewRootImpl.mAttachInfo 的引用。

mAttachInfo.mHandler 也是 ViewRootImpl 中的成员变量,在声明时就初始化了,所以这个 mHandler 绑定的是主线程的 Looper,所以 View.post() 的操作都会发送到主线程中执行,那么也就支持 UI 操作了。

dispatchAttachedToWindow() 被调用的时机是在 ViewRootImol 的 performTraversals() 中,该方法会进行 View 树的测量、布局、绘制三大流程的操作。

Handler 消息机制通常情况下是一个 Message 执行完后才去取下一个 Message 来执行(异步 Message 还没接触),所以 View.post(Runnable) 中的 Runnable 操作肯定会在 performMeaure() 之后才执行,所以此时可以获取到 View 的宽高。

大致意思就是如果还没有attachedToWindow,那么就先缓存,等到第一次ViewRootImpl调用performTraversals时会调用dispatchAttachedToWindow将之前的 runnable放进消息队列中去,又因为消息队列中的消息是一条一条执行的,所以必须等当前的操作执行完毕,才会执行到刚刚放到消息队列中的。dispatchAttachedToWindow之后立马就是performMeasure,performLayout,performDraw等到执行到刚刚的runnable时已经进行了测量工作,就可以取到宽高了。

猜你喜欢

转载自blog.csdn.net/u013034413/article/details/79661644