Flutter之UI绘制流程二

2,623 阅读9分钟

经过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绘制过程的流程图。如下。

关于onDrawFrame方法后的绘制绘制流程详见Flutter之UI绘制流程一

2、总结

经过上面,可以发现FlutterAndroid一样,UI都是通过Window(窗口)来展示。但在Android中,是可以拥有多个Window,页面切换也基本上都是Window间的切换。而Flutter中,则仅有一个Window,页面切换也就是在该Window中处理。由于在Flutter中,Window会与引擎一一对应,所以如果想要多Window,则需要创建多个引擎。

【参考资料】

Android 基于 Choreographer 的渲染机制详解

Android 新的流畅体验,90Hz 漫谈