Abstract

This article introduces a thread merge problem that cannot be avoided when using PlatformView scenarios with Flutter multiple engines, and its final solution. The final Pull Request has been merged into Google’s official Flutter repository:

Github.com/flutter/eng…

Key points of this paper:

  1. Thread merging does not actually refer to the advanced interface of the operating system that combines two pthreads, but rather to the four Task runners in the Flutter engine that consume two tasks in their queues simultaneously.
  2. Thread merge problem refers to the problem that the four main threads of the Flutter engine (Platform thread, UI thread, Raster thread and IO thread) need to merge and separate the Platform thread and Raster thread when using PlatformView. The previous official thread merge mechanism only supported one-to-one thread merge, but the multi-engine scenario requires one-to-many merge and some associated logic. Please refer to the following introduction for details.
  3. On the four Task of Flutter Engine Runner can refer to zhihu column is introduced, Flutter Engine thread model: zhuanlan.zhihu.com/p/64034467
  4. The thread merge operation described in this article (thus achieving the effect of one Looper consuming two queues of messages) is shown in the following diagram, so that we can get an idea:

background

What is a PlatformView?

First of all, let’s talk about what a PlatformView is. It’s simply a platform-dependent View. That is, there are widgets that are native to The Android and iOS platforms, but not implemented in the Flutter cross-platform control library. These widgets can be rendered and briated using the PlatformView mechanism provided by Flutter. In addition, Flutter can be used to create and control these native views in the upper layer to ensure the unification of cross-platform interfaces at both ends.

Such as WebView, map control, third party advertising SDK and so on, we must use PlatformView.

For example, here is a mix of WebView and Flutter controls on Android using PlatformView:

You can see that there is indeed a WebView on the Android ViewTree.

Here is an example of the upper layer code for Flutter using WebView:

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

/ /.. Omit App code
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, ), ), ); }}Copy the code

The yellow background content is the method of using WebView. It can be seen that, encapsulated by the WebView plug-in, the WebView of Android platform or iOS platform itself is just as convenient as the Flutter Widget.

In fact, during the evolution of Flutter history, there were two solutions to deal with PlatformView:

VirtualDisplay before Flutter 1.20, and HybridComposition recommended after Flutter 1.20. Now HybridComposition embedding is officially recommended, which can avoid many previous bugs and performance problems. For details, please refer to the official documents.

Use the integrated PlatformView to host your native Android and iOS views in the Flutter application

The Flutter engine thread model

To understand thread merging below, we first need to understand the thread model of the Flutter engine.

The Flutter Engine needs to provide four Task runners, which by default correspond to four operating system threads, each of which has its own responsibilities:

Task Runner role
Platform Task Runner The main thread of the App, which handles user operations, various messages, and platformChannels and passes them to and from other Task Runners.
UI Task Runner The thread in which the Dart VM runs. The thread running the Dart code is responsible for generating the Layer tree to be passed to the Flutter engine.
GPU Task Runner (Raster Task Runner) Threads associated with GPU processing. It is the process-related thread that uses Skia for the final drawing (OpenGL or Vulkan, etc.)
IO Task Runner A dedicated thread that performs time-consuming processes involving I/O access, such as decoding image files.

As shown below:

Thread merger

We may have the following questions about thread merging:

  1. Why do both engines work fine when you don’t use Platform View?
  2. When using platform View, iOS and Android both need to merge.
  3. Will merge be cancelled and restored to all flutter pages that do not use the Platform View?

Let us analyze the problem with these questions in mind.

Why thread merge?

Why is it necessary to merge the Platform thread and Raster thread when using PlatformView?

To put it simply:

  1. All PlatformView operations need to take place in the main thread (Platform thread refers to the main thread of the App), otherwise when the Raster thread handles composition and drawing of the PlatformView, If the Android Framework detects a non-APP main thread, it will throw an exception directly.
  2. The Raster rendering operation of Flutter and the rendering logic of PlatformView are rendered separately. When they are used together, each frame needs to be rendered in sync. A simpler and more straightforward way to do this is to merge the two task queues. Let only one main thread runner consume two queues of tasks one at a time;
  3. Related operations between Skia and GPU can be put into any thread, and there is no problem to merge them into the main thread of App

Therefore, after Platform Task Runner merges GPU Task Runner, the main thread also undertakes all the tasks of the original two runners. Please refer to the following schematic diagram:

We analyzed external_view_embedder.cc related code and can also see the merge operation:

// src/flutter/shell/platform/android/external_view_embedder/external_view_embedder.cc
// |ExternalViewEmbedder|
PostPrerollResult AndroidExternalViewEmbedder::PostPrerollAction( fml::RefPtr
       <:rasterthreadmerger>
         raster_thread_merger)
        {
  if (!FrameHasPlatformLayers()) {
    // Check whether the current frame has a Platform view
    return PostPrerollResult::kSuccess;
  }
  if(! raster_thread_merger->IsMerged()) { 
    // Merge if there is a platform view and no merger
    // 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(a);return PostPrerollResult::kSkipAndRetryFrame;
  }

  // Extend and update the lease so that there is no platform view behind and the lease counter is reduced to 0
  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;
}
Copy the code

That is to say, we have two cases, one is that there is no PlatformView on Layers at present, the other is that there is PlatformView at the beginning. Let’s analyze the running status of the four main threads:

  1. First, in the case of no PlatformView, the status of the four Task runners:

Platform ✅ / UI ✅ / Raster ✅ / IO ✅

  1. In the case of PlatformView, the status of the four Task runners:

Platform ✅(a task queue that also handles Raster threads)/UI ✅ / Raster ❌(idle)/IO ✅

Merge and unmerge operations can be performed as follows:

How does a runner consume two task queues?

The two key points are:

  1. The TaskQueueEntry class has two member variables that record the queue_ID upstream and downstream of the current queue
  2. When TaskQueueRunner takes the next task (i.ePeekNextTaskUnlockedThe function) does special processing:

Declarations and documentation for these two members of the TaskQueueEntry class:

/// 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;
  // ...
};
Copy the code

To remove the PeekNextTaskUnlocked logic for the next task (see comments) :

// src/flutter/fml/message_loop_task_queues.cc
const 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) { // If you do not merge, take your current top task
    top_queue_id = owner;
    return entry->delayed_tasks.top(a); }const auto& owner_tasks = entry->delayed_tasks;
  const auto& subsumed_tasks = queue_entries_.at(subsumed)->delayed_tasks;

  // we are owning another task queue
  const boolsubsumed_has_task = ! subsumed_tasks.empty(a);const boolowner_has_task = ! owner_tasks.empty(a);if (owner_has_task && subsumed_has_task) {
    const auto owner_task = owner_tasks.top(a);const auto subsumed_task = subsumed_tasks.top(a);// Merge the top tasks of the two queues, and compare which is the first
    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(a); }Copy the code

Problems and Analysis

Problems encountered

In the process of using the official engine, we encountered the problem of thread merging in both the standalone multi-engine and lightweight multi-engine PlatformView scenarios.

Problem 1: Thread merging problem in independent multiple engines

Slardar crash was first reported by the business side of WebView, writing an example of unable_to_merge_raster_demo, then submitting an issue to the official:

Github.com/flutter/flu…

In other words, using a platform view in a standalone multi-engine scenario, raster_thread_merger does not support more than one to one merge operations and will fail with an error.

The collapse of the demo:

Github.com/eggfly/unab…

This is a crash, then a native SIGABRT crash, the log is as follows:

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
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: 10224 signal 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 values backtrace: #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/libflutt er.so (BuildId: 854273bae6db1c10c29f7189cb0cf640ad4db110) #02 pc 000000000133426c /data/app/~~W2sUpMihWXQXs-Yx0cuHWg==/com.example.unable_to_merge_raster_demo-IUwY4BX5gBqjR0Pxu09Pfw==/lib/arm64/libflutt er.so (BuildId: 854273bae6db1c10c29f7189cb0cf640ad4db110) // ... more stack frames Lost connection to device.Copy the code

Problem 2: Thread merging under lightweight multi-engine

Lightweight Flutter engines have been introduced since The release of Flutter 2.0. They can generate a lightweight engine using the FlutterEngineGroups and spawn() functions.

Github.com/flutter/eng…

When using the official Lightweight Multiple Engine sample code, we tried adding PlatformView to the multiple engine, i.e. adding webView to main.dart.

Official demo code: github.com/flutter/sam…

There is a slight difference between the error and problem 1:

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

Problem analysis

Analysis 1: Independent multi-engine thread merging problem

Raster_thread_merger. Cc (48)] Check failed: Unable to merge the raster and platform threads where raster_thread_merger. Cc

Merge() when success == false, SIGABRT is triggered. Merge() returns 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; // <-- this is the only way to return false
    }
  }

  owner_entry->owner_of = subsumed;
  subsumed_entry->subsumed_by = owner;

  if (HasPendingTasksUnlocked(owner)) {
    WakeUpUnlocked(owner, GetNextWakeTimeUnlocked(owner));
  }

  return true;
}
Copy the code

