Flutter 繪製動機 VSYNC 流程原始碼全方位分析

工匠若水發表於2021-08-22

這是我參與8月更文挑戰的第6天,活動詳情檢視: 8月更文挑戰

Flutter 系列文章連載~

背景

前面系列我們依賴 Android 平臺實現分析了端側很多機制,但是有一個知識點一直比較迷糊,那就是 Flutter 是怎麼被觸發繪製的?這個問題在網上的答案基本都說 VSYNC,但是少有人說這個 VSYNC 是怎麼被關聯起來的,本文就針對這個問題進行一個 Platform 到 Engine 到 Dart Framework 分析,原始碼依賴 Flutter 2.2.3。

Android 平臺 Java 層

還記得我們前面系列文章分析過的io.flutter.embedding.engine.FlutterJNI嗎,FlutterJNI 的作用就是架起 Android 端 Java 與 Flutter Engine C/C++ 端的一座介面橋樑。記不記得當時我們分析 FlutterEngine 時(《Flutter Android 端 FlutterEngine Java 相關流程原始碼分析》)在他的例項化過程中有這麼一段呼叫邏輯:

-> 呼叫 FlutterEngine 構造方法
-> 呼叫 FlutterLoader 的 startInitialization(context.getApplicationContext()) 方法
-> 呼叫 VsyncWaiter.getInstance((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE)).init() 方法
複製程式碼

基於上面流程,我們把重點轉向 Java 端的 VsyncWaiter 類及其 init 方法,如下:

//程式單例例項類
public class VsyncWaiter {
  //......
  //一個來自engine觸發的回撥
  private final FlutterJNI.AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate =
      new FlutterJNI.AsyncWaitForVsyncDelegate() {
        @Override
        public void asyncWaitForVsync(long cookie) {
          //每逢回撥回來就向Choreographer post一個繪製VSYNC請求。
          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邏輯通知VSYNC
                      FlutterJNI.nativeOnVsync(
                          frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);
                    }
                  });
        }
      };
  //......
  //唯一被呼叫的方法
  public void init() {
    //設定委託例項回撥引用
    FlutterJNI.setAsyncWaitForVsyncDelegate(asyncWaitForVsyncDelegate);

    //傳輸fps值給engine
    float fps = windowManager.getDefaultDisplay().getRefreshRate();
    FlutterJNI.setRefreshRateFPS(fps);
  }
}
複製程式碼

我們簡單看下 FlutterJNI 裡面是怎麼做的,如下:

@Keep
public class FlutterJNI {
  //......
  //VsyncWaiter.init方法設定的委託回撥實現就是賦值給了他
  private static AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate;
  //......
  //VsyncWaiter.init方法中呼叫
  public static void setAsyncWaitForVsyncDelegate(@Nullable AsyncWaitForVsyncDelegate delegate) {
    asyncWaitForVsyncDelegate = delegate;
  }

  //這個方法的註釋明確說了被 netive engine 呼叫,也就是 JNI 的 C/C++ 端呼叫
  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.");
    }
  }

  //java端呼叫native實現
  public static native void nativeOnVsync(
      long frameTimeNanos, long frameTargetTimeNanos, long cookie);

  public interface AsyncWaitForVsyncDelegate {
    void asyncWaitForVsync(final long cookie);
  }
}
複製程式碼

對於安卓仔來說,上面程式碼中熟悉的系統 API 比較多,所以我們先回到純 Android 平臺。老司機都知道,現代 Android 系統至少都是基於 VSYNC 的 Double Buffer(雙緩衝)機制實現繪製,而雙緩衝機制背後的核心思想是讓繪製和顯示擁有各自的影像緩衝區,也就是說 GPU 始終將完成的一幀影像資料寫入到 Back Buffer,而顯示器使用 Frame Buffer 資料進行顯示,這樣雙緩衝 Frame Buffer 中的資料一定不會存在撕裂(類似併發不安全的寫),VSYNC 訊號負責排程從 Back Buffer 到 Frame Buffer 的交換操作,這裡並不是真正的資料 copy,實際是交換各自的記憶體地址,可以認為該操作是瞬間完成。

在這裡插入圖片描述 看過我 Android 原始碼分析系列文章或者其他網文的小夥伴一定都知道,Android中有一個 ViewRootImpl,他的 mView 成員是 DecorView(本質 FrameLayout),而 DecorView 是一個 Activity 的根 View。整個介面的重繪入口都是 ViewRootImpl 類的 scheduleTraversals 方法(不懂就去看歷史文章),我們自己呼叫 View 的 invalidate 方法也是類似,如下:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        //......
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        //......
    }
}
複製程式碼

上面的 mTraversalRunnable 就是呼叫了 Activity 中 View 樹的 measure、layout、draw 進行繪製。而 mChoreographer 就是 Choreographer,在安卓平臺上,Choreographer 通過 postXXX 呼叫 FrameDisplayEventReceiver(繼承自 DisplayEventReceiver) 的 nativeScheduleVsync 方法進行 VSYNC 請求;同時 Choreographer 也通過 FrameDisplayEventReceiver 的 onVsync 方法監聽了系統 VSYNC 脈衝訊號,該監聽方法中會觸發 Choreographer 的 doFrame 方法,該方法會把我們 post 進去的 callback 佇列拿出來執行,然後將已經執行過的 callback 進行移除。整個過程如下圖: 在這裡插入圖片描述 簡單總結下結論,安卓應用程式如果有繪製(包括動畫)需求的話,必須向系統框架發起 VSYNC 請求,請求在下一次 VSYNC 訊號到來時繪製應用介面。

看到上面這個結論其實如果你有一定悟性應該能猜到 Flutter 的 VSYNC 是怎麼工作的了,他其實也實現了類似標準安卓繪製觸發的流程,即傳送 VSYNC 請求,等待下一個 VSYNC 訊號到來執行 callback 回撥。我們在繼續分析前可以基於上面 VsyncWaiter 和 FlutterJNI 相關介面進行一個猜想如下: 在這裡插入圖片描述

Flutter Framework Dart 層

Android 平臺 Java 層面的問題我們都分析完畢了,通過上面 Flutter VSYNC 猜想時序圖我們知道重點都在 Flutter Engine 裡面。也就是說 Flutter Engine 呼叫 FlutterJNI 的 asyncWaitForVsync 方法通過安卓平臺的 Choreographer 傳送 VSYNC 請求,請求在安卓平臺下一次 VSYNC 訊號到來時通過 FlutterJNI 的 nativeOnVsync 方法向 Flutter Engine 傳遞繪製訊號,整個過程像極了安卓 View 統管的 ViewRootImpl 實現。

我們知道,Flutter Engine 是 Flutter Dart Framework 與 Android 平臺之間的一個橋樑,抽象如下: 在這裡插入圖片描述 所以我們基於前面 Flutter 系列分析及上面 Android 繪製機制大膽猜測可以知道,VSYNC 請求來自 Flutter Dart Framework,下一次 VSYNC 訊號到來觸發繪製也呼叫到了 Flutter Dart Framework,Flutter Engine 只是一個橋樑處理過程。

發起繪製 VSYNC 請求

前面我們分析 Flutter App Dart main 方法時有提到 scheduleWarmUpFrame 方法最終呼叫了 SchedulerBinding 的 scheduleFrame 方法,進而呼叫window.scheduleFrame(),再呼叫 PlatformDispatcher 的scheduleFrame(),程式碼如下:

class PlatformDispatcher {
  /// 發起VSYNC請求,等待下一幀呼叫onBeginFrame和onDrawFrame回撥。
  void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';
}
複製程式碼

PlatformDispatcher 的 scheduleFrame 方法實現其實是 Dart 呼叫 C/C++ native 程式碼,對應的也是 PlatformConfiguration_scheduleFrame,我們可以在 engine 的 C/C++ 中搜其註冊入口。

上面方法就是 Flutter 層真正發起 VSYNC 請求的地方,然後等系統下一個 VSYNC 訊號到來進行繪製操作(即來自 FlutterJni 的 nativeOnVsync 方法觸發),也就是最終呼叫到 Dart 層的 onBeginFrame 和 onDrawFrame。

其實我們日常中呼叫 Flutter Dart StatefulWidget 的 setState 方法也是呼叫了上面 scheduleFrame 方法,也就是說繪製的發起都來自 Widget 的變更主動呼叫觸發,包括動畫效果等也是同樣道理。

收到下一幀 VSYNC 繪製訊號

當上面 VSYNC 請求發出且等到下一個 VSYNC 訊號到來時會通過 Java 到 C/C++ 再到 Dart Framework 層,對應到 Dart 層入口在hooks.dart檔案(呼叫詳見下面 Flutter Engine C/C++ 層分析),如下:

@pragma('vm:entry-point')
// ignore: unused_element
void _beginFrame(int microseconds) {
  PlatformDispatcher.instance._beginFrame(microseconds);
}

@pragma('vm:entry-point')
// ignore: unused_element
void _drawFrame() {
  PlatformDispatcher.instance._drawFrame();
}
複製程式碼

本質在 PlatformDispatcher 中呼叫對應方法,即如下:

class PlatformDispatcher {
  FrameCallback? get onBeginFrame => _onBeginFrame;
  FrameCallback? _onBeginFrame;
  Zone _onBeginFrameZone = Zone.root;
  set onBeginFrame(FrameCallback? callback) {
    _onBeginFrame = callback;
    _onBeginFrameZone = Zone.current;
  }

  VoidCallback? get onDrawFrame => _onDrawFrame;
  VoidCallback? _onDrawFrame;
  Zone _onDrawFrameZone = Zone.root;
  set onDrawFrame(VoidCallback? callback) {
    _onDrawFrame = callback;
    _onDrawFrameZone = Zone.current;
  }
}
複製程式碼

也就是呼叫了 PlatformDispatcher 通過 onBeginFrame、onDrawFrame 設定的對應回撥,我們反過來推看誰設定了這個回撥賦值,首先看到的呼叫賦值位於 SingletonFlutterWindow:

class SingletonFlutterWindow extends FlutterWindow {
  FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame;
  set onBeginFrame(FrameCallback? callback) {
    platformDispatcher.onBeginFrame = callback;
  }
}
複製程式碼

接著看 SingletonFlutterWindow 的 onBeginFrame 屬性是誰賦值的,發現賦值呼叫如下:

mixin SchedulerBinding on BindingBase {
  void ensureFrameCallbacksRegistered() {
    //回撥實現位於SchedulerBinding中
    window.onBeginFrame ??= _handleBeginFrame;
    window.onDrawFrame ??= _handleDrawFrame;
  }

  void scheduleFrame() {
    //......
    ensureFrameCallbacksRegistered();
    window.scheduleFrame();
    //......
  }
}
複製程式碼

可以看到本質回到了 SchedulerBinding 的 scheduleFrame 方法,也就是說第一次 Dart 發起 VSYNC 請求前先設定了回撥,當下一個系統 VSYNC 訊號到來時就呼叫了 onBeginFrame、onDrawFrame 的回撥賦值。也就是說真正的繪製到 Dart 層入口在 SchedulerBinding 的void handleBeginFrame(Duration? rawTimeStamp)handleDrawFrame()中,關於他們的具體內容不在本文分析範圍,本文關注 VSYNC 動機過程。

Dart 層大致流程如下: 在這裡插入圖片描述

Flutter Engine C/C++ 層

有了上面 Dart 層及 Java 層的分析,我們其實分析 Engine 層的 C/C++ 時就大致知道關鍵入口是什麼了,所以下面依然基發起 VSYNC 請求和下一幀回撥 VSYNC 訊號流程進行分析。

發起繪製 VSYNC 請求

通過 Dart 分析得知 VSYNC 訊號的發起的實現是通過 Dart 呼叫了 engine C/C++ 的PlatformConfiguration_scheduleFrame native 方法,所以我們搜尋可以看到對應 C/C++ 只有一處註冊且位於lib/ui/window/platform_configuration.cc檔案:

void PlatformConfiguration::RegisterNatives(
    tonic::DartLibraryNatives* natives) {
  natives->Register({
      //......
      {"PlatformConfiguration_scheduleFrame", ScheduleFrame, 1, true},
      //......
  });
}

void ScheduleFrame(Dart_NativeArguments args) {
  UIDartState::ThrowIfUIOperationsProhibited();
  //client()本質PlatformConfigurationClient,也就是RuntimeController
  UIDartState::Current()->platform_configuration()->client()->ScheduleFrame();
}
複製程式碼

通過上面程式碼可以看到,Dart 呼叫 engine C/C++ 的PlatformConfiguration_scheduleFrame native 方法走進了lib/ui/window/platform_configuration.cc檔案的void ScheduleFrame(Dart_NativeArguments args)方法,通過platform_configuration.h檔案中可以知道,client 是 PlatformConfigurationClient 型別,而 RuntimeController 類是他的實現,即runtime/runtime_controller.h中如下:

class RuntimeController : public PlatformConfigurationClient {
    //......
}
複製程式碼

