Flutter之UI繪製流程二

大逗大人發表於2020-04-05

經過Flutter之引擎啟動流程一文,瞭解了FlutterAndroid平臺中是如何建立引擎並執行入口函式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函式對應著引擎中windowScheduleFrame函式。程式碼實現如下。

[->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函式中,主要做了兩件事。

  1. 呼叫VsyncWaiterAsyncWaitForVsync函式,並傳遞一個回撥函式。當Android平臺確定了UI繪製時機後,就會呼叫該回撥函式來進行UI的繪製。
  2. 呼叫ShellOnAnimatorNotifyIdle函式,該函式主要是執行一些設定的回撥函式及對當前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_);
}
複製程式碼

VsyncWaiterAsyncWaitForVsync函式中呼叫了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方法對應著VsyncWaiterAndroidOnNativeVsync函式,在該函式中通過傳遞過來的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函式中,主要是執行已經設定的回撥函式。該回撥函式是在執行VsyncWaiterAsyncWaitForVsync函式時設定的。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);
  }
}
複製程式碼

上面程式碼中,有兩個回撥函式callbacksecondary_callbacksecondary_callback主要用於滑動中,在類SmoothPointerDataDispatcher中使用,不是本文的重點。所以來看callback,在該回撥函式中,會呼叫AnimatorBeginFrame函式。程式碼如下。

[->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函式中呼叫WindowBeginFrame函式。這樣就又回到Window物件,開始於WindowscheduleFrame函式,結束於WindowBeginFrame函式。

再來看WindowBeginFrame函式的實現,它主要做了三件事。

  1. 呼叫Flutter的framework層Window物件的onBeginFrame方法。
  2. 一次性執行所有的微任務,不包括執行微任務過程中新增的微任務。
  3. 呼叫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方法中來進行一幀的繪製,當需要繪製下一幀時,通過主動呼叫windowscheduleFrame方法即可,以此迴圈,從而實現了Flutter中絢麗多彩的UI。

再來看這一幀UI繪製過程的流程圖。如下。

Flutter之UI繪製流程二

關於onDrawFrame方法後的繪製繪製流程詳見Flutter之UI繪製流程一

2、總結

經過上面,可以發現FlutterAndroid一樣,UI都是通過Window(視窗)來展示。但在Android中,是可以擁有多個Window,頁面切換也基本上都是Window間的切換。而Flutter中,則僅有一個Window,頁面切換也就是在該Window中處理。由於在Flutter中,Window會與引擎一一對應,所以如果想要多Window,則需要建立多個引擎。

Flutter之UI繪製流程二

【參考資料】

Android 基於 Choreographer 的渲染機制詳解

Android 新的流暢體驗,90Hz 漫談

相關文章