作为一个 Flutter 开发者,我们仅需组合 widget 即可实现各种不同的交互。这其中 Flutter 是如何通过 widget 完成屏幕上的呈现?Native 层起到了怎样的作用?作为 UI 核心渲染流程的完结篇,这次我们不陷于每一个细节,而是从全局梳理主要流程,把握关键节点。对于细节的知识点,我会附上一些更深入的文章。希望能对你认识 Flutter 渲染机制有所帮助。
一、缘起:setState()
无论是一个简单的列表,还是一段灵巧的动画,本质都是由一个个快速切换的画面组成,术语称其为「帧」。 当我们在滑动列表,或者播放动画的时候。系统不断地生产帧,并将其绘制到屏幕上,呈现出「动」起来了的感觉。
而在 Flutter 中当需要更新 UI 展示的时候,我们第一时间往往想到 setState()
。更新 UI 本质上,不就是用一个新的「帧」去替换上一个「帧」么。所以,其中必定会执行帧的调度逻辑。而 setState
最终调用到 BuildOwner.scheduleBuildFor
。
/// dart
/// Adds an element to the dirty elements list so that it will be rebuilt
/// when [WidgetsBinding.drawFrame] calls [buildScope].
void scheduleBuildFor(Element element) {
…
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled();
}
_dirtyElements.add(element);
element._inDirtyList = true;
…
}
方法中有两个关键点:
1、onBuildScheduled()
2、将 element 添加到
_dirtyElements
中
第二点没什么好说,后面会用到,关键先看第一点。跟踪引用,会发现第一个方法最终会执行到 SchedulerBinding.scheduleFrame(),这便是绘制的源头。
二、渲染起源:SchedulerBinding.scheduleFrame()
/// dart
/// 调用 C++ 到 Native 层,请求 Vsync 信号
void scheduleFrame() {
if (_hasScheduledFrame || !_framesEnabled)
return;
ensureFrameCallbacksRegistered();
window.scheduleFrame();
_hasScheduledFrame = true;
}
这个方法代码量并不多,关键在 window.scheduleFrame()
,这是一个 native 方法。
void scheduleFrame() native ‘Window_scheduleFrame’;
以安卓为例,最终会执行到 JNI_OnLoad 注册的 Java 接口 AsyncWaitForVsyncDelegate.asyncWaitForVsync
,这个接口在 Flutter 启动时初始化。实现内容如下
new FlutterJNI.AsyncWaitForVsyncDelegate() {
@Override
public void asyncWaitForVsync(long cookie) {
Choreographer.getInstance()
.postFrameCallback(
new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
float fps = windowManager.getDefaultDisplay().getRefreshRate();
long refreshPeriodNanos = (long) (1000000000.0 / fps);
FlutterJNI.nativeOnVsync(
frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);
}
});
}
}
初始化可以看:深入理解Flutter引擎启动
Choreographer.getInstance().postFrameCallback
用于监听系统垂直同步信号,在下一个垂直信号来临时回调 doFrame
,通过 FlutterJNI.nativeOnVsync
走到 c++ 中。经过复杂的链路,将下面的任务添加到到 UI Task Runner 中的事件队列中:
lib/ui/window/platform_configuration.cc
void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime) {
…
// 调用 dart 中的 _window.onBeginFrame
tonic::LogIfError(
tonic::DartInvoke(begin_frame_.Get(), {Dart_NewInteger(microseconds),}));
// 执行 microTask
UIDartState::Current()->FlushMicrotasksNow();
// 调用 dart 中的 _window.onDrawFrame
tonic::LogIfError(tonic::DartInvokeVoid(draw_frame_.Get()));
}
回调的初始化流程可看:Flutter渲染机制—UI线程(文章基于 sdk 版本较早,部分函数位置有所修改)
这个方法有三个主要流程:
1、执行 window.onBeginFrame
,这个方法映射到 dart 中的 handleBeginFrame()
2、接着执行 MicroTask 队列(所以 MicroTask 不仅只在某个 Event 执行后被调度)
3、最后执行 window.onDrawFrame
,对应 dart 中的 drawFrame()
2 很好理解,就是执行 MicroTask 下面我们主要分析 1 和 3。
总结:整个流程由 Dart 发起,通过 C++ 回调到 Native 层,注册一次垂直同步信号的监听。等到信号来到,再通知 Dart 进行渲染。可以看出,Flutter 上的渲染,是先由 Dart 侧主动发起,而不是被动等待垂直信号的通知。这可以解释,比如一些静态页面时,整个屏幕不会多次渲染。并且由于是 Native 层的垂直同步信号,所以也完全适配高刷的设备。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
文末
面试:如果不准备充分的面试,完全是浪费时间,更是对自己的不负责!
不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
lCoPEF.jpg" />
文末
面试:如果不准备充分的面试,完全是浪费时间,更是对自己的不负责!
不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