The Merge function appears to be the key logic to Merge the two task_queues together by setting entry->owner_of and subsumed_by. Refer to the declaration code for the TaskQueueEntry class above.

Type log in the owner_subsumed_keys vector. The for loop checks owner and upstream and downstream, and subsumed upstream and downstream. If one of the elements of the four ids does not equal the _kUnmerged element, merge and assign will fail and return false.

It can be seen from log that:

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

Merge is called twice, and the 0th element in the second call is 2. This for loop does not equal the unmerge constant.

Where 2 and 5 are the raster threads of engine 1 and engine 2, respectively, through

 adb root
 adb shell kill- 3$pid 
Copy the code

1. UI, 2. UI, 1. Raster, 2. Raster, 1. 2. A named thread like IO (with functions like pthread_setname) :

Unable to merge the raster and Platform Threads search on Google to find a submission: Unable to merge the raster and Platform Threads

Github.com/flutter/eng…

The submission says:

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

For merging operations, you need to merge them by merging them. For merging operations, you need to merge them by merging them.

Therefore, it is officially a todo, which is a feature to be implemented.

Analysis 2: Lightweight multi-engine thread merging problem

Q2 is a problem with the Flutter 2.0+ lightweight engine.

Obviously, the FML_CHECK to create the RasterThreadMerger constructor fails, proving that Platform and Raster are merged, so it’s SIGABRT and exits.

The platform_queue_id of engine 1 and engine 2 is 0, and the raster_queue_id of engine 2 is 2

Summary: Many-to-one merging is an officially unimplemented feature

It is easy to deduce that each multi-engine engine needs a set of four threads, which can choose to be common, or they can choose to create their own separate threads.

Task_queue_id = task_queue_id = task_queue_id

  1. In the case of problem 1 (two separate engines) it looks like this (the four main threads except platform are not shared by the other three) :
Independent engine 1 Independent engine 2
platform_task_queue_id 0 0
ui_task_queue_id 1 4
raster_task_queue_id 2 5
io_task_queue_id 3 6
  1. The case for problem 2 (two lightweight engines) looks like this (all four threads are shared) :
Lightweight engine 1 Lightweight engine 2
platform_task_queue_id 0 0
ui_task_queue_id 1 1
raster_task_queue_id 2 2
io_task_queue_id 3 3

So it feels like problem 2 is relatively easy to solve, and our business with Flutter 2.0 and card solutions will soon encounter this problem.

The official lightweight engine has a TODO list that flags this problem as a Cleanup task:

Github.com/flutter/flu…

The P5 priorities are officially marked:

Because the business needs it, we just don’t wait. We implement it ourselves.

Thread merge solution

Quick fix # 2: Fix the lightweight engine

Since platform threads and Raster threads are shared under lightweight engines, except that the objects of Engine and Rasterizer are separate, and now the logic is to new its own RasterThreadMerger object in both engines, Perform subsequent merge and unmerge operations. And do the Owns check when you merge.

Here are a few simple things we can do:

  1. Change to remove the Owns() check and associated thread check
  2. Share a RasterThreadMerger object for merge and unmerge operations
  3. Forget the lease_term counter and leave it for later

The modified solution is basically the solution submitted by The Prototype of Kun God (our comrade of the Flutter group) and just add some edges and corners.

Prototype key changes:

Each FlutterView with a title is a FlutterView and finally does not crash:

Effect screenshot:

But this is just a prototype, and there are a lot of state issues and merge logic that we don’t deal with very well, including:

  1. We can’t hardcode directly to share a merger object in the Byte Flutter engine like the prototype, so standalone multiple engines before 2.0 will still have problems
  2. We did not properly handle the return result of IsMerged
  3. We have not yet handled the lease_term counter correctly, and should unmerge when the lease_term counter drops to 0
  4. We assume that engine 1 requires unmerge, but engine 2 also needs to render platformView. In this case, unmerge of engine 1 cannot be called immediately, and platform and Raster need to be separated until all engines do not need to merge

So we need to have a real ultimate solution, preferably one that covers the situation where two Rasters merge into one platform, and then contribute to the authorities.

Solve problems 1 and 2 completely (final solution)

solution

The raster_thread_merger object is a member of the rasterizer:

