開發與維運

Flutter 性能優化的利器 —— Tracing

滾動.gif

對於任何技術棧,都會有一個繞不過去的坎,那就是性能優化,而對於如何進行性能優化,最重要的前提就是需要知道具體的耗時分佈,要知道耗時分佈,就得打點(時間戳),一般的性能打點都是一些散點,比較凌亂,而本文要講的 Tracing 則是性能打點的一種非常優雅的實現,它以瀑布流的形式呈現,非常直觀,還有一個更直觀的名字叫 火焰圖

Tracing 顧名思義 —— 追蹤每段耗時分佈。

背景

image.png

上面這張圖是 Flutter Engine 初始化過程中的一部分流程,非常直觀的反應了執行流程中每個階段的耗時分佈。

Tracing 是 Chrome 開發者工具中強大的性能分析工具之一,它能記錄 Chrome 所有進程間的各種活動。例如能記錄每個進程中每個線程裡 C++ 或者 JavaScript 方法的調用棧/耗時,不僅僅如此,還能看到視圖 Layer 之間的層級關係,相關文檔介紹 The Trace Event Profiling Tool (about:tracing)

本文會專注在 Flutter Engine 中 Tracing 原理與實踐,會分為原理篇與實踐篇,原理篇會涉及到具體實現,實踐篇主要包括如何使用、分析、定製。

⚠️:Flutter 中用 Timeline 這個詞代替了 Tracing,Flutter Devtool 也提供了 Timeline 工具(展示的就是 Tracing 結構的信息)。這兩個詞是一個對等的概念,下文提到的 Timeline 可以和 Tracing 對等。

原理篇

整個 Timeline 的過程主要包括初始化 Timeline 與記錄 Tracing 信息兩個部分。

▐ 初始化 Timeline

初始化 Timeline 包括四個過程:註冊 Flag、設置 Flag、TimelineStream 初始化、Timeline 初始化。
註冊 Flag

Flutter 中會註冊非常多的 Flag 用於各種功能標記,對於 Timeline/Tracing 功能就是 timeline_streams 標實,具體如下:

/path/to/engine/src/third_party/dart/runtime/vm/timeline.cc

// 執行宏定義
DEFINE_FLAG(charp,
            timeline_streams,
            NULL,
            "Comma separated list of timeline streams to record. "
            "Valid values: all, API, Compiler, CompilerVerbose, Dart, "
            "Debugger, Embedder, GC, Isolate, and VM.");

// 展開後:
charp FLAG_timeline_streams = Flags::Register_charp(&FLAG_timeline_streams, 'timeline_streams', NULL, "Comma separated list of timeline streams to record. "
            "Valid values: all, API, Compiler, CompilerVerbose, Dart, "
            "Debugger, Embedder, GC, Isolate, and VM.");

其中 charp 為 typedef const char* charp;

真正執行的函數如下:

/path/to/engine/src/third_party/dart/runtime/vm/flags.cc

const char* Flags::Register_charp(charp* addr,
                                  const char* name,
                                  const char* default_value,
                                  const char* comment) {
  ASSERT(Lookup(name) == NULL);
  Flag* flag = new Flag(name, comment, addr, Flag::kString);
  AddFlag(flag);
  return default_value;
}

其中 addr_ 是一個 union 成員,初始值為當前註冊函數的默認值為 NULL,即 FLAG_timeline_streams 初始值為 NULL。

註冊 Flag 的過程就是定義了 FLAG_timeline_streams 標記。

設置 Flag

在 Flutter Engine 初始化的過程中,可以進行 DartVm 參數的透傳,例如 —trace-startup,這個參數就可以記錄啟動時 Tracing 信息,會由如下方法進行設置:

path/to/engine/src/flutter/runtime/dart_vm.cc

char* flags_error = Dart_SetVMFlags(args.size(), args.data());

最終調用方法:

/path/to/engine/src/third_party/dart/runtime/vm/flags.cc

char* Flags::ProcessCommandLineFlags(int number_of_vm_flags,
                                     const char** vm_flags) {
...
while ((i < number_of_vm_flags) &&
         IsValidFlag(vm_flags[i], kPrefix, kPrefixLen)) {
    const char* option = vm_flags[i] + kPrefixLen;
    Parse(option);
    i++;
}
...
}

這裡主要會進行 Flag 的有效性驗證,關鍵步驟為 Parse 方法中的 SetFlagFromString

bool Flags::SetFlagFromString(Flag* flag, const char* argument) {
  ASSERT(!flag->IsUnrecognized());
  switch (flag->type_) {
    ...
    case Flag::kString: {
      *flag->charp_ptr_ = argument == NULL ? NULL : strdup(argument);
      break;
    }
    ....
  }
  flag->changed_ = true;
  return true;
}

會針對不同 Flag Type 設置不同變量,而這些變量是一個 union 結構體,如下:

union {
    void* addr_;
    bool* bool_ptr_;
    int* int_ptr_;
    uint64_t* uint64_ptr_;
    charp* charp_ptr_;
    FlagHandler flag_handler_;
    OptionHandler option_handler_;
}

根據 union 的特性,針對不同的 Flag Type,會得到不同值類型,可見之前定義的 FLAG_timeline_streams 值最終就會設置成透傳的值。例如 —trace_startup 對應的值為 Compiler,Dart,Debugger,Embedder,GC,Isolate,VM。

設置 Flag 的過程就是具體設置了之前定義的 FLAG_timeline_streams 值。

TimelineStream 初始化

在 FLAG_timeline_streams 中非常多的類型值,每種都定義了不同的 Stream,初始化過程包括三個步驟:Declare Stream(申明)、Get Stream(獲取)、Define Stream(定義)。

✎ Declare Stream

/path/to/engine/src/third_party/dart/runtime/vm/timeline.h

// stream 申明
#define TIMELINE_STREAM_DECLARE(name, fuchsia_name)                            \
  static TimelineStream stream_##name##_;
  TIMELINE_STREAM_LIST(TIMELINE_STREAM_DECLARE)
#undef TIMELINE_STREAM_DECLARE

// 展開後
static TimelineStream stream_API_;
static TimelineStream stream_Compiler_;
static TimelineStream stream_Dart_;
static TimelineStream stream_Embedder_;
....

Flutter Engine 中的 Timeline 信息為 stream_Embedder_,其它的 Timeline 也包括 Dart 層、API 層等等,本文主要會關注在 stream_Embedder_。

✎ Get Stream

/path/to/engine/src/third_party/dart/runtime/vm/timeline.h

// 獲取 Stream
#define TIMELINE_STREAM_ACCESSOR(name, fuchsia_name)                           \
  static TimelineStream* Get##name##Stream() { return &stream_##name##_; }
  TIMELINE_STREAM_LIST(TIMELINE_STREAM_ACCESSOR)
#undef TIMELINE_STREAM_ACCESSOR

// 展開後
static TimelineStream* GetAPIStream() { return &stream_API_; }
static TimelineStream* GetDartStream() { return &stream_Dart_; }
static TimelineStream* GetEmbedderStream() { return &stream_Embedder_; }
...

設置了相應的靜態獲取方法。

Define Stream

/path/to/engine/src/third_party/dart/runtime/vm/timeline.cc

#define TIMELINE_STREAM_DEFINE(name, fuchsia_name)                             \
  TimelineStream Timeline::stream_##name##_(#name, fuchsia_name, false);
TIMELINE_STREAM_LIST(TIMELINE_STREAM_DEFINE)
#undef TIMELINE_STREAM_DEFINE

// 展開後
TimelineStream Timeline::stream_API_("API", "dart:api", false);
TimelineStream Timeline::stream_Dart_("Dart", "dart:dart", false);
TimelineStream Timeline::stream_Embedder_("Embedder", "dart:embedder", false);
...

Timeline 初始化

void Timeline::Init() {
  ASSERT(recorder_ == NULL);
  recorder_ = CreateTimelineRecorder();
  ASSERT(recorder_ != NULL);
  enabled_streams_ = GetEnabledByDefaultTimelineStreams();
// Global overrides.
#define TIMELINE_STREAM_FLAG_DEFAULT(name, fuchsia_name)                       \
  stream_##name##_.set_enabled(HasStream(enabled_streams_, #name));
  TIMELINE_STREAM_LIST(TIMELINE_STREAM_FLAG_DEFAULT)
#undef TIMELINE_STREAM_FLAG_DEFAULT
}

1、通過 CreateTimelineRecorder 創建 TimelineEventRecorder,如果需要獲取啟動 Tracing 信息會創建 TimelineEventEndlessRecorder,會記錄無上限的 Trace 信息。
2、設置剛才創建的一系列 TimelineStream 實例的 set_enable 函數,後續在進行 Timeline 記錄的時候都會查詢是否 enable。

▐ 記錄 Timeline 信息

上一部分主要講了 Timeline 初始化準備的各種信息變量,這部分主要會講記錄 Tracing 信息的過程。

記錄 Tracing 信息有非常多的調用方法,包括記錄同步事件(TRACE_EVENT)、異步事件(TRACE_EVENT_ASYNC)、事件流(TRACE_FLOW_)。以下講同步事件的調用過程,其他事件整個流程基本類似。

同步事件包括 TRACE_EVENT0 、TRACE_EVENT1、TRACE_EVENT2 等,以 TRACE_EVENT0 調用為例:

{
    TRACE_EVENT0("flutter", "Shell::CreateWithSnapshots");
}

// 展開後
::fml::tracing::TraceEvent0("flutter", "Shell::CreateWithSnapshots");
::fml::tracing::ScopedInstantEnd __trace_end___LINE__("Shell::CreateWithSnapshots");

主要包括兩個部分:

  • 記錄階段 TraceEvent0,記錄當前信息
  • 標記結束 ScopedInstantEnd ,一般在作用域析構時調用

TraceEvent0

TraceEvent0 最終會調用如下方法:

path/to/engine/src/third_party/dart/runtime/vm/dart_api_impl.cc

DART_EXPORT void Dart_TimelineEvent(const char* label,
                                    int64_t timestamp0,
                                    int64_t timestamp1_or_async_id,
                                    Dart_Timeline_Event_Type type,
                                    intptr_t argument_count,
                                    const char** argument_names,
                                    const char** argument_values) {
...
TimelineStream* stream = Timeline::GetEmbedderStream();
  ASSERT(stream != NULL);
  TimelineEvent* event = stream->StartEvent();
...
switch (type) {
    case Dart_Timeline_Event_Begin:
      event->Begin(label, timestamp0);
      break;
    case Dart_Timeline_Event_End:
      event->End(label, timestamp0);
      break;
...
  }
...
event->Complete();
}

整個過程主要包括四個階段:

  • TimelineStream::StartEvent:生成 TimelineEvent,其中Timeline::GetEmbedderStream() 即為初始化階段的 stream_Embedder_。
  • TimelineEvent::Begin/End:記錄起始、結束的時間等信息
  • TimelineEvent::Complete:完成當前記錄
  • TimelineEventBlock::Finish:上報記錄的信息

✎ TimelineStream::StartEvent

stream->StartEvent() 最終會調用如下方法產生 TimelineEvent:

/path/to/engine/src/third_party/dart/runtime/vm/timeline.cc

TimelineEvent* TimelineEventRecorder::ThreadBlockStartEvent() {
  // Grab the current thread.
  OSThread* thread = OSThread::Current();
  ASSERT(thread != NULL);
  Mutex* thread_block_lock = thread->timeline_block_lock();
...
  thread_block_lock->Lock(); // 會一直持有,直到調用 CompleteEvent()
...
  TimelineEventBlock* thread_block = thread->timeline_block();

  if ((thread_block != NULL) && thread_block->IsFull()) {
    MutexLocker ml(&lock_);
    // Thread has a block and it is full:
    // 1) Mark it as finished.
    thread_block->Finish();
    // 2) Allocate a new block.
    thread_block = GetNewBlockLocked();
    thread->set_timeline_block(thread_block);
  } else if (thread_block == NULL) {
    MutexLocker ml(&lock_);
    // Thread has no block. Attempt to allocate one.
    thread_block = GetNewBlockLocked();
    thread->set_timeline_block(thread_block);
  }
  if (thread_block != NULL) {
    // NOTE: We are exiting this function with the thread's block lock held.
    ASSERT(!thread_block->IsFull());
    TimelineEvent* event = thread_block->StartEvent();
    return event;
  }
....
  thread_block_lock->Unlock();
  return NULL;
}

1、首先會調用線程鎖,一直持有本次記錄過程,直到調用 CompleteEvent()。
2、如果沒有 TimelineEventBlock ,則首先會創建一個,並記錄在當前線程中。
3、如果 TimelineEventBlock 滿了,會先 Finish (見下文分析),再創建一個新的,並記錄。
4、最後都會在 TimelineEventBlock 中創建一個新的 TimelineEvent,每個 TimelineEventBlock 創建的 TimelineEvent 會有數量限制,最多為 64 個。

⚠️:如果為 TimelineEventEndlessRecorder,則會無限創建 TimelineEventBlock,否則會有數量限制。

✎ TimelineEvent::Begin/End

/path/to/engine/src/third_party/dart/runtime/vm/timeline.cc

void TimelineEvent::Begin(const char* label,
                          int64_t micros,
                          int64_t thread_micros) {
  Init(kBegin, label);
  set_timestamp0(micros);
  set_thread_timestamp0(thread_micros);
}

這些階段主要是記錄具體的信息,包括:
1、Init: 記錄事件標籤名,事情類型(kBegin,kEnd),End 一般會在作用域析構時調用(下面會分析)。
2、micros: 記錄系統啟動後運行的時間戳。

3、thread_micros: 記錄該線程CPU運行的時間戳。

✎ TimelineEvent::Complete

最終調用方法如下:

/path/to/engine/src/third_party/dart/runtime/vm/timeline.cc

void TimelineEventRecorder::ThreadBlockCompleteEvent(TimelineEvent* event) {
...
   // Grab the current thread.
  OSThread* thread = OSThread::Current();
  ASSERT(thread != NULL);
  // Unlock the thread's block lock.
  Mutex* thread_block_lock = thread->timeline_block_lock();
...
  thread_block_lock->Unlock();
}

一次記錄結束後會調用 Complete 方法,並最終會釋放一開始 Lock 的同步鎖。

✎ TimelineEventBlock::Finish

在 TimelineStream::StartEvent 中創建的TimelineEventBlock 提到,默認最多是 64 個,滿了之後會調用 Finsih 方法。

void TimelineEventBlock::Finish() {
...
  in_use_ = false;
#ifndef PRODUCT
  if (Service::timeline_stream.enabled()) {
    ServiceEvent service_event(NULL, ServiceEvent::kTimelineEvents);
    service_event.set_timeline_event_block(this);
    Service::HandleEvent(&service_event);
  }
#endif
}

最終會將事件信息發送給 ServiceIsolate 來處理,關於 ServiceIsolate 簡單可以理解為後端服務,是由 Dart VM 初始化的時候創建的, DevTool 顯示的信息(包括 Tracing 信息)都會和 ServiceIsolate 通信獲取。

ScopedInstantEnd

class ScopedInstantEnd {
 public:
  ScopedInstantEnd(const char* str) : label_(str) {}
  ~ScopedInstantEnd() { TraceEventEnd(label_); }
 private:
  const char* label_;
  FML_DISALLOW_COPY_AND_ASSIGN(ScopedInstantEnd);
};

可以看到析構函數中會調用 TraceEventEnd 方法,也就是說離開了作用域就會調用 TraceEventEnd 方法,而 TraceEventEnd 方法最終調用的就是 TimelineEvent::End 階段進行信息記錄。

以上就是整體的 Tracing 信息的路由過程,實現上使用了大量的宏,宏在開發階段還是方便實現,不過對於閱讀源碼來說會有一定的障礙,不能直觀的進行代碼搜索查找。

實踐篇

主要介紹 Timeline 的使用、啟動性能分析、有用的 Debug 參數介紹、以及添加自定義 Tracing 節點。

▐ Timeline 使用

Timeline 的使用在官方文檔中已經有詳細的說明,Using the Timeline view - Flutter 直接看文檔即可。

▐ 啟動性能分析

Timeline 工具僅僅只能分析 Flutter 頁面啟動之後的運行時情況,整個 Flutter 的啟動過程完全是無法分析的,而啟動/初始化過程也是比較關鍵的一環。

對於啟動性能分析,官方文檔描述甚少,目前只發現了這一處,Measuring app startup time - Flutter

啟動性能分析包括三個步驟:添加啟動性能參數、獲取 Tracing 信息、分析。

添加啟動參數

只有添加了特定的參數後才能獲取啟動時 Tracing 信息。

✎ Flutter App 場景

flutter run --trace-startup --profile

主要是通過 flutter cli 命令行參數運行 Flutter App,最終會在當前目錄下生成 build/start_up_info.json 文件。

可惜的是這個文件只產出了四個關鍵的 Timestamp,遠遠達不到能夠分析的地步,跟進 Flutter Tools 源碼後,關鍵源碼如下:

path/to/flutter/packages/flutter_tools/lib/src/tracing.dart

/// Download the startup trace information from the given observatory client and
/// store it to build/start_up_info.json.
Future<void> downloadStartupTrace(VMService observatory, { bool awaitFirstFrame = true }) async {
    final Tracing tracing = Tracing(observatory);

  final Map<String, dynamic> timeline = await tracing.stopTracingAndDownloadTimeline(
    awaitFirstFrame: awaitFirstFrame,
  );
......
 final Map<String, dynamic> traceInfo = <String, dynamic>{
    'engineEnterTimestampMicros': engineEnterTimestampMicros,
  };
......
    traceInfo['timeToFrameworkInitMicros'] = timeToFrameworkInitMicros;
......
    traceInfo['timeToFirstFrameRasterizedMicros'] = firstFrameRasterizedTimestampMicros - engineEnterTimestampMicros;
......
    traceInfo['timeToFirstFrameMicros'] = timeToFirstFrameMicros;
......
  traceInfo['timeAfterFrameworkInitMicros'] = firstFrameBuiltTimestampMicros - frameworkInitTimestampMicros;
......
traceInfoFile.writeAsStringSync(toPrettyJson(traceInfo));
}

可以看到關鍵的四個 Timestamp 被保存在 Map 進行輸出到文件,最關鍵的一點是整個 timeline 數據其實都已經拿到了,於是可以進行如下改造:

/// Download the startup trace information from the given observatory client and
/// store it to build/start_up_info.json.
Future<void> downloadStartupTrace(VMService observatory, { bool awaitFirstFrame = true }) async {
    final Tracing tracing = Tracing(observatory);

  final Map<String, dynamic> timeline = await tracing.stopTracingAndDownloadTimeline(
    awaitFirstFrame: awaitFirstFrame,
  );
......
// 原來的 start_up_info.json 生成
    traceInfoFile.writeAsStringSync(toPrettyJson(traceInfo));
......
// 新增 start_up_trace_events.json 生成
    final String traceEventsFilePath = globals.fs.path.join(getBuildDirectory(), 'start_up_trace_events.json');
  final File traceEventsFile = globals.fs.file(traceEventsFilePath);
  final List<Map<String, dynamic>> events =
        List<Map<String, dynamic>>.from((timeline['traceEvents'] as List<dynamic>).cast<Map<String, dynamic>>());
  traceEventsFile.writeAsStringSync(toPrettyJson(events));
}

改造後會在當前目錄下生成 build/start_up_trace_events.json 文件,並通過 chrome://tracing 打開查看。有一個注意點,在改動 flutter tools 代碼後,需要重新生成 flutter command ,具體可以看文檔。The flutter tool · flutter/flutter Wiki · GitHub

上面這個場景對於整個 Flutter App 來講是完全可以進行啟動性能分析了,但是對於 Add to App 的場景還是無法滿足,因為這種場景無法通過 flutter cli 來進行參數透傳。

**✎ Add To App 場景
**
對於這種場景,需要通過 Platform 層去透傳參數。

Android

Android 側參數透傳方法如下:

path/to/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java

public FlutterEngine(
      @NonNull Context context,
      @NonNull FlutterLoader flutterLoader,
      @NonNull FlutterJNI flutterJNI,
      @NonNull PlatformViewsController platformViewsController,
      @Nullable String[] dartVmArgs,
      boolean automaticallyRegisterPlugins) {
......
}

通過實例化 FlutterEngine 時的構造參數 dartVmArgs 中添加 --trace-startup 即可。

new FlutterEngine(mPlatform.getApplication().getApplicationContext(),
                    FlutterLoader.getInstance(),new FlutterJNI(),new String[]{"--trace-startup"},true);

iOS

iOS 側通過源碼查看,對應的 FlutterEngine.mm 的構造參數中是沒有對應的 dartVmArgs 參數透傳。真正參數轉換的地方如下:

path/to/engine/src/flutter/shell/platform/darwin/common/command_line.mm

fml::CommandLine CommandLineFromNSProcessInfo() {
  std::vector<std::string> args_vector;
  for (NSString* arg in [NSProcessInfo processInfo].arguments) {
    args_vector.emplace_back(arg.UTF8String);
  }
  return fml::CommandLineFromIterators(args_vector.begin(), args_vector.end());
}

通過 [NSProcessInfo processInfo].arguments 拿的命令行參數,無法通過自定義加入參數實現,對於從 XCode 啟動 App 的可以通過編輯 schema 添加參數實現,示例如下:

image.png

但是絕大多數情況下,不會通過 XCode 來啟動 App,因此還是需要通過修改 Engine 代碼來實現參數傳遞。對此提了 PR 來支持 dartVm 參數的透傳。

path/to/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm

- (instancetype)initWithDartVmArgs:(nullable NSArray<NSString*>*)args {
  return [self initWithPrecompiledDartBundle:nil dartVmArgs:args];
}

初始化 FlutterEngine.mm 中可以通過如下方式初始化:

_dartProject = [[FlutterDartProject alloc] initWithPrecompiledDartBundle:dartBundle dartVmArgs:@[@"--trace-startup"]];
_engine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:_dartProject allowHeadlessExecution:YES];

Android Systrace

對於 Android 設備來講,還可以用 Android 獨有的 Systrace 來看,不需要改任何 Flutter 相關的參數。

相關參考文檔:
Understanding Systrace | Android Open Source Project
Overview of system tracing | Android Developers

獲取 Tracing 文件

添加了啟動參數之後,需要有工具進行查看,Flutter 默認提供的 DevTool 默認就能進行查看,按如下步驟:

拿到啟動後的 Observatory 地址。
通過 flutter attach --debug-uri=observatory_url attach 到對應的服務,會生成一個 debugger/profiler 地址。
打開 debugger/profiler 地址後就是 Fluuter 默認的 DevTool 工具,點擊 timeline 按鈕即可打開 Tracing 內容。

分析 Tracing 文件

關於 Tracing 工具的使用可以查看相關 Chrome 文檔, The Trace Event Profiling Tool (about:tracing)

展示的信息比較直觀,對於啟動性能分析,能非常直觀的看到各個部分的耗時情況,下圖是 Flutter 啟動時 iOS 上的各個耗時階段的大致分佈,圖的左邊,可以看到各個階段執行對應的線程。

image.png

▐ Debug 參數

上面介紹瞭如何獲取 Tracing 的方法,生成的 Tracing 耗時分佈主要包括各個階段的耗時,但是還並不是包含所有的階段,介紹兩個有用的 Debug 參數,其他相關參數參考文檔 [Debug flags: performance - Flutter
](鏈接地址https://flutter.dev/docs/testing/code-debugging?spm=ata.13261165.0.0.32ca24d41mrBFF#debug-flags-performance)
debugProfilePaintsEnabled

path/to/flutter/packages/flutter/lib/src/rendering/debug.dart

bool debugProfilePaintsEnabled = false;

這個參數會在渲染 Paint 階段,顯示所有 Paint 時節點的遍歷情況,可以根據這些信息查看是否有無用的節點 Paint

debugProfileBuildsEnabled

path/to/flutter/packages/flutter/lib/src/widgets/debug.dart

bool debugProfileBuildsEnabled = false;

這個參數會在 Widget Build 階段,顯示所有 Widget 節點 Build 時的遍歷情況,可以根據這些信息查看是否有無用的節點 Build。

image.png

上圖把 build、paint 階段的過程全都顯示出來了,有了這些信息後,還需要結合自身的業務邏輯分析 Widget Build/Paint 是否合理,是否執行了無用的操作,然後進行優化。

自定義 Tracing 節點

對於默認沒有打點的地方,如果自己需要查看其耗時,則可以自行進行打點。例如需要查看創建 IOSContext 的耗時,則可以進行如下打點:

std::unique_ptr<IOSContext> IOSContext::Create(IOSRenderingAPI rendering_api) {
  TRACE_EVENT0("flutter", "IOSContext::Create");
  ......
  FML_CHECK(false);
  return nullptr;
}

最終會反應在 Tracing 上,如下圖:

image.png

後記

本文主要分析了 Tracing 在 Flutter 上的實現以及一些實踐,Tracing 是 Chrome 實現的一種標準格式,任何技術棧的性能分析都可以生成這種標準格式,然後利用現成的 Chrome DevTool 工具打開即可分析,非常直觀,能啟到事半功倍的效果。

關注「淘系技術」微信公眾號,一個有溫度有內容的技術社區~

image.png

Leave a Reply

Your email address will not be published. Required fields are marked *