作者:字节挪动技术-李皓骅

摘要

本文介绍了 Flutter 多引擎下,应用 PlatformView 场景时不能绕开的一个线程合并问题,以及它最终的解决方案。最终 Pull Request 曾经 merge 到 Google 官网 Flutter 仓库:

https://github.com/flutter/en...

本文关键点:

  1. 线程合并,实际上指的并不是操作系统有什么高级接口,能够把两个 pthread 合起来,而是 flutter 引擎中的四大 Task Runner 里,用一个 Task Runner 同时生产解决两个 Task Queue 中排队的工作。
  2. 线程合并问题,指的是 Flutter 引擎四大线程(Platform 线程、UI 线程、Raster 线程、IO 线程)其中的 Platform 线程和 Raster 线程在应用 PlatformView 的场景时须要合并和拆散的问题。之前的官网的线程合并机制,只反对一对一的线程合并,但多引擎场景就须要一对多的合并和一些相干的配套逻辑。具体请看下文介绍。
  3. 对于 Flutter 引擎的四大 Task Runner 能够参考官网 wiki 中的 Flutter Engine 线程模型 : https://github.com/flutter/fl...
  4. 本文介绍的线程合并操作(也就实现了一个 looper 生产两个队列的音讯的成果),见如下的示意图,这样咱们能够有个初步的印象:

背景介绍

什么是 PlatformView?

首先,介绍下 PlatformView 是什么,其实它简略了解成——平台相干的 View 。也就是说,在Android 和 iOS 平台原生有这样的控件,然而在Flutter的跨平台控件库里没有实现过的一些Widget,这些控件咱们能够应用Flutter提供的PlatformView的机制,来做一个渲染和桥接,并且在下层能够用Flutter的办法去创立、管制这些原生View,来保障两端跨平台接口对立。

比方WebView,地图控件,第三方广告SDK等等这些场景,咱们就必须要用到PlatformView了。

举一个例子,下图就是 Android 上应用 PlatformView 机制的 WebView 控件和 Flutter控件的混合渲染的成果:

能够看到Android ViewTree上的确存在一个WebView。

上面是一个Flutter的应用WebView的下层代码示例:

import 'package:flutter/material.dart';import 'package:webview_flutter/webview_flutter.dart';// .. 省略App代码class _BodyState extends State<Body> {  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text('InAppWebView Example'),      ),      body: Expanded(        child: WebView(          initialUrl: 'https://flutter.dev/',          javascriptMode: JavascriptMode.unrestricted,        ),      ),    );  }}

黄色背景内容是应用WebView的办法,能够看到,通过 WebView 插件的封装,尽管背地是 Android 平台或者 iOS 平台自身的 WebView,然而就像应用 Flutter Widget 一样不便。

其实在Flutter历史演进过程中,对于 PlatformView 的解决已经有过两种计划,别离是:

Flutter 1.20版本之前的 VirtualDisplay 形式,和 Flutter 1.20 之后举荐应用的 HybridComposition 形式。当初官网举荐 HybridComposition 的 embedding 形式,能够防止很多之前的 bug 和性能问题,具体不再赘述,能够参考官网文档。

官网的PlatformView介绍文档:在 Flutter 利用中应用集成平台视图托管您的原生 Android 和 iOS 视图

Flutter 引擎线程模型

要了解下文的线程合并,首先咱们须要理解下Flutter 引擎的线程模型。

Flutter Engine 须要提供4个 Task Runner,这4个 Runner 默认的个别状况下别离对应别离着4个操作系统线程,这四个 Runner 线程各司其职:

Task Runner作用
Platform Task RunnerApp 的主线程,用于解决用户操作、各类音讯和 PlatformChannel ,并将它们传递给其余 Task Runner 或从其余 Task Runner 传递过去。
UI Task RunnerDart VM 运行所在的线程。运行 Dart 代码的线程,负责生成要传递给 Flutter 引擎的 layer tree。
GPU Task Runner (Raster Task Runner)与 GPU 解决相干的线程。它是应用 Skia 最终绘制的过程相干的线程(OpenGL 或 Vulkan 等等)
IO Task Runner执行波及 I/O 拜访的耗时过程的专用线程,例如解码图像文件。

