Flutter 多引擎支持 PlatformView 以及线程合并解决方案

本文详细探讨了在Flutter多引擎环境中,使用PlatformView时遇到的线程合并问题及其解决方案。作者分析了Flutter引擎的线程模型,提出了一种支持一对多线程合并的方案,解决了独立和轻量级多引擎场景下的问题,并已将解决方案合并到官方Flutter仓库。
摘要由CSDN通过智能技术生成


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

摘要

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

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

本文关键点:

  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/flutter/wiki/The-Engine-architecture#threading
  4. 本文介绍的线程合并操作(也就实现了一个 looper 消费两个队列的消息的效果),见如下的示意图,这样我们可以有个初步的印象:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FIt1i6la-1631187770198)( https://lf3-client-infra.bytetos.com/obj/client-infra-images/eggfly/215a5ce14fe240e88df1f44e8d665887/2021-09-09/0.png)]

背景介绍

什么是 PlatformView?

首先,介绍下 PlatformView 是什么,其实它简单理解成——平台相关的 View 。也就是说,在Android 和 iOS 平台原生有这样的控件,但是在Flutter的跨平台控件库里没有实现过的一些Widget,这些控件我们可以使用Flutter提供的PlatformView的机制,来做一个渲染和桥接,并且在上层可以用Flutter的方法去创建、控制这些原生View,来保证两端跨平台接口统一。

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

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

img

可以看到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 Runner App 的主线程,用于处理用户操作、各类消息和 PlatformChannel ,并将它们传递给其他 Task Runner 或从其他 Task Runner 传递过来。
UI Task Runner Dart VM 运行所在的线程。运行 Dart 代码的线程,负责生成要传递给 Flutter 引擎的 layer tree。
GPU Task Runner (Raster Task Runner) 与 GPU 处理相关的线程。它是使用 Skia 最终绘制的过程相关的线程(OpenGL 或 Vulkan 等等)
IO Task Runner 执行涉及 I/O 访问的耗时过程的专用线程,例如解码图像文件。

如下图所示:

img

线程合并

关于线程合并,我们可能有下面几个疑问:

  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的所有任务,参考下面的示意图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YD2wkTFV-1631187770203)( https://lf3-client-infra.bytetos.com/obj/client-infra-images/eggfly/215a5ce14fe240e88df1f44e8d665887/2021-09-09/3.png)]

我们分析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 操作,可以如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xYWxLtuO-1631187770204)( https://lf3-client-infra.bytetos.com/obj/client-infra-images/eggfly/215a5ce14fe240e88df1f44e8d665887/2021-09-09/4.png)]

一个 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.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) { // 如果没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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值