Flutter 笔记 | Flutter 核心原理(四)绘制流程

Vsync 机制

在分析首帧渲染的过程中,可以发现Render Tree的渲染逻辑(handleDrawFrame方法)是直接执行的,但是后续每一帧的渲染都是Framework的主动调用导致的吗?实际上并非如此,也不能如此。试想一下,如果由Framework层控制每一帧的渲染,那么可能某一帧还没渲染完成,屏幕就开始刷新了,因为屏幕是按照自己的固有频率刷新的,而不会考虑具体的软件逻辑。此时,可能用于渲染的Buffer中,一半是当前帧的数据,一半是上一帧的数据,这就是所谓的“撕裂”(Tearing),如图5-9所示。

在这里插入图片描述
为了避免撕裂,大部分UI框架都会引入Vsync机制Vsync是垂直同步(Vertical Synchronization)的简称,其基本的思路是同步帧的渲染和显示器的刷新率。下面开始分析Flutter的Vsync机制。

Vsync 准备阶段

当UI需要更新一帧(通常是由于动画、手势或者直接调用setState导致Element Tree中出现脏节点)时,会调用ensureVisualUpdate方法, 如果没有处于渲染状态,将调用scheduleFrame方法。ensureVisualUpdate代码如下:

void ensureVisualUpdate() {
   
  switch (schedulerPhase) {
   
    case SchedulerPhase.idle:  
    case SchedulerPhase.postFrameCallbacks:
      scheduleFrame();  
      return;
    case SchedulerPhase.transientCallbacks:   
    case SchedulerPhase.midFrameMicrotasks: 
    case SchedulerPhase.persistentCallbacks:
      return;
  }
}

scheduleFrame代码如下:

void scheduleFrame() {
   
  if (_hasScheduledFrame || !framesEnabled) return;
  ensureFrameCallbacksRegistered();  
  window.scheduleFrame();  
  _hasScheduledFrame = true;
}

其中ensureFrameCallbacksRegistered方法代码如下:

// 代码清单5-25 flutter/packages/flutter/lib/src/scheduler/binding.dart

void ensureFrameCallbacksRegistered() {
   
  window.onBeginFrame ??= _handleBeginFrame; // 由代码清单5-35调用
  window.onDrawFrame ??= _handleDrawFrame;   // 由代码清单5-35调用
}
void _handleBeginFrame(Duration rawTimeStamp) {
   
  if (_warmUpFrame) {
    // 首帧仍在渲染,见代码清单5-21
    _rescheduleAfterWarmUpFrame = true;
    return;
  }
  handleBeginFrame(rawTimeStamp); // 见代码清单5-36
}
void _handleDrawFrame() {
   
  if (_rescheduleAfterWarmUpFrame) {
    // 首帧预渲染导致的调用
    _rescheduleAfterWarmUpFrame = false;
    addPostFrameCallback((Duration timeStamp) {
   
      _hasScheduledFrame = false;
      scheduleFrame(); // 见代码清单5-20
    });
    return;
  }
  handleDrawFrame(); // 见代码清单5-37
}

window.onBeginFramewindow.onDrawFrame将在注册的Vsync信号到达后调用,这部分内容后面将详细分析,其对应的接口分别调用了handleBeginFrame方法和handleDrawFrame 方法,后面将详细分析其逻辑。

需要注意的是,如果_warmUpFrame字段为true,即通过Vsync驱动的渲染开始时发现首帧渲染仍在进行,则将_rescheduleAfterWarmUpFrame标记为true,并在_handleDrawFrame 中注册一个回调后退出,该回调将在首帧渲染后请求再次渲染一帧, 而这一帧将是通过Vsync信号驱动的。

下面分析window.scheduleFrame接口的逻辑,其对应的是一个Engine方法,如代码清单5-26所示。

// 代码清单5-26 engine/lib/ui/window/platform_configuration.cc
void ScheduleFrame(Dart_NativeArguments args) {
   
  UIDartState::ThrowIfUIOperationsProhibited(); // 确保在UI线程中
  UIDartState::Current()->platform_configuration()->client()->ScheduleFrame(); // 见代码清单5-27 
} // RuntimeController是client的具体实现类

以上逻辑主要检查当前是否处于UI线程,然后调用RuntimeControllerScheduleFrame方法,最终会调用AnimatorRequestFrame方法,如代码清单5-27所示。

