经过Flutter之引擎启动流程一文,了解了Flutter
在Android
平台中是如何创建引擎并执行入口函数main
。那么接下来就来看Flutter
中每一帧的绘制。
1、Flutter的UI绘制
在main
函数中,我们必须调用runApp
方法。该方法很简单,主要用于一些WidgetsFlutterBinding
对象的初始化及第一帧的绘制。
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()//WidgetsFlutterBinding对象的初始化
..scheduleAttachRootWidget(app)//Widget树的构建
..scheduleWarmUpFrame();//第一帧的绘制
}
这里重点来看scheduleWarmUpFrame
的实现。
void scheduleWarmUpFrame() {
if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
return;
_warmUpFrame = true;
Timeline.startSync('Warm-up frame');
final bool hadScheduledFrame = _hasScheduledFrame;
Timer.run(() {
handleBeginFrame(null);
});
Timer.run(() {
handleDrawFrame();
resetEpoch();
_warmUpFrame = false;
if (hadScheduledFrame)
scheduleFrame();
});
lockEvents(() async {
//开始第一帧的绘制
await endOfFrame;
Timeline.finishSync();
});
}
在上面代码中,虽然Timer
中的回调方法在前,但endOfFrame
方法却是先开始执行,也就是会在endOfFrame
方法中来执行window
中的scheduleFrame
方法。
Future<void> get endOfFrame {
if (_nextFrameCompleter == null) {
//如果当前处于空闲状态
if (schedulerPhase == SchedulerPhase.idle)
scheduleFrame();
...
}
return _nextFrameCompleter.future;
}
void scheduleFrame() {
if (_hasScheduledFrame || !_framesEnabled)
return;
//注册window的onBeginFrame方法与onDrawFrame方法
ensureFrameCallbacksRegistered();
//请求一帧的绘制
window.scheduleFrame();
_hasScheduledFrame = true;
}
//
void scheduleFrame() native 'Window_scheduleFrame';
1.1、Flutter引擎的处理
在window
中,scheduleFrame
对应着一个native函数。该native函数对应着引擎中window
的ScheduleFrame
函数。代码实现如下。
[->flutter/lib/ui/window.cc]
void ScheduleFrame(Dart_NativeArguments args) {
UIDartState::Current()->window()->client()->ScheduleFrame();
}
在代码中,window()
函数返回的是一个Window
对象。在创建Window
对象会传递一个WindowClient
对象,所以这里的client()
函数返回的是一个WindowClient
对象。而RuntimeController
继承自WindowClient
,所以上面代码调用的是RuntimeController
对象的ScheduleFrame
函数,代码如下。
[->flutter/lib/runtime/runtime_controller.cc]
void RuntimeController::ScheduleFrame() {
//client_对应着engine对象
client_.ScheduleFrame();
}
上面代码中调用的是engine
对象的ScheduleFrame
函数。由于给该函数的regenerate_layer_tree
参数设置了缺省值,所以在调用该函数时可以不用传递参数。代码如下。
[->flutter/lib/shell/common/engine.cc]
//regenerate_layer_tree缺省值为true
void Engine::ScheduleFrame(bool regenerate_layer_tree) {//
animator_->RequestFrame(regenerate_layer_tree);
}
animator
是跟随engine
对象一起在Flutter
的UI线程中创建的。在其RequestFrame
函数中主要是调用animator
对象的AwaitVSync
函数。代码如下。
[->flutter/lib/shell/common/animator.cc]
void Animator::RequestFrame(bool regenerate_layer_tree) {
if (regenerate_layer_tree) {//默认为true
regenerate_layer_tree_ = true;
}
//如果当前生命周期处于暂停状态且尺寸不需要改变,那么就不继续执行
if (paused_ && !dimension_change_pending_) {
return;
}
if (!pending_frame_semaphore_.TryWait()) {
//防止VsyncWaiter处理未完成时,多次调用Animator::RequestFrame函数
return;
}
//如果当前线程不是flutter的UI线程,那么切换回flutter的UI线程。
task_runners_.GetUITaskRunner()->PostTask([self = weak_factory_.GetWeakPtr(),
frame_number = frame_number_]() {
if (!self.get()) {
return;
}
self->AwaitVSync();
});
//表示有新帧需要绘制
frame_scheduled_ = true;
}
在AwaitVSync
函数中,主要做了两件事。
- 调用
VsyncWaiter
的AsyncWaitForVsync
函数,并传递一个回调函数。当Android
平台确定了UI绘制时机后,就会调用该回调函数来进行UI的绘制。 - 调用
Shell
的OnAnimatorNotifyIdle
函数,该函数主要是执行一些设置的回调函数及对当前isolate中的堆进行一次垃圾回收。在Android
中,为了保证帧率的稳定,每一帧都会间隔一段时间(60hz是16.67ms,90hz是11.11ms)来进行UI绘制。所以在执行UI绘制的回调函数之前,UI线程是空闲的,而OnAnimatorNotifyIdle
函数就是在这段空闲时间来执行的,所以不允许OnAnimatorNotifyIdle
的执行时间过长,否则影响UI的绘制。
AwaitVSync
的代码实现如下。
[->flutter/lib/shell/common/animator.cc]
//regenerate_layer_tree_默认为true,所以该函数默认返回值为false
bool Animator::CanReuseLastLayerTree() {
return !regenerate_layer_tree_;
}
//等待vsync信号
void Animator::AwaitVSync() {
waiter_->AsyncWaitForVsync(
[self = weak_factory_.GetWeakPtr()](fml::TimePoint frame_start_time,
fml::TimePoint frame_target_time) {
if (self) {
if (self->CanReuseLastLayerTree()) {
self->DrawLastLayerTree();
} else {
//开始处理帧
self->BeginFrame(frame_start_time, frame_target_time);
}
}
});
//delegate_是一个Shell对象
delegate_.OnAnimatorNotifyIdle(dart_frame_deadline_);
}
在VsyncWaiter
的AsyncWaitForVsync
函数中调用了VsyncWaiterAndroid
对象的AwaitVSync
函数。
[->flutter/lib/shell/common/vsync_waiter.cc]
void VsyncWaiter::AsyncWaitForVsync(const Callback& callback) {
//如果回调函数不存在,直接返回
if (!callback) {
return;
}
{
std::scoped_lock lock(callback_mutex_);
if (callback_) {
//防止在一个帧处理间隔内多次请求
return;
}
//设置帧处理的回调函数,在后面会用到
callback_ = std::move(callback);
if (secondary_callback_) {
// Return directly as `AwaitVSync` is already called by
// `ScheduleSecondaryCallback`.
return;
}
}
//通过JNI调用Java方法
AwaitVSync();
}
再来看AwaitVSync
函数,该函数就是通过JNI来调用Java方法。
[->flutter/lib/shell/platform/android/vsync_waiter_android.cc]
void VsyncWaiterAndroid::AwaitVSync() {
//持有弱引用
auto* weak_this = new std::weak_ptr<VsyncWaiter>(shared_from_this());
//拿到VsyncWaiter对象的地址
jlong java_baton = reinterpret_cast<jlong>(weak_this);
//切换回平台线程
task_runners_.GetPlatformTaskRunner()->PostTask([java_baton]() {
JNIEnv* env = fml::jni::AttachCurrentThread();
//调用Java方法
env->CallStaticVoidMethod(g_vsync_waiter_class->obj(), //
g_async_wait_for_vsync_method_, //
java_baton //
);
});
}
1.2、Android平台的处理
AwaitVSync
函数通过平台线程来调用Java中的方法时,会传递VsyncWaiter
对象的地址。这里调用的Java方法就是FlutterJNI
中的asyncWaitForVsync
方法,该方法的是在Flutter
引擎初始化时注册的。
@Keep
public class FlutterJNI {
public static void setAsyncWaitForVsyncDelegate(@Nullable AsyncWaitForVsyncDelegate delegate) {
asyncWaitForVsyncDelegate = delegate;
}
//cookie就是VsyncWaiter对象的地址
private static void asyncWaitForVsync(final long cookie) {
if (asyncWaitForVsyncDelegate != null) {
asyncWaitForVsyncDelegate.asyncWaitForVsync(cookie);
} else {
throw new IllegalStateException("An AsyncWaitForVsyncDelegate must be registered with FlutterJNI before asyncWaitForVsync() is invoked.");
}
}
// TODO(mattcarroll): add javadocs
public static native void nativeOnVsync(long frameTimeNanos, long frameTargetTimeNanos, long cookie);
}
在asyncWaitForVsync
方法中会执行asyncWaitForVsyncDelegate
对象的asyncWaitForVsync
方法。
再来看asyncWaitForVsync
方法的实现,该方法主要是通过Choreographer
来获取UI绘制时机。根据fps不同,每一帧的的绘制时间也不尽相同。当fps为60hz的时候,refreshPeriodNanos
约为16.67ms,当fps为90hz的时候,refreshPeriodNanos
约为11.11ms。
也就是如果没有在规定的时间内完成UI的绘制,那么就会出现掉帧现象。
public class VsyncWaiter {
private static VsyncWaiter instance;
...
@NonNull
private final WindowManager windowManager;
private final FlutterJNI.AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate = new FlutterJNI.AsyncWaitForVsyncDelegate() {
@Override
public void asyncWaitForVsync(long cookie) {
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
//一般都是60HZ,但现在也存在fps为90HZ的手机
float fps = windowManager.getDefaultDisplay().getRefreshRate();
//每一帧的绘制时间,当fps为60hz时,refreshPeriodNanos约为16.67ms。当fps为90hz时,refreshPeriodNanos约为11.11ms。
long refreshPeriodNanos = (long) (1000000000.0 / fps);
//第一个参数时帧绘制的开始时间
//第二个参数是帧绘制的理论结束时间。之所以是理论,是因为帧绘制可能会提前结束,也可能延后结束,从而出现掉帧现象。
//第三个参数就是VsyncWaiter对象的地址
FlutterJNI.nativeOnVsync(frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);
}
});
}
};
...
//flutter引擎初始化时调用
public void init() {
FlutterJNI.setAsyncWaitForVsyncDelegate(asyncWaitForVsyncDelegate);
// TODO(mattcarroll): look into moving FPS reporting to a plugin
float fps = windowManager.getDefaultDisplay().getRefreshRate();
FlutterJNI.setRefreshRateFPS(fps);
}
}
通过Android
平台计算出每一帧的绘制时间,然后通过nativeOnVsync
方法将绘制时间传递给Flutter
引擎。
1.3、Flutter引擎处理
nativeOnVsync
方法对应着VsyncWaiterAndroid
的OnNativeVsync
函数,在该函数中通过传递过来的VsyncWaiter
对象地址来调用VsyncWaiter
对象的FireCallback
函数。代码实现如下。
[->flutter/lib/shell/platform/android/vsync_waiter_android.cc]
void VsyncWaiterAndroid::OnNativeVsync(JNIEnv* env,
jclass jcaller,
jlong frameTimeNanos,
jlong frameTargetTimeNanos,
jlong java_baton) {
//帧绘制的开始时间
auto frame_time = fml::TimePoint::FromEpochDelta(
fml::TimeDelta::FromNanoseconds(frameTimeNanos));
//帧绘制结束的目标时间
auto target_time = fml::TimePoint::FromEpochDelta(
fml::TimeDelta::FromNanoseconds(frameTargetTimeNanos));
ConsumePendingCallback(java_baton, frame_time, target_time);
}
// static
void VsyncWaiterAndroid::ConsumePendingCallback(
jlong java_baton,
fml::TimePoint frame_start_time,
fml::TimePoint frame_target_time) {
auto* weak_this = reinterpret_cast<std::weak_ptr<VsyncWaiter>*>(java_baton);
auto shared_this = weak_this->lock();
delete weak_this;
if (shared_this) {
//调用VsyncWaiter的FireCallback函数
shared_this->FireCallback(frame_start_time, frame_target_time);
}
}
在FireCallback
函数中,主要是执行已经设置的回调函数。该回调函数是在执行VsyncWaiter
的AsyncWaitForVsync
函数时设置的。FireCallback
函数代码实现如下。
[->flutter/lib/shell/common/vsync_waiter.cc]
void VsyncWaiter::FireCallback(fml::TimePoint frame_start_time,
fml::TimePoint frame_target_time) {
Callback callback;
fml::closure secondary_callback;
{
std::scoped_lock lock(callback_mutex_);
callback = std::move(callback_);
secondary_callback = std::move(secondary_callback_);
}
//检查是否设置回调函数(callback)及第二个回调函数(secondary_callback)
if (!callback && !secondary_callback) {
//未找到匹配的回调函数
return;
}
if (callback) {
auto flow_identifier = fml::tracing::TraceNonce();
//切换回flutter的UI线程
task_runners_.GetUITaskRunner()->PostTaskForTime(
[callback, flow_identifier, frame_start_time, frame_target_time]() {
//执行callback回调函数
callback(frame_start_time, frame_target_time);
},
frame_start_time);
}
if (secondary_callback) {
task_runners_.GetUITaskRunner()->PostTaskForTime(
std::move(secondary_callback), frame_start_time);
}
}
上面代码中,有两个回调函数callback
与secondary_callback
。secondary_callback
主要用于滑动中,在类SmoothPointerDataDispatcher
中使用,不是本文的重点。所以来看callback
,在该回调函数中,会调用Animator
的BeginFrame
函数。代码如下。
[->flutter/lib/shell/common/animator.cc]
//如果等待51毫秒(比60hz的3帧多1毫秒)后,没有UI更新,那么UI线程处于空闲状态
constexpr fml::TimeDelta kNotifyIdleTaskWaitTime =
fml::TimeDelta::FromMilliseconds(51);
}
void Animator::BeginFrame(fml::TimePoint frame_start_time,
fml::TimePoint frame_target_time) {
...
frame_scheduled_ = false;
notify_idle_task_id_++;
regenerate_layer_tree_ = false;
pending_frame_semaphore_.Signal();
if (!producer_continuation_) {
//如果pipeline中存在有效的continuation,则重用
producer_continuation_ = layer_tree_pipeline_->Produce();
if (!producer_continuation_) {
//由于消费者比生产者慢,从而导致pipeline已满。将在下一帧重试。
RequestFrame();
return;
}
}
last_begin_frame_time_ = frame_start_time;
dart_frame_deadline_ = FxlToDartOrEarlier(frame_target_time);
{
//这里的delegate_是一个shell对象
delegate_.OnAnimatorBeginFrame(frame_target_time);
}
//如果不存在下一帧
if (!frame_scheduled_) {
task_runners_.GetUITaskRunner()->PostDelayedTask(
[self = weak_factory_.GetWeakPtr(),
notify_idle_task_id = notify_idle_task_id_]() {
if (!self.get()) {
return;
}
//通知引擎,当前线程处于空闲状态
if (notify_idle_task_id == self->notify_idle_task_id_ &&
!self->frame_scheduled_) {
TRACE_EVENT0("flutter", "BeginFrame idle callback");
self->delegate_.OnAnimatorNotifyIdle(Dart_TimelineGetMicros() +
100000);
}
},
kNotifyIdleTaskWaitTime);
}
}
上面代码中,重点在于OnAnimatorBeginFrame
函数的执行。由于delegate_
是Shell
对象,所以来看Shell
对象的OnAnimatorBeginFrame
函数。代码如下。
[->flutter/lib/shell/common/shell.cc]
void Shell::OnAnimatorBeginFrame(fml::TimePoint frame_time) {
if (engine_) {
engine_->BeginFrame(frame_time);
}
}
在OnAnimatorBeginFrame
中调用的是Engine
对象BeginFrame
函数。代码如下。
[->flutter/lib/shell/common/engine.cc]
void Engine::BeginFrame(fml::TimePoint frame_time) {
runtime_controller_->BeginFrame(frame_time);
}
在BeginFrame
函数中调用的是RuntimeController
对象的BeginFrame
函数。代码如下。
[->flutter/lib/runtime/runtime_controller.cc]
bool RuntimeController::BeginFrame(fml::TimePoint frame_time) {
if (auto* window = GetWindowIfAvailable()) {
window->BeginFrame(frame_time);
return true;
}
return false;
}
在BeginFrame
函数中调用Window
的BeginFrame
函数。这样就又回到Window
对象,开始于Window
的scheduleFrame
函数,结束于Window
的BeginFrame
函数。
再来看Window
中BeginFrame
函数的实现,它主要做了三件事。
- 调用
Flutter
的framework层Window
对象的onBeginFrame
方法。 - 一次性执行所有的微任务,不包括执行微任务过程中新增的微任务。
- 调用
Flutter
的framework层Window
对象的onDrawFrame
方法。
代码如下。
[->flutter/lib/ui/window.cc]
void Window::BeginFrame(fml::TimePoint frameTime) {
std::shared_ptr<tonic::DartState> dart_state = library_.dart_state().lock();
if (!dart_state)
return;
tonic::DartState::Scope scope(dart_state);
int64_t microseconds = (frameTime - fml::TimePoint()).ToMicroseconds();
//调用hook.dart中的_beginFrame方法
tonic::LogIfError(tonic::DartInvokeField(library_.value(), "_beginFrame",
{
Dart_NewInteger(microseconds),
}));
//执行队列中的所有微任务
UIDartState::Current()->FlushMicrotasksNow();
//调用hook.dart中的_drawFrame方法
tonic::LogIfError(tonic::DartInvokeField(library_.value(), "_drawFrame", {}));
}
hook.dart
中的_beginFrame
方法的实现如下。
@pragma('vm:entry-point')
// ignore: unused_element
void _beginFrame(int microseconds) {
_invoke1<Duration>(window.onBeginFrame, window._onBeginFrameZone, Duration(microseconds: microseconds));
}
void _invoke1<A>(void callback(A a), Zone zone, A arg) {
if (callback == null)
return;
assert(zone != null);
//在Zone中执行window.dart中的onBeginFrame方法
if (identical(zone, Zone.current)) {
callback(arg);
} else {
zone.runUnaryGuarded<A>(callback, arg);
}
}
hook.dart
中的_drawFrame
方法的实现如下。
@pragma('vm:entry-point')
// ignore: unused_element
void _drawFrame() {
_invoke(window.onDrawFrame, window._onDrawFrameZone);
}
/// Invokes [callback] inside the given [zone].
void _invoke(void callback(), Zone zone) {
if (callback == null)
return;
//在Zone中执行window.dart中的onDrawFrame方法
if (identical(zone, Zone.current)) {
callback();
} else {
zone.runGuarded(callback);
}
}
从而最终在window
中的onDrawFrame
方法中来进行一帧的绘制,当需要绘制下一帧时,通过主动调用window
的scheduleFrame
方法即可,以此循环,从而实现了Flutter
中绚丽多彩的UI。
再来看这一帧UI绘制过程的流程图。如下。
关于onDrawFrame
方法后的绘制绘制流程详见Flutter之UI绘制流程一
2、总结
经过上面,可以发现Flutter
与Android
一样,UI都是通过Window
(窗口)来展示。但在Android
中,是可以拥有多个Window
,页面切换也基本上都是Window
间的切换。而Flutter
中,则仅有一个Window
,页面切换也就是在该Window
中处理。由于在Flutter
中,Window
会与引擎一一对应,所以如果想要多Window
,则需要创建多个引擎。
【参考资料】