因此我們把目光轉向 RuntimeController 類,即runtime/runtime_controller.h中如下:

void RuntimeController::ScheduleFrame() {
  //client_ 型別為 RuntimeDelegate,也就是 engine instance
  client_.ScheduleFrame();
}
複製程式碼

通過runtime/runtime_controller.h中 client 的註釋可以知道,client_ 其實是 Engine 例項,即shell/common/engine.h中如下:

class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
    //......
}
複製程式碼

對應實現shell/common/engine.cc如下:

void Engine::ScheduleFrame(bool regenerate_layer_tree) {
  animator_->RequestFrame(regenerate_layer_tree);
}
複製程式碼

類似同上分析模式檢視對應 h 和 cpp 檔案可以知道 animator_ 位於shell/common/animator.cc,如下:

void Animator::RequestFrame(bool regenerate_layer_tree) {
  //......
  task_runners_.GetUITaskRunner()->PostTask(//......
       frame_request_number = frame_request_number_]() {
        //......
        self->AwaitVSync();
      });
}
複製程式碼

在引擎的 UITaskRunner 中執行shell/common/animator.cc檔案的 AwaitVSync 方法,如下:

void Animator::AwaitVSync() {
  waiter_->AsyncWaitForVsync(
      [self = weak_factory_.GetWeakPtr()](
          std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) {
        if (self) {
          if (self->CanReuseLastLayerTree()) {
            self->DrawLastLayerTree(std::move(frame_timings_recorder));
          } else {
            self->BeginFrame(std::move(frame_timings_recorder));
          }
        }
      });
}
複製程式碼

類似同上分析模式檢視對應 h 和 cpp 檔案可以知道 waiter_ 位於shell/common/vsync_waiter.cc,在 Android 平臺的實現類是 VsyncWaiterAndroid,位於shell/platform/android/vsync_waiter_android.cc如下:

//父類VsyncWaiter的AsyncWaitForVsync呼叫子類VsyncWaiterAndroid的AwaitVSync方法
void VsyncWaiter::AsyncWaitForVsync(const Callback& callback) {
  //......
  callback_ = std::move(callback);
  //......
  //對應VsyncWaiterAndroid::AwaitVSync()
  AwaitVSync();
}

void VsyncWaiterAndroid::AwaitVSync() {
  //......
  task_runners_.GetPlatformTaskRunner()->PostTask([java_baton]() {
    JNIEnv* env = fml::jni::AttachCurrentThread();
    env->CallStaticVoidMethod(
        //io/flutter/embedding/engine/FlutterJNI
        g_vsync_waiter_class->obj(),  
        //asyncWaitForVsync方法   
        g_async_wait_for_vsync_method_,
        //引數
        java_baton
    );
  });
}
複製程式碼

真相大白,最後通過 Engine 的 PlatformTaskRunner 呼叫了 JNI 方法 asyncWaitForVsync,也就是我們上面分析 Android 平臺 Java 層小節提到的 FlutterJNI java 類的 asyncWaitForVsync 靜態方法。哈哈,Flutter 發起 VSYNC 請求的流程就這樣從 Java 到 C++ 到 Dart,再從 Dart 到 C++ 到 Java 全串起來了。

收到下一幀 VSYNC 繪製訊號

剛剛發起繪製 VSYNC 請求最終走進了 java 層的Choreographer.getInstance().postFrameCallback(callback)方法,上面分析 Java 部分程式碼時也提到了,等下一幀 VSYNC 訊號到來會觸發 java 層 FlutterJNI 類的 nativeOnVsync 方法。經過 C/C++ 搜尋分析可知,上面 FlutterJNI 中的 nativeOnVsync 方法呼叫點位於 engine 的shell/platform/android/vsync_waiter_android.cc中,如下:

void VsyncWaiterAndroid::OnNativeVsync(JNIEnv* env, jclass jcaller, jlong frameTimeNanos,
                                       jlong frameTargetTimeNanos, jlong java_baton) {
  //......
  ConsumePendingCallback(java_baton, frame_time, target_time);
}

void VsyncWaiterAndroid::ConsumePendingCallback( jlong java_baton,
    fml::TimePoint frame_start_time, fml::TimePoint frame_target_time) {
  //......
  shared_this->FireCallback(frame_start_time, frame_target_time);
}

