經過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
,則需要建立多個引擎。
【參考資料】