urlname
type
Post
password
SyncToConfluence
category
Android
date
Apr 7, 2023 10:48
slug
cb7a82c71ff2
icon
Button
catalog
summary
tags
Android
源码解析
UI刷新
cover
Status
BusyTime
Status 1
status
Published
带着问题学习
Android中令UI刷新的流程是怎么样的?
UI刷新的时候,调用invalidated()和requestLayout()方法,有什么区别?
invalidated()方法的真正实现是什么?
requestLayout()方法的真正实现是什么?
invalidated()和requestLayout()都能实现刷新,其区别是什么?
invalidated()和requestLayout()被调用后,UI是怎么进行刷新的?
invalidated()和requestLayout()和VSync的关系是怎么样的?
什么是UI刷新
在
Android 中,UI 刷新,指的是更新展示在用户屏幕上的界面,这其中会涉及到对各个组件的内容进行绘制和更新,具体的,就是对于每个需要被刷新的组件,分别调用其 invalidated() 或 requestLayout() 方法,当然,在我们日常使用的组件中,可能我们没有显示的调用这两个方法,比如我们要改变一个 textView 的文本,我们是直接通过 textView.text = "content" 来实现的,而这内部,其实也是调用了 invalidated() 和 requestLayout() 方法来实现的。UI 刷新的任务是怎么被执行的
我们知道,对于
Android 中刷新界面的时候,都是对整个界面进行刷新一次(即每次都从缓存里面,取出完整的一帧,并绘制到屏幕上)。而上面提到,对于多个组件需要被刷新的时候,就需要调用多次 invalidated() 和 requestLayout() 方法,难道多次调用了 invalidated() 和 requestLayout() 方法,就需要屏幕进行多次刷新吗?也就是每次屏幕刷新,只能刷新一个组件吗?这显然是不合理的,因为对于其他需要修改的组件,完全是可以合并到一次刷新里面的(只要在同一帧内),这样的做法,相比前面的调用一次 invalidated() 和 requestLayout() 方法就刷新一遍屏幕来得高效多了。那么问题又来了。对于这多个
invalidated() 和 requestLayout() 方法发出的刷新请求,应该怎么保证其顺序呢?保证了其顺序之后,因为是 UI 刷新操作,所以对实时性要求很高,因此,对于这种刷新请求,一般都是要优先被执行的,那又该如何保证其调用的优先级呢?答案就是,
Android 中的 消息队列,即利用 Handler 的机制,并把刷新 UI 的请求,包装成异步 Message(需要用到同步屏障),并依次放到消息队列中,就可以保证这些刷新请求的有序性和优先被执行了。invalidated
上面提到
invalidated() 方法,是用于促使我们的组件进行刷新的,那 invalidated() 方法的具体 实现/调用过程 是怎么样的呢?下面来跟进一下 invalidated() 的执行流程,invalidated() 方法是在 View.Java 中定义的,所以,从 View.Java 中跟进即可看到
view 最后是调用了其 parent 的 invalidateChild() 方法,下面继续跟过去看看其实现可以看到,这个方法是一个接口里面定义的方法,而且已经被标记为
Deprecated,并推荐使用 onDescendantInvalidated(View, View) 方法,但是下面还是先来看看实现了这个接口的类的 invalidateChild() 方法的实现,而通过查找其实现类,发现就 ViewGroup 实现了该接口继续跟进
invalidateChildInParent 实现,也是在 ViewParent 中定义,在 ViewGroup 中实现的方法上面看到
parent.invalidateChildInParent() 返回的 mParent 实例,下面先来看看其赋值流程,才好接着往下分析,因为到这里,我们如果不知道 mParent 的类型的话,是没法去最终其 invalidateChildInParent() 的实现的,也就是说,如果要继续往下跟踪,其实也就只有这一条线索了事实上,上面的
assignParent() 方法,其实是唯一给 mParent 赋值的方法了,而通过查找,发现这个方法,其实是在 ViewRootImpl 中被调用的因为赋值的是
ViewRootImpl 对象,所以,上面的 parent.invalidateChildInParent() 方法,其实是调用的 ViewRootImpl.invalidateChildInParent(),下面来看看其实现上面根据
dirty 是否为 null,分别调用了 invalidate() 和 invalidateRectOnScreen() 方法进行刷新, 先来看看 invalidateRectOnScreen() 方法的实现再来看看
invalidate() 的实现可以看到,不管是调用了
invalidate() 还是 invalidateRectOnScreen(),最后都是调用了 scheduleTraversals() 方法,接下来,就来讨论一下 scheduleTraversals() 到底是干什么的RequestLayout
在一开始,我们就提到,要实现组件的刷新,不仅可以通过调用
invalidated() 方法,还可以通过调用 requestLayout() 方法,有了上面对 invalidated() 执行过程的分析,接下来分析 requestLayout() 方法就会很简单了,因为基本是一样的流程,先来看看其在 View.Java 中的定义吧从上面代码,发现
View.requestLayout() 其实是调用了 ViewParent.requestLayout() 方法,而根据上面对 invalidated() 方法的分析,我们知道,这里的 mParent 其实就是被赋予了 ViewRootImpl 实例,因此,下面来看看 ViewRootImpl 中关于 requestLayout() 的实现scheduleTraversals
从上面对
invalidated() 和 requestLayout() 的分析,可以知道,其最终都会调用到 scheduleTraversals() 方法,那么 scheduleTraversals() 到底是怎么让我们的 UI 刷新的呢?下面一起来看看吧先来看看其实现
上面的代码中,先是添加了一个同步屏障,通知主线程的
Looper 只处理 MessageQueue 中的异步任务,然后给 mChoreographer 添加一个回调,且该回调会在下一个 VSync 信号到达时被执行,下面来看看添加的回调 mTraversalRunnable 是什么从上面的分析中,可以知道我们的
mChoreographer 中添加的回调 doTraversal(),最终会调用 performTraversals() 方法,并在其中进行一些判断,比如判断 mLayoutRequested 是否被赋为 true,从而决定是否执行 测量和布局 过程,这也是导致虽然 invalidated() 和 requestLayout() 都会导致 UI 刷新,但是 invalidated() 只会触发 draw() 流程,而 requestLayout() 则会触发整个 measure()、layout()、draw() 流程的原因也就是说,添加的这个回调,其实就是遍历一下整个
View 树,并将其结果绘制到界面上,到这里,就可以引出一个结论:我们虽然的更改了 UI,但是其真正被测量、布局、绘制的时机,其实是在 VSync 信号到来之后。 这也是把我们多个刷新任务,合并到一起执行的逻辑。分析完添加的回调
mTraversalRunnable 之后,我们再来看看 mChoreographer.postCallback() 方法是怎么实现的,这样可以帮助我们更好把握我们的回调,是通过什么机制被执行的mChoreographer.postCallback
mChoreographer.postCallback() 方法,是用于让回调方法,在下一帧的信号到来时候被执行的方法可以看到,对于添加进去的回调,如果执行时机已到,就会执行
scheduleFrameLocked(now) 方法,否则就将其包装成异步任务,并放到主线程的 MessageQueue 中去,下面来具体看看 scheduleFrameLocked(now) 内部是怎么实现的根据上面的分析,可以知道,在
postCallbackDelayedInternal() 方法中,如果执行 action 回调的时机还没到,就将其设置为异步 Message,同时,会将其 Message.what 设置为 MSG_DO_SCHEDULE_CALLBACK,并放到 messageQueue 中,等待被执行;在
scheduleFrameLocked() 方法中,如果使用 VSYNC 且当前不是运行在 Looper 线程上,就将其包装成一个异步 Message,同时,会将其 Message.what 设置为 MSG_DO_SCHEDULE_VSYNC,由 mHandler 来尽快处理该 ASync;如果不使用 VSYNC,就包装成异步 Message,同时,会将其 Message.what 设置为 MSG_DO_FRAME,并由 mHandler 来尽快处理该 ASync需要注意的点是,这里反复提到的
mHandler,其实是一个 FrameHandler,该 Handler 定义如下:doFrame
从前面的分析中,我们已经知道,在
scheduleFrameLocked() 方法中,如果不使用 VSYNC,就包装成异步 Message,同时,会将其 Message.what 设置为 MSG_DO_FRAME,并由 mHandler 来尽快处理该 ASync,而根据放进去的 Message 的 what,最终就会调用到 doFrame(),下面来看看其实现下面来看看
doCallback() 实现doScheduleVsync
从前面的分析中,我们已经知道,在
scheduleFrameLocked() 方法中,如果使用 VSYNC 且当前不是运行在 Looper 线程上,就将其包装成一个异步 Message,同时,会将其 Message.what 设置为 MSG_DO_SCHEDULE_VSYNC,由 mHandler 来尽快处理该 ASync,而根据放进去的 Message 的 what,最终就会调用到 doScheduleVsync() ,并在其中处理 VSYNC,下面来看看其实现doScheduleCallback
从前面的分析中,我们已经知道,在
postCallbackDelayedInternal() 方法中,如果执行 action 回调的时机还没到,就将其设置为异步 Message,同时,会将其 Message.what 设置为 MSG_DO_SCHEDULE_CALLBACK,并放到 messageQueue 中,而根据放进去的 Message 的 what,最终就会调用到 doScheduleCallback() ,并在其中执行 action 回调,下面来看看其实现推荐/参考
- Author:CoderWdd
- URL:https://www.wuinsights.top//article/cb7a82c71ff2
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts
