36

Flutter 性能优化的利器 —— Tracing

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzAxNDEwNjk5OQ%3D%3D&%3Bmid=2650410811&%3Bidx=1&%3Bsn=a250a3cd97cb26c3c6d1e9d77b90cd91
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

640?wx_fmt=gif

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

Tracing 顾名思义 —— 追踪每段耗时分布。

背景

640?wx_fmt=png

上面这张图是 Flutter Engine 初始化过程中的一部分流程,非常直观的反应了执行流程中每个阶段的耗时分布。

Tracing 是 Chrome 开发者工具中强大的性能分析工具之一,它能记录 Chrome 所有进程间的各种活动。例如能记录每个进程中每个线程里 C++ 或者 JavaScript 方法的调用栈/耗时,不仅仅如此,还能看到视图 Layer 之间的层级关系,相关文档介绍  The Trace Event Profiling Tool (about:tracing)。

本文会专注在 Flutter Engine 中 Tracing 原理与实践,会分为原理篇与实践篇,原理篇会涉及到具体实现,实践篇主要包括如何使用、分析、定制。

:warning::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

<span>char* flags_error = Dart_SetVMFlags(args.size(), args.data());</span>

最终调用方法:

/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 个。

:warning::如果为 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

path/to/engine/flutter/fml/trace_event.h

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 添加参数实现,示例如下:

640?wx_fmt=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 默认就能进行查看,按如下步骤:

  1. 拿到启动后的 Observatory 地址。

  2. 通过 flutter attach --debug-uri=observatory_url attach 到对应的服务,会生成一个 debugger/profiler 地址。

  3. 打开 debugger/profiler 地址后就是 Fluuter 默认的 DevTool 工具,点击 timeline 按钮即可打开 Tracing 内容。

分析 Tracing 文件

关于 Tracing 工具的使用可以查看相关 Chrome 文档,  The Trace Event Profiling Tool (about:tracing)。

展示的信息比较直观,对于启动性能分析,能非常直观的看到各个部分的耗时情况,下图是 Flutter 启动时 iOS 上的各个耗时阶段的大致分布,图的左边,可以看到各个阶段执行对应的线程。

640?wx_fmt=png

    Debug 参数

上面介绍了如何获取 Tracing 的方法,生成的 Tracing 耗时分布主要包括各个阶段的耗时,但是还并不是包含所有的阶段,介绍两个有用的 Debug 参数,其他相关参数参考文档  Debug flags: performance - Flutter

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

<span>bool debugProfileBuildsEnabled = false;</span>

这个参数会在 Widget Build 阶段,显示所有 Widget 节点 Build 时的遍历情况,可以根据这些信息查看是否有无用的节点 Build。

640?wx_fmt=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 上,如下图:

640?wx_fmt=png

后记

本文主要分析了 Tracing 在 Flutter 上的实现以及一些实践,Tracing 是 Chrome 实现的一种 标准格式 ,任何技术栈的性能分析都可以生成这种标准格式,然后利用现成的 Chrome DevTool 工具打开即可分析,非常直观,能启到事半功倍的效果。

链接:

[The Trace Event Profiling Tool]

https://www.chromium.org/developers/how-tos/trace-event-profiling-tool

[Using the Timeline view]

https://flutter.dev/docs/development/tools/devtools/timeline

[Measuring app startup time]

https://flutter.dev/docs/testing/debugging?#measuring-app-startup-time

[flutter tool]

https://github.com/flutter/flutter/wiki/The-flutter-tool

[Understanding Systrace]

https://source.android.com/devices/tech/debug/systrace

[Overview of system tracing]

https://developer.android.com/topic/performance/tracing

[Debug flags: performance]

https://flutter.dev/docs/testing/code-debugging?#debug-flags-performance

[Tracing 格式标准]

https://docs.google.com/document/u/0/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/mobilebasic

手淘客户端-小程序与跨平台技术团队

欢迎大家加入手淘客户端-小程序与跨平台技术团队!

我们是广泛支撑淘系业务的基础团队,团队正在维护的有手淘 H5 容器、Weex、淘宝小程序容器、淘宝首页都在用的动态化模板容器、还有正在做的 Flutter !淘系的基础架构、中间件、平台支撑是隔壁的兄弟团队,这里有丰富的业务场景和技术生态,机会与挑战并存,欢迎优秀的小伙伴加入,一起为新零售业务打造稳定的底盘!简历投递: [email protected]

✿  拓展阅读

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

作者| 来斌(来一)

编辑| 橙子君

出品| 阿里巴巴新零售淘系技术

640?wx_fmt=jpeg

640?wx_fmt=png


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK