Flutter 性能优化的利器 —— Tracing

简介: 对于任何技术栈,都会有一个绕不过去的坎,那就是性能优化,而对于如何进行性能优化,最重要的前提就是需要知道具体的耗时分布,要知道耗时分布,就得打点(时间戳),一般的性能打点都是一些散点,比较凌乱,而本文要讲的 Tracing 则是性能打点的一种非常优雅的实现,它以瀑布流的形式呈现,非常直观,还有一个更直观的名字叫 火焰图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

相关文章
|
1月前
|
Web App开发 缓存 前端开发
【Flutter前端技术开发专栏】Flutter中的性能优化与内存管理
【4月更文挑战第30天】本文探讨了Flutter应用的性能优化和内存管理。关键点包括:减少布局重绘(使用`const`构造函数和最小化依赖),选择合适的动画实现,懒加载和按需加载以提升性能。同时,强调了避免内存泄漏和优化内存使用,利用Flutter提供的性能分析工具。实践案例展示了如何优化ListView,包括使用`ListView.builder`和缓存策略。通过这些方法,开发者可以提升应用的响应性、流畅性和稳定性。
【Flutter前端技术开发专栏】Flutter中的性能优化与内存管理
|
1月前
|
存储 缓存 监控
【Flutter前端技术开发专栏】Flutter中的列表滚动性能优化
【4月更文挑战第30天】本文探讨了Flutter中优化列表滚动性能的策略。建议使用`ListView.builder`以节省内存,避免一次性渲染所有列表项。为防止列表项重建,可使用`UniqueKey`或`ObjectKey`。缓存已渲染项、减少不必要的重绘和异步加载大数据集也是关键。此外,选择轻量级组件,如`StatelessWidget`,并利用Flutter DevTools监控性能以识别和解决瓶颈。持续测试和调整以提升用户体验。
【Flutter前端技术开发专栏】Flutter中的列表滚动性能优化
|
1月前
|
前端开发 数据处理
【Flutter 前端技术开发专栏】Flutter 中的滚动性能优化与无限列表实现
【4月更文挑战第30天】本文探讨了 Flutter 中的滚动性能优化和无限列表实现。关键点包括:1) 滚动性能直接影响用户满意度,优化可提升响应速度;2) 影响因素有布局复杂度、频繁重绘和数据处理;3) 优化措施包括懒加载、简化布局、减少不必要的重绘和高效处理数据;4) 无限列表通过监听滚动位置,动态加载新数据;5) 实现时注意加载策略、数据处理效率和内存管理。案例分析和总结强调了优化在实际开发中的重要性。
【Flutter 前端技术开发专栏】Flutter 中的滚动性能优化与无限列表实现
|
1月前
|
设计模式 缓存 前端开发
【Flutter前端技术开发专栏】Flutter开发最佳实践与性能优化指南
【4月更文挑战第30天】本文为Flutter开发者提供了最佳实践和性能优化指南。建议采用MVC、Provider/Repository和InheritedWidget设计模式,注重代码结构和状态管理。遵循最佳实践,如避免全局变量,智能使用Stateful和Stateless Widgets,以及利用`const`和`final`。性能优化要点包括减少渲染次数、优化图片资源、异步处理和使用Profile模式分析。通过测试确保应用质量与稳定性,打造高性能、高质
【Flutter前端技术开发专栏】Flutter开发最佳实践与性能优化指南
|
1月前
|
JSON Dart UED
Flutter 应用程序性能优化建议
Flutter应用程序默认已经具有良好的性能,因此您只需要避免常见的陷阱,就可以获得出色的性能。 您设计和实现应用程序的用户界面的方式可能会对其运行效率产生重大影响。 本文这些最佳实践建议将帮助您编写性能最佳的Flutter应用程序。
Flutter 应用程序性能优化建议
|
11月前
|
Dart 监控 算法
Flutter性能优化分析
使用工具来分析Flutter的性能瓶颈
388 0
|
前端开发
关于flutter列表的性能优化,你必须要了解的
这里是坚果前端小课堂,大家喜欢的话,可以关注我的公众号“坚果前端,”,或者加我好友,获取更多精彩内容 嵌套列表 - ShrinkWrap 与 Slivers
584 0
关于flutter列表的性能优化,你必须要了解的
|
存储 缓存 Dart
深入探索Flutter性能优化
# 目录 - 一、检测手段 - 1、Flutter Inspector - 2、性能图层 - 3、Raster 线程问题 - 4、UI 线程问题定位 - 5、检查多视图叠加的视图渲染开关 checkerboardOffscreenLayers - 6、检查缓存的图像开关 checkerboardRasterCacheImages - 二、关键优化指
1119 0
|
前端开发
flutter 中的列表的性能优化前奏
这里是坚果前端小课堂,大家喜欢的话,可以关注我的公众号“坚果前端,”,或者加我好友,获取更多精彩内容 嵌套列表 - ShrinkWrap 与 Slivers 使用 ShrinkWrap 的列表列表 下面是一些使用ListView对象呈现列表列表的代码,内部列表的shrinkWrap值设置为 true。shrinkWrap强行评估整个内部列表,允许它请求有限的高度,而不是通常的ListView对象高度,即无穷大!
235 0
flutter 中的列表的性能优化前奏
flutter中的列表的性能优化
我们接着上一期的继续今天的 内容 使用 Slivers 的列表列表 下面的代码构建了与之前相同的 UI,但这次它使用Slivers 而不是收缩包装ListView对象。本页的其余部分将引导您逐步完成更改。
366 0