如下图所示:

线程合并

对于线程合并,咱们可能有上面几个疑难:

  1. 为什么不必 platform view 的时候,两种多引擎工作的好好的?
  2. 为什么应用 platform view 的时候,iOS 和 Android 两端,都须要 merge 么,能不能不 merge ?
  3. merge 当前,在不应用 platform view 的 flutter 页面里,还会勾销 merge 还原回来么?

咱们来怀着这几个疑难去剖析问题。

为什么要线程合并?

为什么在应用PlatformView的时候,须要把 Platform 线程和 Raster 线程合并起来?

简略的说就是:

  1. 所有 PlatformView 的操作须要在主线程里进行(Platform线程指的就是App的主线程),否则在 Raster 线程解决 PlatformView 的 composition 和绘制等操作时,Android Framework 查看到非 App 主线程,会间接抛异样;
  2. Flutter 的 Raster渲染操作和 PlatformView 的渲染逻辑是各自渲染的,当他们一起应用的时候每一帧渲染时候,须要做同步,而比较简单间接的一种实现形式就是把两个工作队列合并起来,只让一个主线程的 runner 去一一生产两个队列的工作;
  3. Skia和GPU打交道的相干操作,其实是能够放在任意线程里的,合并到App主线程进行相干的操作是齐全没有问题的

那么,Platform Task Runner在合并GPU Task Runner后,主线程也就包揽并承当了本来两个Runner的所有工作,参考上面的示意图:

咱们剖析external_view_embedder.cc相干的代码也能够看到合并的操作:

// src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.cc// |ExternalViewEmbedder|PostPrerollResult AndroidExternalViewEmbedder::PostPrerollAction(    fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {  if (!FrameHasPlatformLayers()) {    // 这里判断以后frame有没有platform view,有就间接返回    return PostPrerollResult::kSuccess;  }  if (!raster_thread_merger->IsMerged()) {     // 如果有platform view并且没merger,就进行merge操作    // The raster thread merger may be disabled if the rasterizer is being    // created or teared down.    //    // In such cases, the current frame is dropped, and a new frame is attempted    // with the same layer tree.    //    // Eventually, the frame is submitted once this method returns `kSuccess`.    // At that point, the raster tasks are handled on the platform thread.    raster_thread_merger->MergeWithLease(kDefaultMergedLeaseDuration);    CancelFrame();    return PostPrerollResult::kSkipAndRetryFrame;  }  // 扩大并更新租约,使得前面没有platform view并且租约计数器升高到0的时候,开始unmerge操作  raster_thread_merger->ExtendLeaseTo(kDefaultMergedLeaseDuration);  // Surface switch requires to resubmit the frame.  // TODO(egarciad): https://github.com/flutter/flutter/issues/65652  if (previous_frame_view_count_ == 0) {    return PostPrerollResult::kResubmitFrame;  }  return PostPrerollResult::kSuccess;}

也就是说,咱们有两种状况,一种是以后layers中没有 PlatformView ,一种是开始有PlatformView,咱们剖析下各自的四大线程的运行状态:

  1. 首先没有PlatformView的时候的状况下,四大 Task Runner 的状态:

Platform ✅ / UI ✅ / Raster ✅ / IO ✅

  1. 应用PlatformView的时候的状况下,四大 Task Runner 的状态:

Platform ✅(同时解决Raster线程的工作队列) / UI ✅ / Raster ❌(闲置) / IO ✅

merge 和 unmerge 操作,能够如下图所示:

一个 runner 如何生产两个工作队列?

要害的两个点就是:

  1. TaskQueueEntry 类中有两个成员变量,记录了以后队列的上游和上游的queue_id
  2. 在 TaskQueueRunner 取下一个工作的时候(也就是PeekNextTaskUnlocked函数)做了非凡解决:

TaskQueueEntry类的这两个成员的申明和文档:

/// A collection of tasks and observers associated with one TaskQueue.////// Often a TaskQueue has a one-to-one relationship with a fml::MessageLoop,/// this isn't the case when TaskQueues are merged via/// \p fml::MessageLoopTaskQueues::Merge.class TaskQueueEntry { public:  // ....  std::unique_ptr<TaskSource> task_source;  // Note: Both of these can be _kUnmerged, which indicates that  // this queue has not been merged or subsumed. OR exactly one  // of these will be _kUnmerged, if owner_of is _kUnmerged, it means  // that the queue has been subsumed or else it owns another queue.  TaskQueueId owner_of;  TaskQueueId subsumed_by;  // ...};

取下一个工作的PeekNextTaskUnlocked的逻辑(参考正文):

// src/flutter/fml/message_loop_task_queues.ccconst DelayedTask& MessageLoopTaskQueues::PeekNextTaskUnlocked(    TaskQueueId owner,    TaskQueueId& top_queue_id) const {  FML_DCHECK(HasPendingTasksUnlocked(owner));  const auto& entry = queue_entries_.at(owner);  const TaskQueueId subsumed = entry->owner_of;  if (subsumed == _kUnmerged) { // 如果没merge的话,就取本人以后的top工作    top_queue_id = owner;    return entry->delayed_tasks.top();  }  const auto& owner_tasks = entry->delayed_tasks;  const auto& subsumed_tasks = queue_entries_.at(subsumed)->delayed_tasks;  // we are owning another task queue  const bool subsumed_has_task = !subsumed_tasks.empty();  const bool owner_has_task = !owner_tasks.empty();  if (owner_has_task && subsumed_has_task) {    const auto owner_task = owner_tasks.top();    const auto subsumed_task = subsumed_tasks.top();    // 如果merge了的话,依据标记判断,就取两个队列的top工作,再比拟谁比拟靠前    if (owner_task > subsumed_task) {      top_queue_id = subsumed;    } else {      top_queue_id = owner;    }  } else if (owner_has_task) {    top_queue_id = owner;  } else {    top_queue_id = subsumed;  }  return queue_entries_.at(top_queue_id)->delayed_tasks.top();}

问题与剖析

遇到的问题

咱们在应用官网引擎的过程中,别离在独立多引擎和轻量级多引擎两个场景下的PlatformView时,都遇到了线程合并的问题。

问题1:独立多引擎下的线程合并问题

最早是 webview 的业务方报告的 slardar 解体问题,过后写了一个 unable_to_merge_raster_demo的例子,而后给官网提交了一个issue:

https://github.com/flutter/fl...

也就是说,在独立的多引擎下,应用platform view的时候,会因为raster_thread_merger不反对多于一对一的合并(merge)操作而失败并报错。

解体的demo:

https://github.com/eggfly/una...

看日志这是一个解体,而后接一个native的SIGABRT解体,日志如下:

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***Build fingerprint: 'Xiaomi/umi/umi:11/RKQ1.200826.002/21.3.3:user/release-keys'Revision: '0'ABI: 'arm64'pid: 11108, tid: 11142, name: 1.raster  >>> com.example.unable_to_merge_raster_demo <<<uid: 10224signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------Abort message: '[FATAL:flutter/fml/raster_thread_merger.cc(48)] Check failed: success. Unable to merge the raster and platform threads    x0  0000000000000000  x1  0000000000002b86  x2  0000000000000006  x3  0000007c684fd150    // ... register valuesbacktrace:      #00 pc 0000000000089acc  /apex/com.android.runtime/lib64/bionic/libc.so (abort+164) (BuildId: a790cdbd8e44ea8a90802da343cb82ce)      #01 pc 0000000001310784  /data/app/~~W2sUpMihWXQXs-Yx0cuHWg==/com.example.unable_to_merge_raster_demo-IUwY4BX5gBqjR0Pxu09Pfw==/lib/arm64/libflutter.so (BuildId: 854273bae6db1c10c29f7189cb0cf640ad4db110)      #02 pc 000000000133426c  /data/app/~~W2sUpMihWXQXs-Yx0cuHWg==/com.example.unable_to_merge_raster_demo-IUwY4BX5gBqjR0Pxu09Pfw==/lib/arm64/libflutter.so (BuildId: 854273bae6db1c10c29f7189cb0cf640ad4db110)      // ... more stack framesLost connection to device.

问题2:轻量级多引擎下的线程合并问题

Flutter 2.0版本后引入了lightweight flutter engines,也就是轻量级引擎,能够通过FlutterEngineGroups和spawn()函数来生成一个轻量级引擎,官网轻量级相干的提交:

https://github.com/flutter/en...

咱们在用官网的lightweight multiple engine的sample代码的时候,尝试在多引擎下加上PlatformView,也就是在main.dart里加上webview。

官网demo代码:https://github.com/flutter/sa...

运行起来会有这样的解体日志,这里的谬误和问题1有一点区别:

[FATAL:flutter/fml/raster_thread_merger.cc(22)] Check failed: !task_queues_->Owns(platform_queue_id_, gpu_queue_id_). 

问题剖析

剖析1:独立多引擎线程合并问题

问题1是Flutter 1.22+独立引擎的问题,我在代码中搜寻raster_thread_merger.cc(48)] Check failed: success. Unable to merge the raster and platform threads其中raster_thread_merger.cc的48行这样的代码:

success == false的时候会触发SIGABRT,看Merge()函数什么时候返回false:

bool MessageLoopTaskQueues::Merge(TaskQueueId owner, TaskQueueId subsumed) {  if (owner == subsumed) {    return true;  }  std::mutex& owner_mutex = GetMutex(owner);  std::mutex& subsumed_mutex = GetMutex(subsumed);  std::scoped_lock lock(owner_mutex, subsumed_mutex);  auto& owner_entry = queue_entries_.at(owner);  auto& subsumed_entry = queue_entries_.at(subsumed);  if (owner_entry->owner_of == subsumed) {    return true;  }  std::vector<TaskQueueId> owner_subsumed_keys = {      owner_entry->owner_of, owner_entry->subsumed_by, subsumed_entry->owner_of,      subsumed_entry->subsumed_by};  for (auto key : owner_subsumed_keys) {    if (key != _kUnmerged) {      return false; // <--- 这里是返回false惟一的可能    }  }  owner_entry->owner_of = subsumed;  subsumed_entry->subsumed_by = owner;  if (HasPendingTasksUnlocked(owner)) {    WakeUpUnlocked(owner, GetNextWakeTimeUnlocked(owner));  }  return true;}

Merge函数看起来是把两个task_queue合并到一起的要害逻辑,通过设置entry->owner_of和subsumed_by来实现的。参考下面TaskQueueEntry类的申明代码。

那么在owner_subsumed_keys这个vector的四个元素里打上log看一下,for循环的本意是查看owner和上游和上游,以及subsumed的上游和上游,加起来这四个id的任意元素里如果呈现一个不等于_kUnmerged的就会查看失败,进而不进行前面的merge和赋值操作,间接返回false。

通过log能够看出:

E/flutter: ::Merge() called with owner=0, subsumed=2E/flutter: [0]=18446744073709551615 [1]=18446744073709551615 [2]=18446744073709551615 [3]=18446744073709551615E/flutter: ::Merge() called with owner=0, subsumed=5E/flutter: [0]=2 [1]=18446744073709551615 [2]=18446744073709551615 [3]=18446744073709551615A/flutter: Check failed: success. Unable to merge the raster and platform threads.

能够看到Merge调用了两次,并且第二次调用的第0个元素是2,印证了下面for循环呈现不等于unmerge常量的状况了。

其中的2和5别离是引擎1和引擎2的raster线程,通过

 adb root adb shell kill -3 $pid 

再 adb pull /data/anr/trace_00 拉进去看实在的线程也能够看到1.ui, 2.ui, 1.raster, 2.raster, 1.io, 2.io这样的被设置了名字线程(有pthread_setname之类的函数):

在Google搜寻这个Unable to merge the raster and platform threads在也能够搜到一个提交:

https://github.com/flutter/en...

提交介绍说:

This will make sure that people don't use platform view with flutter engine groups until we've successfully accounted for them.

所以它在做第1次merge的时候,设置了block_merging标记,第二次以及前面的merge操作会失败并打印一个日志:

所以,在官网那是一个todo,是待实现的feature。

剖析2:轻量级多引擎线程合并问题

问题2是Flutter 2.0+轻量级引擎下的问题,间接看轻量级多引擎下,查看失败的那一行的源码:

很显著,和下面的独立多引擎不同,这里在创立RasterThreadMerger的构造函数的FML_CHECK查看就失败了,证实platform和raster曾经是merge的状态了,所以这里也是SIGABRT并且程序退出了。

通过打印log看到两个引擎的platform和raster的id是共享的,引擎1和引擎2的platform_queue_id都是0,raster_queue_id都是2

小结:多对一合并是官网未实现的feature

很容易咱们能够推理失去,多引擎的每个引擎都须要有一套四大线程,它们能够抉择专用,或者也能够抉择创立本人独立的线程。

咱们通过之前的log打印的task_queue_id,剖析一下两个问题惟一的区别:

  1. 在问题1(两个独立引擎中)的状况是这样的(四大线程除了platform,其余三个线程不共享):
独立引擎1独立引擎2
platform_task_queue_id00
ui_task_queue_id14
raster_task_queue_id25
io_task_queue_id36
  1. 在问题2(两个轻量级引擎中)的状况是这样的(四大线程全副共享):
轻量级引擎1轻量级引擎2
platform_task_queue_id00
ui_task_queue_id11
raster_task_queue_id22
io_task_queue_id33

所以绝对来讲,感觉问题2更容易解决,并且咱们应用flutter 2.0和卡片计划的业务,马上就将要遇到这个问题。

官网的轻量级引擎有一个TODO列表,把这个问题标记成Cleanup的工作:

https://github.com/flutter/fl...

官网标记了P5优先级:

因为业务须要所以间接就不等了,咱们罗唆本人实现它。

线程合并解决方案

疾速解决问题2:解决轻量级引擎的问题

既然在轻量级引擎下,platform 线程和 raster 线程都是共享的,只是 engine 和 rasterizer 的对象是离开的,并且当初的逻辑是别离在两个引擎里,new 了本人的 RasterThreadMerger对象,进行后续的 merge 和 unmerge 操作。并且在 merge 的时候做是否Owns的查看。

那咱们能够简略的做这几件事:

  1. 改成去掉 Owns() 的检查和相干线程查看
  2. 共享一个 RasterThreadMerger 对象进行 merge 和 unmerge 操作
  3. 先不论那个 lease_term (租约)计数器,留下后续解决

批改计划根本是坤神(咱们Flutter组的战友)的 prototype 提交的计划,并且加一些边角的解决即可。

Prototype原型的要害批改的中央:

每个带title的都是一个FlutterView,终于不解体了:

成果截图:

然而这只是一个原型,很多状态问题和merge的逻辑咱们没有解决的很好,问题包含:

  1. 咱们不能像原型一样,在字节的Flutter引擎里间接hardcode写死共享一个merger对象,所以2.0之前的独立多引擎仍旧会有问题
  2. 咱们没正确处理IsMerged函数的正确返回后果
  3. 咱们还没有正确处理lease_term的计数器,lease_term计数器降到0的时候,应该unmerge
  4. 咱们假象有一种case: 引擎1须要unmerge,然而引擎2还须要渲染platformview,这时候1的unmerge不能立即调用,须要等所有引擎都没有merge的需要的时候,再去把platform和raster脱离开

所以咱们须要有一套真正的终极解决方案,最好能:笼罩两个raster同时merge到一个platform的状况,而后奉献给官网。

彻底解决问题1和2(最终计划)

解决思路

通过查看代码里raster_thread_merger对象是rasterizer的一个成员:

// src/flutter/shell/common/rasterizer.hnamespace flutter {//----------------------------------------------------------------------------class Rasterizer final : public SnapshotDelegate { public:  //------- private:  // ...省略  fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger_;

以下都是 RasterThreadMerger 类里的成员函数,都是须要咱们批改成一对多merge当前,也保障去保护失常调用机会的API:

// src/flutter/fml/raster_thread_merger.h#ifndef FML_SHELL_COMMON_TASK_RUNNER_MERGER_H_#define FML_SHELL_COMMON_TASK_RUNNER_MERGER_H_// ... 省略 #include namespace fml {class RasterThreadMerger    : public fml::RefCountedThreadSafe<RasterThreadMerger> { public:  // Merges the raster thread into platform thread for the duration of  // the lease term. Lease is managed by the caller by either calling  // |ExtendLeaseTo| or |DecrementLease|.  // When the caller merges with a lease term of say 2. The threads  // are going to remain merged until 2 invocations of |DecreaseLease|,  // unless an |ExtendLeaseTo| gets called.  //  // If the task queues are the same, we consider them statically merged.  // When task queues are statically merged this method becomes no-op.  void MergeWithLease(size_t lease_term);  // Un-merges the threads now, and resets the lease term to 0.  //  // Must be executed on the raster task runner.  //  // If the task queues are the same, we consider them statically merged.  // When task queues are statically merged, we never unmerge them and  // this method becomes no-op.  void UnMergeNow();  // If the task queues are the same, we consider them statically merged.  // When task queues are statically merged this method becomes no-op.  void ExtendLeaseTo(size_t lease_term);  // Returns |RasterThreadStatus::kUnmergedNow| if this call resulted in  // splitting the raster and platform threads. Reduces the lease term by 1.  //  // If the task queues are the same, we consider them statically merged.  // When task queues are statically merged this method becomes no-op.  RasterThreadStatus DecrementLease();  bool IsMerged();  // ... 省略一些接口  bool IsMergedUnSafe() const;};}  // namespace fml#endif  // FML_SHELL_COMMON_TASK_RUNNER_MERGER_H_

merger创立的时候,须要思考某些状况下不反对merger须要放弃merger不被创立进去(比方某些不反对的平台或者某些unittest):

// src/flutter/shell/common/rasterizer.ccvoid Rasterizer::Setup(std::unique_ptr<Surface> surface) {  // ... 省略  if (external_view_embedder_ &&      external_view_embedder_->SupportsDynamicThreadMerging() &&      !raster_thread_merger_) {    const auto platform_id =        delegate_.GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId();    const auto gpu_id =        delegate_.GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId();    raster_thread_merger_ = fml::RasterThreadMerger::CreateOrShareThreadMerger(        delegate_.GetParentRasterThreadMerger(), platform_id, gpu_id);  }  if (raster_thread_merger_) {    raster_thread_merger_->SetMergeUnmergeCallback([=]() {      // Clear the GL context after the thread configuration has changed.      if (surface_) {        surface_->ClearRenderContext();      }    });  }}

那么咱们有一种抉择是在每个engine各自的rasterizer的创立的时候,改改它的逻辑,在raster_queue_id雷同的时候,复用之前的对象,听起来是个好方法。

实现的计划

画了个图作为两种状况的展现:

对于线程什么状况下容许合并,什么状况下不容许合并的示意图:

另外还有一种状况没有列出,本人merge到本人的状况:当初的代码默认返回true的。

总结一句话就是一个queue能够合并多个queue(能够有多个上游),然而一个queue不能够有多个上游。

此实现的设计:

  • 首先最重要的:将TaskQueueEntry中的成员owner_ofTaskQueueId改成std::set<TaskQueueId> owner_of,来记录这个线程所有merge过的subsumed_id(一对多的merge关系)
  • 代码的批改平台独立的,能够让Android和iOS共享雷同的代码逻辑,确保不同平台的相干目录中的代码没有被更改(之前做过一个版本的计划,是Android、iOS别离批改了embedder类的逻辑)
  • 删除了之前官网禁用阻塞逻辑的代码(也就是revert了官网之前的这个提交:https://github.com/flutter/en...)
  • 为了缩小现有代码的更改数量,把旧的RasterThreadMerger类视为proxy,并引入了一个新的SharedThreadMerger类,并且在引擎里记录parent_merger,在引擎的spawn函数里拿到父亲引擎的merger,看是否能够共享
  • 与merge相干的办法调用(包含MergeWithLease()、UnmergeNow()、DecrementLease()、IsMergeUnsafe()改成重定向到SharedThreadMerger内的办法,而后用一个std::map<ThreadMergerCaller, int>来记录合并状态和lease_term租约计数器
  • 将UnMergeNow()更改为UnMergeNowIfLastOne(),以记住所有merge的调用者,在调用Rasterizer::Teardown()的时候,并且它是在最初一个merger的时候,立即unmerge,其余状况须要放弃unmerge状态。
  • 在shell_unittest和fml_unittests中增加了更多的测试,并在run_tests.py中启用fml_unittests(之前被一个官网提交禁用了,发现改什么代码都不起作用,比拟坑)

解决方案相干的代码

  1. TaskQueueEntry改成std::set的汇合
class TaskQueueEntry { public:  /// 省略  /// Set of the TaskQueueIds which is owned by this TaskQueue. If the set is  /// empty, this TaskQueue does not own any other TaskQueues.  std::set<TaskQueueId> owner_of; // 原来是TaskQueueId owner_of;
  1. PeekNextTaskUnlocked新的逻辑:
// src/flutter/fml/message_loop_task_queues.ccTaskSource::TopTask MessageLoopTaskQueues::PeekNextTaskUnlocked(    TaskQueueId owner) const {  FML_DCHECK(HasPendingTasksUnlocked(owner));  const auto& entry = queue_entries_.at(owner);  if (entry->owner_of.empty()) {    FML_CHECK(!entry->task_source->IsEmpty());    return entry->task_source->Top();  }  // Use optional for the memory of TopTask object.  std::optional<TaskSource::TopTask> top_task;    // 更新以后最小的工作的lambda函数  std::function<void(const TaskSource*)> top_task_updater =      [&top_task](const TaskSource* source) {        if (source && !source->IsEmpty()) {          TaskSource::TopTask other_task = source->Top();          if (!top_task.has_value() || top_task->task > other_task.task) {            top_task.emplace(other_task);          }        }      };  TaskSource* owner_tasks = entry->task_source.get();  top_task_updater(owner_tasks);  for (TaskQueueId subsumed : entry->owner_of) {    TaskSource* subsumed_tasks = queue_entries_.at(subsumed)->task_source.get();     // 遍历set中subsumed合并的工作队列,更新以后最小的工作    top_task_updater(subsumed_tasks);   }  // At least one task at the top because PeekNextTaskUnlocked() is called after  // HasPendingTasksUnlocked()  FML_CHECK(top_task.has_value());  return top_task.value();}
  1. merge和unmerge相干的查看(省略,详情能够参考 Pull Request中代码提交)

实现过程中的小坑

  1. 和官网一样,应用FlutterFragment的形式来嵌入多引擎的时候,FlutterSurfaceView会给surface设置ZOrder,这时候多个Surface会有ZOrder争抢top的问题
 private void init() {    // If transparency is desired then we'll enable a transparent pixel format and place    // our Window above everything else to get transparent background rendering.    if (renderTransparently) {      getHolder().setFormat(PixelFormat.TRANSPARENT);      setZOrderOnTop(true);    }

须要在创立的时候,去掉Transparent的flag,须要这样改:(这个问题被坑了很久,差点没让我放弃这个提交)

val flutterFragment =    FlutterFragment.withCachedEngine(i.toString())        // Opaque is to avoid platform view rendering problem due to wrong z-order        .transparencyMode(TransparencyMode.opaque) // this is needed        .build<FlutterFragment>()
  1. 在iOS做unittest的时候,发现有相应的解体,也是没有解体的stack和具体log,起初发现iOS目录下有一个README,提到了应用xcode能够关上unittest工程,开启模拟器自动测试,并且发现能够间接在我没有attach的状况下,主动attach lldb并且定位到解体的那一行代码:

  1. 官网review代码的时候,提出的最大问题是之前用了map做一个全局static的std::map<Pair<QueueId, QueueId>, SharedThreadMerger>的字典static变量,用来取platform&raster这一个pair的merger,然而老外扔给我一个google c++标准,明确写了non-trivial的类型才容许保留为全局变量,官网标准文档:https://google.github.io/styl...

最终通过把merger作为Shell类的成员变量来解决这个生命周期的问题。

  1. 在测试的时候发现macOS、Linux的engine的unopt 指标的build和test都没问题,然而偏偏windows的引擎去测试host_debug_unopt的unittest的时候,出间接exit,exitcode不是0的

而后windows的解体栈默认不会打印到terminal:谷歌的luci平台上的失败信息:

能够看到什么log都没有。

困扰半天最终决定:装一个windows虚拟机!神奇的事件产生了,在我的windows 10 + flutter engine环境下编译而后运行我的test,后果全都过了。惊愕!最终还是通过两分法看批改,定位到了一个 unittest 的抽取的改法造成了问题。

留个题目:能够看出如下代码为什么windows会有问题吗?

/// A mock task queue NOT calling MessageLoop->Run() in threadstruct TaskQueueWrapper {  fml::MessageLoop* loop = nullptr;  /// 问题提醒在这里:  /// This field must below latch and term member, because  /// cpp standard reference:  /// non-static data members are initialized in the order they were declared in  /// the class definition  std::thread thread;    /// The waiter for message loop initialized ok  fml::AutoResetWaitableEvent latch;  /// The waiter for thread finished  fml::AutoResetWaitableEvent term;  TaskQueueWrapper()      : thread([this]() {          fml::MessageLoop::EnsureInitializedForCurrentThread();          loop = &fml::MessageLoop::GetCurrent();          latch.Signal();          term.Wait();        }) {    latch.Wait();  }  // .. 省略析构函数, term.Signal() 和 thread.join() 等等};

  1. 跑起来两个 webview 的 demo 当前,点上面的键盘,会有一个 crash(上面的界面弹出键盘当前就崩了):

后果是java层对FlutterImageView的resize造成创立ImageReader的宽高为0了,Android不容许创立宽高是0的ImageReader:

所以又有一个bugfix的提交,已merge到官网

https://github.com/flutter/en...

最终的 Pull Request

已合并到官网Flutter引擎: https://github.com/flutter/en...

给官网奉献代码的小教训

  1. 如果没有issue,最好创立一个issue,而后本人提 Pull Request 解决本人的issue
  2. 最好蕴含test,即便改了一行代码,也是能够写test的,而且他们一看到test就很释怀,也能更好的让前面的人了解你的代码的用意,否则有一个机器人会说你没test,并且打上标签:

  1. 当初最新代码在 git push 的时候,会通过 git_hooks 主动查看所有类型的源码(包含iOS/Android/c++/gn等等)的格局和标准,有不符合规范的间接生成一个diff,作为批改倡议。

这个还能够帮咱们主动触发而后主动批改,命令是:

dart ci/bin/format.dart -f 其中-f是让它主动fix

  1. 官网 review 代码还是很严格的,比方对函数语义的批改,须要同步对docstring进行批改;又比方一言不合给你扔一个c++标准;或者代码进行反复的map[key] = value和map[key] 的取值,能够用 iterator 代替;auto关键词不能滥用,lambda 须要指定返回类型,等等

总结

作为 Flutter Infra 团队的开发,在和咱们的业务部门去实际 Flutter 2.0轻量级引擎和卡片计划的落地的过程中,咱们团队做了很多性能和稳定性的优化,包含空平安迁徙、Image Cache共享、文本动静对齐、Platform View 的多引擎反对、过渡动画性能优化、大内存优化、官网 issue 和稳定性修复等很多工作。

咱们在致力反对字节外部业务的同时,也会继续将其中比拟通用的一些 fix 和优化计划的 Pull Request 提交给官网,和全世界开发者一起共建更好的Flutter社区。

另外,咱们也通过字节跳动的企业级技术服务平台火山引擎对外部客户提供 Flutter 全流程解决方案,助力应用 Flutter 技术栈的开发团队高效持重地落地业务。

对于字节终端技术团队

字节跳动终端技术团队(Client Infrastructure)是大前端根底技术的全球化研发团队(别离在北京、上海、杭州、深圳、广州、新加坡和美国山景城设有研发团队),负责整个字节跳动的大前端基础设施建设,晋升公司全产品线的性能、稳定性和工程效率;反对的产品包含但不限于抖音、今日头条、西瓜视频、飞书、瓜瓜龙等,在挪动端、Web、Desktop等各终端都有深入研究。

就是当初!客户端/前端/服务端/端智能算法/测试开发 面向寰球范畴招聘!一起来用技术扭转世界,感兴趣请分割 chenxuwei.cxw@bytedance.com,邮件主题 简历-姓名-求职意向-冀望城市-电话