Flutter multi engine supports platformview and thread merging solution

Byte hopping terminal technology 2021-10-14 06:58:33
flutter multi engine supports platformview


 picture

Abstract

This paper introduces Flutter Multiple engines , Use PlatformView A thread merging problem that cannot be bypassed in the scenario , And its final solution . Final Pull Request already merge To Google official Flutter Warehouse :

https://github.com/flutter/engine/pull/27662

Key points of this paper :

  1. Thread merge , It doesn't really mean that the operating system has any high-level interfaces , You can put two pthread Close , It is flutter Four of the engines Task Runner in , Use one Task Runner Consume and process two at the same time Task Queue Tasks queued in .

  2. Thread merging problem , refer to Flutter The engine has four threads (Platform Threads 、UI Threads 、Raster Threads 、IO Threads ) Among them Platform Threads and Raster Threads are using PlatformView The problem of merging and separating the scenes . The previous official thread merging mechanism , Only one-to-one thread merging is supported , However, a multi engine scenario requires one to many merging and some related supporting logic . For details, please see the following introduction .

  3. About Flutter The four major engines Task Runner You can refer to the official wiki Medium Flutter Engine Threading model :  

    https://github.com/flutter/flutter/wiki/The-Engine-architecture#threading.

  4. The thread merge operation introduced in this article ( And that's how it works looper The effect of consuming messages from two queues ), See the diagram below , So we can have a preliminary impression :

 picture

One 、 Background introduction

1.1 What is? PlatformView?

First , introduce PlatformView What is it? , In fact, it is simply understood as —— Platform related View . in other words , stay Android and iOS The native platform has such controls , But in Flutter Some that have not been implemented in the cross platform control library Widget, These controls we can use Flutter Provided PlatformView The mechanism of , To do a rendering and bridging , And it can be used on the upper layer Flutter To create 、 Control these native View, To ensure that the cross platform interfaces at both ends are unified .
such as WebView, Map control , Third party advertising SDK Wait, these scenes , We have to use PlatformView 了 .
For example , The picture below is Android Upper use PlatformView The mechanism WebView Controls and Flutter Control's mixed rendering effect :
 picture

You can see Android ViewTree There is indeed a WebView.

Here's a Flutter Use WebView Upper layer code example :

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,
        ),
      ),
    );
  }
}

The yellow background content uses WebView Methods , You can see , after WebView Package of plug-ins , Although behind it is Android Platform or iOS Of the platform itself WebView, But it's like using Flutter Widget As convenient as .

Actually in Flutter In the process of historical evolution , about PlatformView There have been two ways to deal with , Namely :

Flutter 1.20 pre-release VirtualDisplay The way , and Flutter 1.20 After that, it is recommended to use HybridComposition The way .

Now the official recommendation HybridComposition Of embedding The way , Can avoid a lot of previous bug And performance issues , I will not repeat the details , Please refer to the official documents .

Official PlatformView Introduction documents : stay Flutter Use the integrated platform view to host your native application Android and iOS View .( Read the link at the end of the article )

1.2 Flutter Engine thread model

To understand thread merging below , First of all, we need to understand Flutter The threading model of the engine .

Flutter Engine Need to provide 4 individual Task Runner, this 4 individual Runner In general, the default corresponds to 4 Operating system threads , The four Runner Threads perform their duties :

 Task Runner                              effect

Platform Task Runner

App The main thread , Used to handle user actions 、 All kinds of news and PlatformChannel , And pass them on to others Task Runner Or from other Task Runner Pass it on .

UI Task Runner

Dart VM The thread on which it runs . function Dart Threads of code , Responsible for generating information to be passed to Flutter Engine layer tree.

GPU Task Runner (Raster Task Runner)

And GPU Processing related threads . It's using Skia The thread related to the final drawing process (OpenGL or Vulkan wait )

IO Task Runner

Execution involves I/O Dedicated threads for accessing time-consuming processes , For example, decoding image files .

As shown in the figure below :

 picture

1.3  Thread merge

About thread merging , We may have the following questions :

  1. Why not platform view When , Two kinds of multi engine work well ?

  2. Why use platform view When ,iOS and Android Both ends , Need to be merge Well , Can you not merge ?

  3. merge in the future , Without using platform view Of flutter In the page , It will be cancelled merge Restore it back ?

Let's analyze the problem with these questions .

1.3.1 Why thread merging ?

Why are you using PlatformView When , Need to put Platform Threads and Raster Threads merge ?

Simply put :

  1. all PlatformView The operation of needs to be carried out in the main thread (Platform Thread means App The main thread ), Otherwise, in the Raster threading PlatformView Of composition And drawing ,Android Framework Non detected App The main thread , Will throw the exception directly ;

  2. Flutter Of Raster Rendering operations and PlatformView The rendering logic is rendered separately , When they are used together, each frame is rendered , Need to synchronize , A relatively simple and direct implementation method is to merge the two task queues , Let only one main thread runner To consume two queues of tasks one by one ;

  3. Skia and GPU Dealing with related operations , In fact, it can be placed in any thread , Merge into App There is no problem for the main thread to perform related operations .

that ,Platform Task Runner Merging in GPU Task Runner after , The main thread takes over and undertakes the original two Runner All the tasks of , Refer to the diagram below :

 picture

We analyze external_view_embedder.cc Related code You can also see Merge operations :

// 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()) {
    //  Here we judge the present frame Is there any platform view, Go straight back if you have
    return PostPrerollResult::kSuccess;
  }
  if (!raster_thread_merger->IsMerged()) { 
    //  If there is platform view And no merger, it merge operation
    // 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;
  }

  //  Extend and update the lease , So that there is no platform view And the lease counter drops to 0 When , Start unmerge operation
  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;
}

in other words , We have two situations , One is the current layers There is no PlatformView , One is to start with PlatformView, We analyze the running state of each of the four threads :

  1. First of all, no PlatformView In the case of , Four big Task Runner The state of :

 Platform / UI / Raster / IO

  1. Use PlatformView In the case of , Four big Task Runner The state of :

 Platform ( At the same time processing Raster Thread's task queue ) / UI / Raster ( idle ) / IO

merge and unmerge operation , As shown in the following figure :

 picture

1.3.2 One runner How to consume two task queues ?

The two key points are :

  1. TaskQueueEntry Class has two member variables , The upstream and downstream of the current queue are recorded  queue_id

  2. stay TaskQueueRunner When you take down a task ( That is to say PeekNextTaskUnlocked function ) Special treatment :

TaskQueueEntry The declaration and documentation of these two members of the 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;
  // ...
};

Take the next task  PeekNextTaskUnlocked  The logic of ( See note ):

// 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 not merge Words , Take your current top Mission
    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();
    //  If merge If you do , Judge according to the mark , Just take two queues top Mission , Compare who is ahead
    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();
}


Two 、 Problems and Analysis

2.1  Problems encountered

We are using the official engine , In the two scenarios of independent multi engine and lightweight multi engine PlatformView when , Have encountered the problem of thread merging .

2.1.1 problem 1: Thread merging under independent multi engine

The first is webview Reported by the business party slardar Breakdown problem , I wrote a unable_to_merge_raster_demo  Example , Then he submitted a to the official issue:

https://github.com/flutter/flutter/issues/78946

in other words , In a separate multi engine , Use platform view When , because raster_thread_merger More than one-to-one consolidation is not supported (merge) The operation fails and an error is reported .

Crumble demo:

https://github.com/eggfly/unable_to_merge_raster_demo

Look at the log, it's a crash , And then one after another native Of SIGABRT collapse , 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/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 frames
Lost connection to device.

2.1.2 problem 2: Thread merging under lightweight multi engine

Flutter 2.0  Version after the introduction of lightweight flutter engines, That is, the lightweight engine , Can pass FlutterEngineGroups and spawn()  Function to generate a lightweight engine , Official lightweight related submissions :

https://github.com/flutter/engine/pull/22975

We're using the official lightweight multiple engine Of sample Code time , Try multi engine add PlatformView, That is to say main.dart Riga webview.

official demo Code :

https://github.com/flutter/samples/tree/master/add_to_app/multiple_flutters

It will run like this Rouri Records , The mistake here Mistakes and problems 1 There is a difference :

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

2.2  Problem analysis

2.2.1 analysis 1: Independent multi engine thread merging problem

problem 1 yes  Flutter 1.22+  The problem with independent engines , I search the code for  https://flutter.cn/docs/development/platform-integration/platform-views among raster_thread_merger.cc Of 48 Line of code :

 picture

When  success == false  It will trigger  SIGABRT, see Merge() When does the function return 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// <---  Here is the return false The only possibility
    }
  }

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

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

  return true;
}

Merge The function looks like putting two task_queue The key logic of merging together , By setting entry->owner_of and subsumed_by To achieve . Refer to the above TaskQueueEntry Class declaration code .

So in owner_subsumed_keys  This vector In the four elements of log  to glance at ,for The intent of the loop is to check owner And upstream and downstream , as well as subsumed Upstream and downstream , Add up these four  id If there is one in any element of, it is not equal to  _kUnmerged  The check will fail , Then do not carry out the following merge And assignment operations , Go straight back to false.

adopt log It can be seen that :

 picture
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.

You can see Merge Called twice , And the second call of 0 The element is 2, Confirmed the above for The occurrence of a cycle is not equal to unmerge In the case of constants .

Among them 2 and 5 The engines 1 And the engine 2 Of raster Threads , adopt

 adb root
 adb shell kill -3 $pid 

Again adb pull /data/anr/trace_00 Pull it out to see the real thread, and you can also see  1.ui, 2.ui, 1.raster, 2.raster, 1.io, 2.io  Such a thread is set with a name ( Yes  pthread_setname  Functions like that ):

 picture

stay Google Search this  Unable to merge the raster and platform threads  You can also find a submission in :

https://github.com/flutter/engine/pull/23733

Submit an introduction saying :

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

So it's doing the first  1 Time  merge  When , Set up  block_merging  Mark , The second and later  merge  The operation will fail and print a log :

 picture

therefore , Officially, it's a  todo, Is to be realized  feature.

2.2.2 analysis 2: Lightweight multi engine thread merging problem

problem 2  yes  Flutter 2.0+  Problems with lightweight engines , Look directly at the lightweight multi engine , Check the source code of the failed line :

 picture

Obviously , Unlike the independent multi engine above , Here we are creating  RasterThreadMerger Of the constructor of  FML_CHECK  The check fails , prove  platform  and  raster  It's already  merge  The state of , So here, too  SIGABRT  And the program exited .

By printing  log  See two engines  platform  and  raster  Of  id Is Shared , engine  1  and   engine  2  Of  platform_queue_id  All are  0,raster_queue_id  All are  2.

2.3 Summary : Many to one mergers are officially unrealized feature

It's easy to reason that , Each engine of multi engine needs a set of four threads , They can choose to share , Or you can choose to create your own independent thread .

We passed the previous  log  Printed  task_queue_id, Analyze the only difference between the two problems :

  1. In question  1( In two separate engines ) This is the case ( Four threads except  platform, The other three threads do not share ):


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

 2.   In question 2( Two lightweight engines ) This is the case ( All four threads share ):


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 relatively speaking , Feeling problem 2 Easier to solve , And we use flutter 2.0 And card schemes , This problem will be encountered soon .

The official lightweight engine has a TODO list , Mark this question as Cleanup The task of :

https://github.com/flutter/flutter/issues/72009

 picture

Officially marked P5 priority :

 picture

Because of business needs, it doesn't wait directly , Let's just implement it ourselves .

3、 ... and 、 Thread merging solution

3.1 Solve problems quickly 2: Solve the problem of lightweight engine

Since under the lightweight engine ,platform Threads and raster Threads are shared , It's just engine and rasterizer The objects are separate , And now the logic is in two engines ,new My own RasterThreadMerger object , Carry on the following merge and unmerge operation . And in merge Whether to do Owns The inspection of .

Then we can simply do these things :

  1. Change to remove Owns() And related thread checks

  2. Share a RasterThreadMerger Object to carry out merge and unmerge operation

  3. Leave that alone lease_term ( lease ) Counter , Leave for subsequent processing

The modification scheme is basically Kunshen ( We Flutter My comrades in arms ) Of prototype Proposal submitted , And add some corner treatment .

Prototype Key modifications to the prototype :

 picture


Each belt title All of them are FlutterView, Finally don't collapse :

Effect screenshots :

 picture

But this is just a prototype , Many state problems and merge We didn't handle the logic well , The questions include :

  1. We can't be like prototypes , At the end of the byte  Flutter Directly in the engine hardcode  Write dead and share a merger object , therefore 2.0 The previous independent multi engine will still have problems

  2. We didn't handle it correctly IsMerged The correct return result of the function

  3. We haven't handled it correctly lease_term The counter of ,lease_term The counter drops to 0 When , should  unmerge

  4. We pretend to have a kind of case: engine 1 need unmerge, But the engine 2 You also need to render platformview, Now 1 Of unmerge Cannot call immediately , Need to wait until all the engines are gone merge When you need it , Let's go platform and raster  Take off

So we need to have a real ultimate solution , Better be able to : Cover two raster meanwhile merge To a platform The situation of , Then contribute to the official .

3.2  Solve the problem thoroughly 1 and 2 ( Final plan )

3.2.1  Solutions

After checking the code  raster_thread_merger  The object is  rasterizer  Of A member :

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

Here are RasterThreadMerger Member functions in classes , We need to modify it into one to many merge in the future , It is also guaranteed to maintain the normal call timing API:

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

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

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

}  // namespace fml

#endif  // FML_SHELL_COMMON_TASK_RUNNER_MERGER_H_

merger At the time of creation , It needs to be considered that it is not supported in some cases merger Need to keep merger Not created ( For example, some unsupported platforms or some unittest):

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

So we have a choice in every engine Respective rasterizer When we created , Change its logic , stay raster_queue_id At the same time , Reuse previous objects , Sounds like a good idea .

3.2.2  Implementation plan

A diagram is drawn to show two cases :

 picture

About when threads are allowed to merge , Under what circumstances are merged diagrams not allowed :

 picture
Another case is not listed , own merge To their own situation : Now the code returns by default true Of .

In a word, it is a queue Can merge multiple queue( There can be multiple downstream ), But one queue There can be no more than one upstream .

The design of this implementation :

  • First and foremost : take TaskQueueEntry Members of the  owner_of from TaskQueueId Change to std::set<TaskQueueId> owner_of , To record all... Of this thread merge Yes  subsumed_id( One to many merge Relationship )

  • The modification of the code is platform independent , It can make Android and iOS Share the same code logic , Ensure that the code in the relevant directories of different platforms has not been changed ( I've done a version of the scheme before , yes Android、iOS We have revised embedder Class logic )

  • Removed the previous code that officially disabled blocking logic ( That is to say revert The official submission :

    https://github.com/flutter/engine/pull/23733

  • To reduce the number of changes to existing code , Put the old RasterThreadMerger  Class as proxy, And introduces a new SharedThreadMerger class , And record in the engine parent_merger, In the engine spawn Get the father engine in the function merger, See if you can share

  • And merge Related method calls ( Include MergeWithLease() 、UnmergeNow()、DecrementLease() 、IsMergeUnsafe() Redirect to SharedThreadMerger In the method , Then use one  std::map<ThreadMergerCaller, int>  To remember Record merge status and lease_term Lease counter

  • take UnMergeNow()  Change to UnMergeNowIfLastOne(), To remember all merge The caller , Calling Rasterizer::Teardown() When , And it's in the last merger When , immediately unmerge, Other situations need to be maintained unmerge state .

  • stay shell_unittest and fml_unittests Added more tests , And in run_tests.py Enable fml_unittests( Previously banned by an official submission , I found that changing any code didn't work , Comparison pit )

3.2.3  Solution related code

  1. TaskQueueEntry Change to std::set Set
class TaskQueueEntry {
 public:
  ///  Omit
  /// 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; //  Turned out to be TaskQueueId owner_of;
    2. PeekNextTaskUnlocked New 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();
  }

  // Use optional for the memory of TopTask object.
  std::optional<TaskSource::TopTask> top_task;
  
  //  Update the current minimum task 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();
          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(); 
    //  Traverse set in subsumed Merged task queue , Update the current minimum task
    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 and unmerge Relevant inspections ( Omit , For details, please refer to Pull Request Code submission in )

3.3  Small pits in the implementation process

  1. As official , Use FlutterFragment The way to embed multiple engines ,FlutterSurfaceView Will give surface Set up ZOrder, At this time, there are many Surface There will be ZOrder Scramble for top The problem of

 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);
    }
    It needs to be created , Get rid of Transparent Of flag, It needs to be changed like this :( This problem has been stuck for a long time , Almost didn't make me give up this submission )
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. stay iOS do unittest When , Found a corresponding crash , There is no collapse stack And details log, It turned out iOS There is a README, Mention the use of xcode Can open unittest engineering , Turn on the simulator automatic test , And found that it can be directly in my absence attach Under the circumstances , Automatically attach lldb And locate the line of code that crashed :

 picture
  1. official review Code time , The biggest problem raised is the use of map Make a global static Of std::map<Pair<QueueId,QueueId>, SharedThreadMerger>  Dictionary static Variable , Used to get platform&raster This one pair Of merger, But the foreigner threw me one google c++  standard , Clearly written non-trivial Can only be saved as a global variable , Official specification documents :

    https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables

Finally, by putting merger As Shell Class to solve this life cycle problem .

  1. It was found during the test that macOS、Linux Of engine Of unopt  Target build and test No problem. , But it's just that windows The engine to test host_debug_unopt Of unittest When , Out directly exit,exitcode No 0 Of

then windows By default, the crash stack will not print to terminal: Google's luci Failure information on the platform :

 picture

What you can see log None .

After a long time of trouble, I finally decided : Put on a windows virtual machine ! Something amazing happened , In my windows 10 + flutter engine Compile and run my test, It's all over . Amazement ! Finally, we can see the revision through the dichotomy , Located a unittest The modification of extraction caused problems .

Leave a title : You can see why the following code  windows Is there a problem ?

/// A mock task queue NOT calling MessageLoop->Run() in thread
struct TaskQueueWrapper {
  fml::MessageLoop* loop = nullptr;

  ///  Here's the question :
  /// 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();
  }
  // ..  Omit the destructor , term.Signal()  and  thread.join()  wait
};
 picture

  1. Run for two webview Of demo in the future , Click the keyboard below , There will be one. crash( The following interface pops up and crashes after the keyboard ):


 picture

 

The result is java Layer pair FlutterImageView Of resize Cause to create ImageReader The width and height of 0 了 ,Android It is not allowed to create width and height 0 Of ImageReader:

 picture

So there's another one  bugfix Submission of , has merge To the official

https://github.com/flutter/engine/pull/27946

3.4  The final Pull Request

Has been incorporated into the official Flutter engine : 

https://github.com/flutter/engine/pull/27662

Four 、 A little experience of contributing code to the official

  1. without issue, It's better to create one  issue, Then mention it yourself Pull Request Solve your own problems issue

  2. Preferably include test, Even if you change a line of code , You can also write test Of , And as soon as they see test I'm relieved , It can also better let the latter people understand the intention of your code , Or a robot will say you didn't test, And label it :

 picture
  1. Now the latest code is git push When , Will pass git_hooks Automatically check all types of source code ( Include iOS/Android/c++/gn wait ) Format and specification of , If it does not meet the specification, directly generate a diff, As a modification suggestion .

This can also help us automatically trigger and modify , The order is :

dart ci/bin/format.dart -f  among -f Is to let it automatically fix

 picture
  1. official review The code is still very strict , For example, the modification of function semantics , Need to synchronize to docstring Make changes ; Another example is to throw you a c++ standard ; Or repeat the code map[key] = value and map[key]  The value of , It can be used iterator Instead of ;auto Keywords cannot be abused ,lambda The return type needs to be specified , wait .

5、 ... and 、 summary

As Flutter Infra Team development , In practice with our business department Flutter 2.0 During the landing of lightweight engine and card scheme , Our team has done a lot of performance and stability optimization , Including empty secure migration 、Image Cache share 、 Text dynamic alignment 、Platform View Multi engine support 、 Transition animation performance optimization 、 Large memory optimization 、 official issue And stability repair .

While trying to support byte internal business , Will also continue to use some of the more general fix And optimization scheme Pull Request Submit to the official , Work with developers around the world to build better Flutter Community .

in addition , We also provide services to external customers through our enterprise level technical service platform volcano engine Flutter Whole process solution , Help use Flutter The development team of the technology stack effectively and steadily landed the business .


Expand Links :

《 stay Flutter Use the integrated platform view to host your native application Android and iOS View 》

https://flutter.cn/docs/development/platform-integration/platform-views



About byte terminal technology team

Byte hop terminal technology team (Client Infrastructure) It is a global R & D team of large front-end basic technology ( In Beijing 、 Shanghai 、 Hangzhou 、 Shenzhen 、 Guangzhou 、 Singapore and mountain view have R & D teams ), Responsible for the whole front-end infrastructure construction , Improve the performance of the company's entire product line 、 Stability and engineering efficiency ; Tiktok products include, but are not limited to, the jitter 、 Today's headline 、 Watermelon Video 、 anonymous letter 、 Guagualong, etc , In mobile terminal 、Web、Desktop And other terminals have in-depth research .
The time is now ! client / front end / Server side / Terminal intelligence algorithm / Test Development   Global recruitment ! Let's change the world with technology , Interested please contact  chenxuwei.cxw@bytedance.com , Email subject : resume - full name - Employment intention - Expect the city - Telephone .

Mobile R & D platform veMARS  It is tiktok based on byte beating in the past nine years 、 Today's headline 、 Watermelon Video 、 anonymous letter 、 Guagualong, etc App Practical achievements in R & D , Precipitate and open . Committed to providing mobile development solutions for developers , Help enterprises reduce costs and increase efficiency , Create high quality 、 High performance and high quality App Experience .


 picture

        ️  Scan the code for more details

       Little assistant wechat :niannian-020

Click on Read the original , Free experience .

版权声明
本文为[Byte hopping terminal technology]所创,转载请带上原文链接,感谢
https://javamana.com/2021/10/20211002145641025B.html

  1. Day17 Java Foundation
  2. Day18 Java Foundation
  3. Linux installe JDK 1.8 et configure les variables d'environnement
  4. Tutoriel d'utilisation Maven super détaillé
  5. Spring boot reads project parameter configuration
  6. Docker installing rocketmq
  7. Java Zero Basic small white Beginner must make a summary of issues (recommended Collection) Chapitre 1
  8. Manuel pour vous apprendre à utiliser le développement Java pour générer des documents PDF en ligne
  9. 40 + comment les femmes s'habillent - elles pour montrer leur jeunesse?Un manteau et une jupe vous donnent un look haut de gamme tout au long de l'automne et de l'hiver
  10. Tutoriel d'installation Ubuntu 16.04 / Hadoop 3.1.3Configuration autonome / pseudo - distribuée
  11. L'apprentissage le plus détaillé de springboot à l'échelle du réseau - day01
  12. L'apprentissage le plus détaillé de springboot sur le Web - day02
  13. L'apprentissage le plus détaillé de springboot sur le Web - day03
  14. L'apprentissage le plus détaillé de springboot sur le Web - day04
  15. Tutoriel d'utilisation Maven super détaillé
  16. L'apprentissage le plus détaillé de springboot sur le Web - day05
  17. L'apprentissage le plus détaillé de springboot sur le Web - day06
  18. L'apprentissage le plus détaillé de springboot sur le Web - day07
  19. Introduction to JavaScript - write a photo album for your girlfriend
  20. [Hadoop 3. X] HDFS storage type and storage strategy (V) overview
  21. L'apprentissage le plus détaillé de springboot sur le Web - day08
  22. Introduction à la page Web de rabbitmq (3)
  23. No Converter found for return value of type: class java.util.arraylist Error Problem
  24. (16) , spring cloud stream message driven
  25. Que faut - il apprendre de l'architecture des microservices Spring Cloud?
  26. Résolution: erreur: Java: distribution cible invalide: 11problème d'erreur
  27. Springboot démarre en une minute et sort de l'enfer de la configuration SSM!
  28. Maven - un outil de gestion essentiel pour les grands projets d'usine, de l'introduction à la maîtrise![️ Collection recommandée]
  29. ️ Push to interview in Large Factory ᥧ - - Spring Boot Automatic Assembly Principle
  30. [️ springboot Template Engine] - thymeleaf
  31. Springboot - MVC Automatic configuration Principle
  32. Mybatis reverse engineering and the use of new version mybatisplus 3.4 reverse engineering
  33. Base de données MySQL - transactions et index
  34. Sécurité du printemps - [authentification, autorisation, déconnexion et contrôle des droits]
  35. Moteur de base de données InnoDB diffère de myisam
  36. Swagger - [springboot Integrated Swagger, configure Swagger, configure scan Interface, configure API Group]
  37. Cadre de sécurité Shiro - [QUICKstart, login Block, User Authentication, request Authorization]
  38. [Introduction à Java] installation de l'environnement de développement - Introduction à Java et construction de l'environnement
  39. 【 linux】 notes d'utilisation tmux
  40. MySQL + mybatis paging query - database series learning notes
  41. Usage relations and differences of count (1), count (*) and count (a field) in MySQL
  42. 2021 Ali Java advanced interview questions sharing, Java Architect interview materials
  43. Mybatis - dynamic SQL statement - if usage - MySQL series learning notes
  44. [go to Dachang series] deeply understand the use of where 1 = 1 in MySQL
  45. [secret room escape game theme ranking list] Based on spring MVC + Spring + mybatis
  46. Redis log: the killer mace of fearless downtime and rapid recovery
  47. 5 minutes to build redis cluster mode and sentinel mode with docker
  48. Java小白入门200例106之遍历ArrayList的几种方式
  49. Java小白入门200例105之Java ArrayList类
  50. Java小白入门200例104之JDK自带记录日志类logging
  51. Practice of high availability architecture of Tongcheng travel network based on rocketmq
  52. Chapter 9 - Linux learning will - file archiving and compression tar --- zip
  53. Java小白入門200例104之JDK自帶記錄日志類logging
  54. JDK avec journalisation de classe dans 200 cas 104
  55. Java ArrayList Class for Introduction to Java LITTLE WHITE 200 example 105
  56. Plusieurs façons de traverser ArrayList à partir de 200 exemples 106
  57. Provectus / Kafka UI: open source Apache Kafka's Web GUI Graphical interface management tool
  58. Design pattern series: Singleton pattern
  59. Java小白入門200例105之Java ArrayList類
  60. Understanding Java record types