void VsyncWaiter::FireCallback(fml::TimePoint frame_start_time,
                               fml::TimePoint frame_target_time,
                               bool pause_secondary_tasks) {
    //......
    PauseDartMicroTasks();
    //......
    task_runners_.GetUITaskRunner()->PostTaskForTime(
        [ui_task_queue_id, callback, flow_identifier, frame_start_time,
         frame_target_time, pause_secondary_tasks]() {
          //......
          callback(std::move(frame_timings_recorder));
          //......
          ResumeDartMicroTasks(ui_task_queue_id);
          //......
        }, frame_start_time);
  }
  //......
}
複製程式碼

其實上面繞一圈最終就是在引擎的 UITaskRunner 中執行了上面 dart 發起 VSYNC 請求小節分析的 callback 引數。即,這裡的callback(std::move(frame_timings_recorder))等價於Animator::AwaitVSync()方法中呼叫waiter_->AsyncWaitForVsync方法傳遞的引數 callback,callback 賦值程式碼位於shell/common/animator.cc檔案的 AwaitVSync 方法,如下:

void Animator::AwaitVSync() {
  //AsyncWaitForVsync函式引數就是callback
  waiter_->AsyncWaitForVsync(
      [self = weak_factory_.GetWeakPtr()](
          std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) {
        if (self) {
          if (self->CanReuseLastLayerTree()) {
            self->DrawLastLayerTree(std::move(frame_timings_recorder));
          } else {
            self->BeginFrame(std::move(frame_timings_recorder));
          }
        }
      });
}
複製程式碼

真相大白,callback 被回撥(即 VSYNC 繪製訊號過來)時呼叫了 Animator 的 DrawLastLayerTree 或者 BeginFrame 方法,具體取決於是否需要重新生成 LayerTree 樹進行繪製。

由於本文我們主要關心繪製動機流程,所以上面 DrawLastLayerTree 就先不分析了,我們看看 Animator 的 BeginFrame 方法,可以發現其呼叫了delegate_.OnAnimatorBeginFrame(frame_target_time, frame_number);,也就是 Shell 的 OnAnimatorBeginFrame 方法,本質就是 Engine 的 BeginFrame 方法,如下shell/common/engine.cc

void Engine::BeginFrame(fml::TimePoint frame_time, uint64_t frame_number) {
  TRACE_EVENT0("flutter", "Engine::BeginFrame");
  runtime_controller_->BeginFrame(frame_time, frame_number);
}
複製程式碼

RuntimeController 的 BeginFrame 方法呼叫了 PlatformConfiguration 的 BeginFrame 方法,如下lib/ui/window/platform_configuration.cc

void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime,
                                       uint64_t frame_number) {
  //......
  tonic::LogIfError(
      tonic::DartInvoke(begin_frame_.Get(), {
          Dart_NewInteger(microseconds),
          Dart_NewInteger(frame_number),
      }));
  UIDartState::Current()->FlushMicrotasksNow();
  tonic::LogIfError(tonic::DartInvokeVoid(draw_frame_.Get()));
}

void PlatformConfiguration::DidCreateIsolate() {
  Dart_Handle library = Dart_LookupLibrary(tonic::ToDart("dart:ui"));
  //......
                   Dart_GetField(library, tonic::ToDart("_beginFrame")));
  draw_frame_.Set(tonic::DartState::Current(),
                  Dart_GetField(library, tonic::ToDart("_drawFrame")));
  //......
}
複製程式碼

哈哈,這就呼應了上面 Flutter Framework Dart 層小節收到下一幀 VSYNC 繪製訊號部分被呼叫的入庫,即下一個 VSYNC 繪製訊號過來最終引擎 engine 呼叫了 Dart 層入口在hooks.dart檔案的_beginFrame_drawFrame等方法觸發 dart 層進行繪製操作。

C++ 層流程大致總結如下: 在這裡插入圖片描述

總結

到此我想你應該就能大概看懂 Flutter 官網貼的這張經典繪製流程圖了: 在這裡插入圖片描述 關於上圖中的每一步細節不在本文分析範圍之內,但是關於上圖從發起 Flutter VSYNC 請求到收到系統下一個 VSYNC 繪製訊號進行繪製操作的全流程我們算是徹底搞明白了,也從一定程度上理解了 Flutter 架構分層圖的整個架構流轉機制。

其實搞懂本文 VSYNC 訊號從 Dart 到 C++ 到 Java,再從 Java 到 C++ 到 Dart,可以不誇張的說你已經掌握了 Flutter 架構的精髓,缺少的只是這條鏈條上的各個細節節點而已。

相關文章