// src/flutter/shell/common/rasterizer.h
namespace flutter {
//----------------------------------------------------------------------------
class Rasterizer final : public SnapshotDelegate {
 public:
  //-------
 private:
  / /... omit
  fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger_;
Copy the code

The following are the member functions of the RasterThreadMerger class that we need to modify into a one-to-many merge to maintain the normal call timing apis:

// src/flutter/fml/raster_thread_merger.h
#ifndef FML_SHELL_COMMON_TASK_RUNNER_MERGER_H_
#define FML_SHELL_COMMON_TASK_RUNNER_MERGER_H_

/ /... Omit the # 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(a);

  // 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(a);

  bool IsMerged(a);

  / /... Omit some interfaces
  bool IsMergedUnSafe(a) const;
};

}  // namespace fml

#endif  // FML_SHELL_COMMON_TASK_RUNNER_MERGER_H_
Copy the code

When a merger is created, it is necessary to consider the circumstances in which a merger is not supported and it is necessary to keep a merger from being created (e.g. some unsupported platforms or some unittests) :

// src/flutter/shell/common/rasterizer.cc
void Rasterizer::Setup(std::unique_ptr<Surface> surface) {
  / /... omit
  if (external_view_embedder_ &&
      external_view_embedder_->SupportsDynamicThreadMerging() &&
      !raster_thread_merger_) {
    const auto platform_id =
        delegate_.GetTaskRunners().GetPlatformTaskRunner() - >GetTaskQueueId(a);const auto gpu_id =
        delegate_.GetTaskRunners().GetRasterTaskRunner() - >GetTaskQueueId(a); 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(a); }}); }}Copy the code

One option would be to change the logic of each engine’s rasterizer when it is created, and reuse the previous object if the raster_queue_id is the same, which sounds like a good idea.

Implementation scheme

I drew a graph to illustrate two cases:

A schematic diagram of when threads allow merging and when they don’t:

There is another case, not listed, of merging yourself into yourself: the current code returns true by default.

To sum up, a queue can merge multiple queues (it can have multiple downstream queues), but a queue cannot have multiple upstream queues.

The design of this implementation:

  • First and foremost: put the members in TaskQueueEntryowner_offromTaskQueueIdtostd::set<TaskQueueId> owner_ofTo record all merge subsumed_id of this thread.
  • Code changes platform independent, which allows Android and iOS to share the same code logic, ensuring that the code in the respective directories of the different platforms has not been changed. (A previous version of the solution was that Android and iOS amended the logic of the embedder class respectively.)
  • Removed the code that officially disables blocking logic (i.e., this is an official submission: github.com/flutter/eng…
  • In order to reduce the number of changes to the existing code, a new SharedThreadMerger class was introduced, and parent_merger was recorded in the engine, and the parent engine’s merger was recorded in the spawn function of the engine. See if you can share it
  • Method calls related to merge (including MergeWithLease(), UnmergeNow(), DecrementLease(), IsMergeUnsafe() change to redirection to methods within SharedThreadMerger, and then use onestd::map<ThreadMergerCaller, int>To record merge status and leASe_term lease counters
  • Change UnMergeNow() to UnMergeNowIfLastOne() to remember that all callers of merge, at the time of calling Rasterizer::Teardown(), and it’s in the last merger, unmerge immediately, In other cases, keep the unmerge state.
  • – Added more tests in shell_unittest and fml_unittests, and enabled fml_unittests in run_tests.py (which was disabled by an official commit)

Solution-related code

  1. Change TaskQueueEntry to a collection of STD ::set
class TaskQueueEntry {
 public:
  / / / omitted
  /// 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;
Copy the code
  1. PeekNextTaskUnlockedNew logic:
// src/flutter/fml/message_loop_task_queues.cc
TaskSource::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(a); }// Use optional for the memory of TopTask object.
  std::optional<TaskSource::TopTask> top_task;
  
  // Update the current smallest task's lambda function
  std::function<void(const TaskSource*)> top_task_updater =
      [&top_task](const TaskSource* source) {
        if(source && ! source->IsEmpty()) {
          TaskSource::TopTask other_task = source->Top(a);if(! top_task.has_value() || top_task->task > other_task.task) {
            top_task.emplace(other_task); }}}; TaskSource* owner_tasks = entry->task_source.get(a);top_task_updater(owner_tasks);

  for (TaskQueueId subsumed : entry->owner_of) {
    TaskSource* subsumed_tasks = queue_entries_.at(subsumed)->task_source.get(a);// Update the smallest task by traversing the subsumed task queue in set
    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(a); }Copy the code
  1. Merge and unmerge related checks (omitted, see code submission in Pull Request for details)

Potholes in the implementation process

  1. Just like official, when using FlutterFragment to insert multiple engines, FlutterSurfaceView will set ZOrder for surface, which will cause ZOrder competition for multiple surfaces
 private void init(a) {
    // 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);
    }
Copy the code

[root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost]

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>()
Copy the code
  1. When I was making unittest in iOS, I found corresponding crash, but there was also no crash stack and detailed log. Later, I found a README in the iOS directory, which mentioned that xcode could be used to open unittest project and simulator automatic test. And I found that I could attach LLDB automatically and locate the line of code that crashed directly when I did not attach:

  1. The biggest problem with the official review of the code was the use of a global static mapstd::map<Pair<QueueId, QueueId>, SharedThreadMerger>A class of static variables that can be used to merger a platform&raster pair. The Google c++ specification makes it a non-trivial type to save as a global variable.Google. Making. IO/styleguide /…

Finally, this lifecycle problem is solved by using a merger as a member variable of a Shell class.

  1. [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost] [root@localhost

Then Windows crash stack does not print to Terminal by default: Failure information on Google’s Luci platform:

You can see there are no logs at all.

Confused half a day final decision: install a Windows VIRTUAL machine! Magically something happened. I compiled and ran my test on Windows 10 + Flutter Engine and everything passed. Surprise! Finally, through the dichotomy method, we found that a unittest amendment caused problems.

Question: Can you see why Windows has a problem with the following code?

/// A mock task queue NOT calling MessageLoop->Run() in thread
struct 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(a); loop = &fml::MessageLoop::GetCurrent(a); latch.Signal(a); term.Wait(a); }) { latch.Wait(a); }/ /.. Omit destructors, term.signal (), thread.join(), and so on
};
Copy the code

  1. After running two webView demos, click the following keyboard and there will be a crash:

The result is that the Java layer resize the FlutterImageView to create an ImageReader with a width of 0. Android does not allow the creation of an ImageReader with a width of 0:

So there is another bugfix submission, which has been merged into the official 😂

Github.com/flutter/eng…

Final Pull Request

Has been incorporated into the official Flutter engine: github.com/flutter/eng…

A little experience contributing code to the official

  1. If there is no issue, it is better to create an issue and then Pull Request to solve your own issue😂
  2. It is better to include test, even if you change a line of code, you can write test, and they will be relieved to see the test, and it will be better to let the people behind you understand the purpose of your code, otherwise a robot will say you did not test, and tag:

  1. Git_hooks check all types of source code for iOS/Android/c++/gn/git_hooks. If not, generate a diff.

This can also help us to automatically trigger and automatically modify, the command is:

Dart ci/bin/format.dart -f where -f is to make it automatically fix

  1. The official review code is still very strict, such as the modification of function semantics, need to synchronize the modification of docString; Another example is to throw you a c++ specification; Or the code repeats map[key] = value and map[key] values, which can be replaced by iterator. The auto keyword cannot be abused, lambda needs to specify the return type, and so on

conclusion

As the developer of the Flutter Infra team, our team made many performance and stability optimizations during the implementation of the Flutter 2.0 lightweight engine and card solution with our business units. This includes air safety migration, Image Cache sharing, text dynamic alignment, multi-engine support for Platform View, performance optimization for transition animations, large memory optimization, official issues and stability fixes.

While making efforts to support the internal business of Byte, we will also continue to submit some common Pull requests for fixes and optimization solutions to the authorities, so as to build a better Flutter community together with developers around the world.

We also provide external customers with a full Flutter process solution through ByteDance’s enterprise technology service platform volcano Engine, enabling development teams using the Flutter stack to deliver efficient and robust business.

About the Byte Terminal technology team

Bytedance Client Infrastructure is a global r&d team of big front-end Infrastructure technology (with r&d teams in Beijing, Shanghai, Hangzhou, Shenzhen, Guangzhou, Singapore and Mountain View), responsible for the construction of the whole big front-end Infrastructure of Bytedance. Improve the performance, stability and engineering efficiency of the company’s entire product line; The supported products include but are not limited to Douyin, Toutiao, Watermelon Video, Feishu, Guagualong, etc. We have in-depth research on mobile terminals, Web, Desktop and other terminals.

Now! Client/front-end/server/side intelligent algorithm/test development for global recruitment! Let’s change the world with technology. If you are interested, please contact [email protected], email subject resume – Name – Job intention – Desired city – Phone number.