// 代码清单5-27 engine/shell/common/animator.cc
void Animator::RequestFrame(bool regenerate_layer_tree) {
   
  if (regenerate_layer_tree) {
    // 仅有Platform View更新,复用上一帧的Layer Tree
    regenerate_layer_tree_ = true; // 如果Animator已停止,则返回,但是如果屏幕配置发生改变(即第2个字段为true)
  } 
  if (paused_ && !dimension_change_pending_) {
    return; } // 仍会请求Vsync信号
  if (!pending_frame_semaphore_.TryWait()) {
    return; } // 已经有正在渲染的帧,返回
                                                       // 见代码清单5-34
  task_runners_.GetUITaskRunner()->PostTask([ ...... ]() {
   
    if (!self) {
    return; }
    self->AwaitVSync(); // 见代码清单5-28
  });
  frame_scheduled_ = true; // 注意,比上一句逻辑先执行
}

以上逻辑中,regenerate_layer_tree用于表示是否重新生成Flutter的渲染数据,一般情况下为true,仅当UI中存在Platform View且只有该部分需要更新时才为false。接着检查当前是否可以注册Vsync,并通过AwaitVSync发起注册。由于是PostTask方式,因此frame_scheduled会在此之前标记为true,表示当前正在计划渲染一帧。AwaitVSync方法的逻辑如代码清单5-28所示。

// 代码清单5-28 engine/shell/common/animator.cc
void Animator::AwaitVSync() {
   
  waiter_->AsyncWaitForVsync( // Vsync信号到达后将触发的逻辑
      [self = weak_factory_.GetWeakPtr()](fml::TimePoint vsync_start_time, // Vsync信号到达的时间
                 fml::TimePoint frame_target_time) {
    // 根据帧率计算的一帧绘制完成的最晚的时间点
        if (self) {
    // 见代码清单5-33
          if (self->CanReuseLastLayerTree()) {
    // 可以复用上一帧的Layer Tree
            self->DrawLastLayerTree();
          } else {
    // 开始渲染新的一帧
            self->BeginFrame(vsync_start_time, frame_target_time);
          }
        }
      }); // 通知Dart VM:当前处于等待Vsync的空闲状态,非常适合进行GC等行为
  delegate_.OnAnimatorNotifyIdle(dart_frame_deadline_); 
}

以上逻辑中,OnAnimatorNotifyIdle方法将通知 Dart VM 当前处于空闲状态,用于驱动 GC(Garbage Collection,垃圾回收)等逻辑的执行,因为当前将要注册Vsync并等待其信号,所以肯定不会更新UI,非常适合进行GC等行为。AsyncWaitForVsync方法负责Vsync监听的注册,下面进行分析。

Vsync 注册阶段

代码清单5-28中,AsyncWaitForVsync方法将继续Vsync信号的注册,其逻辑如代码清单5-29所示。

// 代码清单5-29 engine/shell/common/vsync_waiter.cc
void VsyncWaiter::AsyncWaitForVsync(const Callback& callback) {
   
  if (!callback) {
   
    return; // 若没有设置回调,则监听没有意义,直接返回
  }
  TRACE_EVENT0("flutter", "AsyncWaitForVsync");
  {
   
    std::scoped_lock lock(callback_mutex_);
    if (callback_) {
    return; } // 说明有其他逻辑注册过,直接返回
        callback_ = std::move(callback); // 赋值
    if (secondary_callback_) {
    return; } // 说明有其他逻辑注册过,无须再次注册,返回
  }
  AwaitVSync(); // 具体的实现由平台决定,Android平台的实现见代码清单5-30
}

以上逻辑中,callback是必须携带的参数,因为没有回调的注册没有意义。接着,会检查callback_字段是否已经被设置,如果有则说明其他逻辑已经注册过了,直接返回;如果为null,则赋值为当前参数。注意,这里会接着检查secondary_callback_是否有值,其一般由触摸事件触发,其赋值时会触发AwaitVSync,所以此时callback_只需要完成赋值即可,而无须重复注册Vsync信号。以上两个回调会在后面Vsync信号到达时一并处理。

完成以上逻辑后,将调用AwaitVSync方法开始正式注册Vsync信号,如代码清单5-30所示。

// 代码清单5-30 engine/shell/platform/android/vsync_waiter_android.cc
void VsyncWaiterAndroid::AwaitVSync() {
   
  auto* weak_this = new std::weak_ptr<VsyncWaiter>(shared_from_this());
  jlong java_baton = reinterpret_cast<jlong>(weak_this); // 将当前实例变成long类型的id
  task_runners_.GetPlatformTaskRunner()->PostTask([java_baton]() {
    // 切换线程
    JNIEnv* env = fml::jni::AttachCurrentThread(); // 确保JNIEnv已准备完毕
    env->CallStaticVoidMethod(g_vsync_waiter_class->obj(), // Embedder中的FlutterJNI实例
                       g_async_wait_for_vsync_method_,  // 对应的Embedder方法
                       java_baton);  // Vsync到达后通过该参数调用本对象
  });
}

以上逻辑将在Java侧调用(在Platform线程中),这是因为NDK中没有监听硬件信号Vsync的API,而Android SDK中有。注意,java_baton是当前对象的弱引用指针,将用于Vsync信号到达时触发对应的回调。

该逻辑将调用Java侧的FlutterJNI对象的asyncWaitForVsync方法,最终将调用AsyncWaitForVsyncDelegateasyncWaitForVsync方法。由代码清单4-3可知,该对象在启动时已经完成注册,具体逻辑如代码清单5-31所示。

// 代码清单5-31 engine/shell/platform/android/io/flutter/view/VsyncWaiter.java
private final FlutterJNI.AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate =
  new FlutterJNI.AsyncWaitForVsyncDelegate() {
   
    
    public void asyncWaitForVsync(long cookie) {
    // cookie即代码清单5-30中的java_baton
      Choreographer.getInstance().postFrameCallback( // 为下一个Vsync信号注册回调
        new Choreographer.FrameCallback() {
   
          
          public void doFrame(long frameTimeNanos) {
    // Vsync到达时触发
            float fps = windowManager.getDefaultDisplay().getRefreshRate(); // 设备帧率
            long refreshPeriodNanos = (long) (1000000000.0 / fps); // 渲染一帧的最大耗时
            FlutterJNI.nativeOnVsync( // 调用Engine的方法,见代码清单5-32
                frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);
          }
        }
      );
    }
  };

以上逻辑主要是调用系统API,该API将在下一个Vsync信号到达时调用doFrame方法,其中,由frameTimeNanos表示的Vsync信号到达的时间会稍稍早于该回调发生的时间,因为从Vsync信号到达到doFrame被调用,中间必然有一些逻辑耗时。接着通过系统API获取当前设备的帧率,并计算绘制一帧所需要的时间,对fps60的设备而言,绘制一帧需要16.6ms。最后将通过FlutterJNI调用Native方法,开始进行Vsync的响应,其中,第1个参数是Vsync信号到达时间;第2个参数表示当前帧最晚完成绘制的时间,它将贯穿整个渲染流程;第3个参数表示Flutter Engine中响应该信号的对象的指针。

Vsync 响应阶段

下面开始分析nativeOnVsync方法对应的Engine中的逻辑,如代码清单5-32所示。

// 代码清单5-32 engine/shell/platform/android/vsync_waiter_android.cc
void VsyncWaiterAndroid::OnNativeVsync( ...... ) {
   
  TRACE_EVENT0("flutter", "VSYNC");
  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);
}
void VsyncWaiterAndroid::ConsumePendingCallback( ...... ) {
   
  auto* weak_this = reinterpret_cast<std::weak_ptr<VsyncWaiter>*>(java_baton);
  auto shared_this = weak_this->lock(); // 获取代码清单5-30中发起监听的VsyncWaiter实例
  delete weak_this;
  if (shared_this) {
    // 触发回调,以上由具体平台实现,以下是VsyncWaiter通用逻辑
    shared_this->FireCallback(frame_start_time, frame_target_time);
  }
}

以上逻辑首先提取frame_timetarget_time,其含义前面内容已解释过。其次调用ConsumePendingCallback,将java_baton还原成进行注册的实例,并调用其Callback,具体逻辑如代码清单5-33所示。

// 代码清单5-33 engine/shell/common/vsync_waiter.cc
void VsyncWaiter::FireCallback( ...... ) {
   
  Callback callback;
  fml::closure secondary_callback;
  {
   
    std::scoped_lock lock(callback_mutex_);
    callback = std::move(callback_); // callback_的赋值逻辑位于代码清单5-29
    secondary_callback = std::move(secondary_callback_);
  }
  if (!callback && !secondary_callback) {
    return; } // 没有回调,返回
  if (callback) {
   
    auto flow_identifier = fml::tracing::TraceNonce();
    task_runners_.GetUITaskRunner()->PostTaskForTime([ ...... ]() {
   
          callback(frame_start_time, frame_target_time); // 触发:见代码清单5-28
        }, frame_start_time);
  }
  if (secondary_callback) {
   
    task_runners_.GetUITaskRunner()->PostTaskForTime(
           std::move(secondary_callback), frame_start_time);
  }
}

以上逻辑提取callback_secondary_callback_,如果都为null则说明没有任何响应逻辑;如果有回调则在UI线程依次调用。对callback_字段而言,其赋值在代码清单5-28中,其BeginFrame方法如代码清单5-34所示。

// 代码清单5-34 engine/shell/common/animator.cc
void Animator::BeginFrame( ...... ) {
   
  // SKIP 第1步,Trace & Timeline存储
  frame_scheduled_ = false; // 当前不处于等待Vsync信号的状态
  notify_idle_task_id_++; // idle(空闲状态)的计数id,每帧递增,作用见后面内容
  regenerate_layer_tree_ = false; // 默认不重新生产layer_tree
  pending_frame_semaphore_.Signal(); // 释放信号,允许接收新的Vsync信号请求,见代码清单5-27
  if (!producer_continuation_) {
    // 第2步,产生一个待渲染帧,详见5.2.5节
    producer_continuation_ = layer_tree_pipeline_->Produce(); // 见代码清单5-40
    if (!producer_continuation_) {
    // 当前待渲染帧的队列已满
      RequestFrame(); // 重新注册Vsync,在下一帧尝试加入队列,见代码清单5-27
      return;
    }
  } // 第3步,重要属性的存储
  last_frame_begin_time_ = fml::TimePoint::Now(); // 帧渲染实际开始时间
  last_vsync_start_time_ = vsync_start_time; // Vsync信号通知的开始时间
  last_frame_target_time_ = frame_target_time; // 当前帧完成渲染的最晚时间
  dart_frame_deadline_ = FxlToDartOrEarlier(frame_target_time); // Dart VM的当前时间戳
  {
    // 第4步,开始渲染
    delegate_.OnAnimatorBeginFrame(frame_target_time); // 见代码清单5-35
  }
  if (!frame_scheduled_) {
    // 第5步,在UI线程注册任务,用于通知Dart VM当前空闲,可以进行GC等操作
    task_runners_.GetUITaskRunner()->PostDelayedTask([ ...... ]() {
   
      if (!self) {
    return; } 
      if (notify_idle_task_id == self->notify_idle_task_id_ && // 没有正在渲染的帧
          !self->frame_scheduled_) {
    // 不等待Vsync信号(准备渲染) 
        self->delegate_.OnAnimatorNotifyIdle(Dart_TimelineGetMicros() + 100000);
      } // 以上判断的核心是保证当前确实处于空闲状态
    }, kNotifyIdleTaskWaitTime);
  }
}

以上逻辑相对来说比较复杂,主要分为5步。第1步和第3步主要是一些重要属性的存储,代码中已有说明。第2步涉及一个复杂的设计,将在后面单独分析。第4步将触发Framework的渲染逻辑,这部分内容会在后面详细分析。第5步将在当前帧的渲染完成之后,在UI线程注册一个任务,同样是用于通知Dart VM当前处于空闲状态,可以进行GC等操作。这是非常有必要的,因为一帧绘制完成后Framework层会有大量对象的创建与销毁。该任务发出后能够执行的条件是当前没有新的帧待渲染(即保证渲染的优先级始终高于GC),具体表现为代码中的两个条件。

  • notify_idle_task_id == self->notify_idle_task_id_:说明当前没有正在渲染的帧,否则后者会自增。

  • !self->frame_scheduled_:说明没有正在等待Vsync信号的帧,否则该属性为true

下面开始分析渲染的逻辑。OnAnimatorBeginFrame方法将经由Shell、Engine、Runtime-Controller最终调用PlatformConfigurationBeginFrame方法,如代码清单5-35所示。

// 代码清单5-35 engine/lib/ui/window/platform_configuration.cc
void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime) {
    
// 完成渲染的最晚时间
  std::shared_ptr<tonic::DartState> dart_state = begin_frame_.dart_state().lock();
  if (!dart_state) {
    return; }
  tonic::DartState::Scope scope(dart_state);
  int64_t microseconds = (frameTime - fml::TimePoint()).ToMicroseconds();
  tonic::LogIfError( tonic::DartInvoke(begin_frame_.Get(), // 见代码清单5-25
        {
    Dart_NewInteger(microseconds),})
  );
  UIDartState::Current()->FlushMicrotasksNow(); // 处理微任务
  tonic::LogIfError(tonic::DartInvokeVoid(draw_frame_.Get())); // 见代码清单5-25
}

以上逻辑将调用Framework的window.onBeginFramewindow.onDrawFrame方法,并在中间调用FlushMicrotasksNow方法以处理Dart VM的微任务。由此可以推断,帧渲染的逻辑将主要由Framework执行,下面开始详细分析。

Framework 响应阶段

由代码清单5-25可知,window.onBeginFramewindow.onDrawFrame接口所绑定的Dart 函数分别是handleBeginFrame方法和handleDrawFrame方法。前者逻辑如代码清单5-36所示。

// 代码清单5-36 flutter/packages/flutter/lib/src/scheduler/binding.dart
void handleBeginFrame(Duration? rawTimeStamp) {
   
  Timeline.startSync('Frame'
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值