Flutter 绘制动机 VSYNC 流程源码全方位分析

2021-08-23 11:59:15 浏览数 (1)

Flutter 系列文章连载~

  • 《Flutter Android 工程结构及应用层编译源码深入分析》
  • 《Flutter 命令本质之 Flutter tools 机制源码深入分析》
  • 《Flutter 的 runApp 与三棵树诞生流程源码分析》
  • 《Flutter Android 端 Activity/Fragment 流程源码分析》
  • 《Flutter Android 端 FlutterInjector 及依赖流程源码分析》
  • 《Flutter Android 端 FlutterEngine Java 相关流程源码分析》
  • 《Flutter Android 端 FlutterView 相关流程源码分析》
  • 《Flutter 绘制动机 VSYNC 流程源码全方位分析》

背景

前面系列我们依赖 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 相关流程源码分析》)在他的实例化过程中有这么一段调用逻辑:

代码语言:txt复制
-> 调用 FlutterEngine 构造方法
-> 调用 FlutterLoader 的 startInitialization(context.getApplicationContext()) 方法
-> 调用 VsyncWaiter.getInstance((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE)).init() 方法

基于上面流程,我们把重点转向 Java 端的 VsyncWaiter 类及其 init 方法,如下:

代码语言:txt复制
//进程单例实例类
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 里面是怎么做的,如下:

代码语言:txt复制
@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 方法也是类似,如下:

代码语言:txt复制
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(),代码如下:

代码语言:txt复制
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 层分析),如下:

代码语言:txt复制
@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 中调用对应方法,即如下:

代码语言:txt复制
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:

代码语言:txt复制
class SingletonFlutterWindow extends FlutterWindow {
  FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame;
  set onBeginFrame(FrameCallback? callback) {
    platformDispatcher.onBeginFrame = callback;
  }
}

接着看 SingletonFlutterWindow 的 onBeginFrame 属性是谁赋值的,发现赋值调用如下:

代码语言:txt复制
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文件:

代码语言:txt复制
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中如下:

代码语言:txt复制
class RuntimeController : public PlatformConfigurationClient {
    //......
}

因此我们把目光转向 RuntimeController 类,即runtime/runtime_controller.h中如下:

代码语言:txt复制
void RuntimeController::ScheduleFrame() {
  //client_ 类型为 RuntimeDelegate,也就是 engine instance
  client_.ScheduleFrame();
}

通过runtime/runtime_controller.h中 client 的注释可以知道,client_ 其实是 Engine 实例,即shell/common/engine.h中如下:

代码语言:txt复制
class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
    //......
}

对应实现shell/common/engine.cc如下:

代码语言:txt复制
void Engine::ScheduleFrame(bool regenerate_layer_tree) {
  animator_->RequestFrame(regenerate_layer_tree);
}

类似同上分析模式查看对应 h 和 cpp 文件可以知道 animator_ 位于shell/common/animator.cc,如下:

代码语言:txt复制
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 方法,如下:

代码语言:txt复制
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如下:

代码语言:txt复制
//父类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中,如下:

代码语言:txt复制
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 方法,如下:

代码语言:txt复制
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

代码语言:txt复制
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

代码语言:txt复制
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 架构的精髓,缺少的只是这条链条上的各个细节节点而已。

0 人点赞