关于程序员:从架构到源码一文了解Flutter渲染机制

6次阅读

共计 14198 个字符,预计需要花费 36 分钟才能阅读完成。

简介: Flutter 从实质上来讲还是一个 UI 框架,它解决的是一套代码在多端渲染的问题。在渲染管线的设计上更加精简,加上自建渲染引擎,相比 ReactNative、Weex 以及 WebView 等计划,具备更好的性能体验。本文将从架构和源码的角度详细分析 Flutter 渲染机制的设计与实现。较长,同学们可珍藏后再看。

写在后面

跨平台技术因为其一码多端的生产力晋升而体现出微小的生命力,从晚期的 Hybrid App 到 ReactNative/Weex、小程序 / 快利用,再到当初的 Flutter,跨平台技术始终在解决效率问题的根底上最大化的解决性能和体验问题。这也引出了任何跨平台技术都会面临的外围问题:

  • 效率:解决在多利用、多平台、多容器上开发效率的问题,一码多端,业务快跑。
  • 性能:解决的是业务的性能和体验问题。

效率作为跨平台技术的基本功能,大家都能做到。问题是谁能把性能和体验做得更好,在渲染技术这块一共有三种计划:

  • WebView 渲染:依赖 WebView 进行渲染,在性能和性能上有斗争,例如 PhoneGap、Cordova、小程序(有的小程序底层也采纳了 ReactNative 等渲染计划)等。
  • 原生渲染:下层拥抱 W3C,通过中间层把前端框架翻译为原生控件,例如 ReactNative+React、Weex+Vue 的组合,这种计划多了一层转译层,性能上有损耗。随着原生零碎的降级,在兼容性上也会有问题。
  • 自建渲染:自建渲染框架,底层应用 Skia 等图形库进行渲染,例如 Flutter、Unity。

Flutter 因为其自建渲染引擎,贴近原生的实现形式,取得了优良的渲染性能。

Flutter 领有本人的开发工具,开发语言、虚拟机,编译机制,线程模型和渲染管线,和 Android 相比,它也能够看做一个小型的 OS 了。

第一次接触 Flutter,能够看看 Flutter 的创始人 Eric 之前的访谈《What is Flutter?》,Eric 之前致力于 Chromium 渲染管线的设计与开发,因而 Flutter 的渲染与 Chromium 有肯定的相似之处,前面咱们会做下类比。

前面咱们会从架构和源码的角度剖析 Flutter 渲染机制的设计与实现,在此之前也能够先看看 Flutter 官网对于渲染机制的分享《How Flutter renders Widgets》。视频 + 图文的形式会更加直观,能够有一个大体的了解。

架构剖析

架构设计

从构造上看,Flutter 渲染由 UI Thread 与 GPU Thread 相互配合实现。

1)UI Thread

对应图中 1 -5,执行 Dart VM 中的 Dart 代码(蕴含应用程序和 Flutter 框架代码),次要负责 Widget Tree、Element Tree、RenderObject Tree 的构建,布局、以及绘制生成绘制指令,生成 Layer Tree(保留绘制指令)等工作。

2)GPU Thread

对应图中 6 -7,执行 Flutter 引擎中图形相干代码(Skia),这个线程通过与 GPU 通信,获取 Layer Tree 并执行栅格化以及合成上屏等操作,将 Layer Tree 显示在屏幕上。

 注:图层树(Layer Tree)是 Flutter 组织绘制指令的形式,相似于 Android Rendering 里的 View DisplayList,都是组织绘制指令的一种形式。

UI Thread 与 GPU Thread 属于生产者和消费者的角色。

流程设计

咱们晓得 Android 上的渲染都是在 VSync 信号驱动下进行的,Flutter 在 Android 上的渲染也不例外,它会向 Android 零碎注册并期待 VSync 信号,等到 VSync 信号到来当前,调用沿着 C ++ Engine->Java Engine,达到 Dart Framework,开始执行 Dart 代码,经验 Layout、Paint 等过程,生成一棵 Layer Tree,将绘制指令保留在 Layer 中,接着进行栅格化和合成上屏。

具体说来:

1)向 Android 零碎注册并期待 VSync 信号

Flutter 引擎启动时,会向 Android 零碎的 Choreographer(治理 VSync 信号的类)注册并接管 VSync 信号的回调。

2)接管到 VSync 信号,通过 C ++ Engine 向 Dart Framework 发动渲染调用

当 VSync 信号产生当前,Flutter 注册的回调被调用,VsyncWaiter::fireCallback() 办法被调用,接着会执行 Animator::BeiginFrame(),最终调用到 Window::BeginFrame() 办法,WIndow 实例是连贯底层 Engine 和 Dart Framework 的重要桥梁,基本上与平台相干的操作都会通过 Window 实例来连贯,例如 input 事件、渲染、无障碍等。

3)Dart Framework 开始在 UI 线程执行渲染逻辑,生成 Layer Tree,并将栅格化工作 post 到 GPU 线程执行

Window::BeiginFrame() 接着调用,执行到 RenderBinding::drawFrame() 办法,这个办法会去驱动 UI 界面上的 dirty 节点(须要重绘的节点)进行从新布局和绘制,如果渲染过程中遇到图片,会先放到 Worker Thead 去加载和解码,而后再放到 IO Thread 生成图片纹理,因为 IO Thread 和 GPI Thread 共享 EGL Context,因而 IO Thread 生成的图片纹理能够被 GPU Thread 间接拜访。

4)GPU 线程接管到 Layer Tree,进行栅格化以及合成上屏的工作

Dart Framework 绘制实现当前会生成绘制指令保留在 Layer Tree 中,通过 Animator::RenderFrame() 把 Layer Tree 提交给 GPU Thread,GPU Thread 接着执行栅格化和上屏显示。之后通过 Animator::RequestFrame() 申请接管零碎的下一次 VSync 信号,如此周而复始,驱动 UI 界面不断更新。

一一调用流程比拟长,然而外围点没多少,不必纠结调用链,抓住要害实现即可,咱们把外面波及到的一些次要类用色彩分了个类,对着这个类图,根本能够摸清 Flutter 的脉络。


绿色:Widget 黄色:Element 红色:RenderObject

以上便是 Flutter 渲染的整体流程,会有多个线程配合,多个模块参加,抛开简短的调用链,咱们针对每一步来具体分析。咱们在剖析构造时把 Flutter 的渲染流程分为了 7 大步,Flutter 的 timeline 也能够清晰地看到这些流程,如下所示:

UI Thread

1)Animate

由 handleBeiginFrame() 办法的 transientCallbacks 触发,如果没有动画,则该 callback 为空;如果有动画,则会回调 Ticker.tick() 触发动画 Widget 更新下一帧的值。

2)Build

由 BuildOwner.buildScope() 触发,次要用来构建或者更新三棵树,Widget Tree、Element Tree 和 RenderObject Tree。

3)Layout

由 PipelineOwner.flushLayout() 触发,它会调用 RenderView.performLayout(),遍历整棵 Render Tree,调用每个节点的 layout(),依据 build 过程记录的信息,更新 dirty 区域 RenderObject 的排版数据,使得每个 RenderObject 最终都能有正确的大小(size)和地位(position,保留在 parentData 中)。

4)Compositing Bits

由 PipelineOwner.flushCompositingBits() 触发,更新具备 dirty 合成地位的渲染对象,此阶段每个渲染对象都会理解其子项是否须要合成,在绘制阶段应用此信息抉择如何实现裁剪等视觉效果。

5)Paint

由 PipeOwner.flushPaint() 触发,它会调用 RenderView.paint()。最终触发各个节点的 paint(),最终生成一棵 Layer Tree,并把绘制指令保留在 Layer 中。

6)Submit(Compositing)

由 renderView.compositeFrame() 办法触发,这个中央官网的说法叫 Compositing,不过我感觉叫 Compositing 有歧义,因为它并不是在合成,而是把 Layer Tree 提交给 GPU Thread,因此我感觉叫 Submit 更适合。

GPU Thread

7)Compositing

由 Render.compositeFrame() 触发,它通过 Layer Tree 构建一个 Scene,传给 Window 进行最终的光栅化。

GPU Thread 通过 Skia 向 GPU 绘制一帧数据,GPU 将帧信息保留在 FrameBuffer 里,而后依据 VSync 信号周期性的从 FrameBuffer 取出帧数据交给显示器,从而显示出最终的界面。

Rendering Pipeline

Flutter 引擎启动时,向 Android 零碎的 Choreographer 注册并接管 VSync 信号,GPU 硬件产生 VSync 信号当前,零碎便会触发回调,并驱动 UI 线程进行渲染工作。

1 Animate

触发办法:由 handleBeiginFrame() 办法的 transientCallbacks 触发

Animate 在 handleBeiginFrame() 办法里由 transientCallbacks 触发,如果没有动画,则该 callback 为空;如果有动画,则会回调 Ticker._tick() 触发动画 Widget 更新下一帧的值。

void handleBeginFrame(Duration rawTimeStamp) {
    ...
    try {
      // TRANSIENT FRAME CALLBACKS
      Timeline.startSync('Animate', arguments: timelineWhitelistArguments);
      _schedulerPhase = SchedulerPhase.transientCallbacks;
      final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
      _transientCallbacks = <int, _FrameCallbackEntry>{};
      callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {if (!_removedIds.contains(id))
          _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
      });
      ...
    } finally {...}
  }

handleBeiginFrame() 解决实现当前,接着调用 handleDrawFrame(),handleDrawFrame() 会触发以下回调:

  • postFrameCallbacks 用来告诉监听者绘制曾经实现。
  • pesistentCallbacks 用来触发渲染。

这两个回调都是 SchedulerBinding 外部的回调队列,如下所示:

  • _transientCallbacks:用于寄存一些长期回调,目前是在 Ticker.scheduleTick() 中注册,用来驱动动画。
  • _persistentCallbacks:用来寄存一些长久回调,不能在此回调中再申请新的绘制帧,长久回调一经注册就不嫩嫩移除,RenderBinding.initInstaces().addPersitentFrameCallback() 增加了一个长久回调,用来触发 drawFrame()。
  • _postFrameCallbacks:在 Frame 完结时会被调用一次,调用后会被移除,它次要是用来告诉监听者这个 Frame 曾经实现。

接着会调用 WidgetBinder.drawFrame() 办法,它会先调用会先调用 BuildOwner.buildScope() 触发树的更新,而后才进行绘制。

@override
void drawFrame() {
  ...
  try {if (renderViewElement != null)
      buildOwner.buildScope(renderViewElement);
    super.drawFrame();
    buildOwner.finalizeTree();} finally {assert(() {
      debugBuildingDirtyElements = false;
      return true;
    }());
  }
  ...
}

接着调用 RenderingBinding.drawFrame() 触发 layout、paingt 等流程。

void drawFrame() {assert(renderView != null);
  pipelineOwner.flushLayout();
  pipelineOwner.flushCompositingBits();
  pipelineOwner.flushPaint();
  if (sendFramesToEngine) {renderView.compositeFrame(); // this sends the bits to the GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
    _firstFrameSent = true;
  }
}

以上便是外围流程代码,咱们接着来 Build 的实现。

2 Build

触发办法:由 BuildOwner.buildScope() 触发。

咱们下面说到,handleDrawFrame() 会触发树的更新,事实上 BuildOwner.buildScope() 会有两种调用机会:

  • 树构建(利用启动时):咱们下面提到的 runApp() 办法调用的 scheduleAttachRootWidget() 办法,它会构建 Widgets Tree、Element Tree 与 RenderObject Tree 三棵树。
  • 树更新(帧绘制与更新时):这里不会从新构建三棵树,而是只会更新 dirty 区域的 Element。

也即是说树的构建和更新都是由 BuildOwner.buildScope() 办法来实现的。它们的差异在于树构建的时候传入了一个 element.mount(null, null) 回调。在 buildScope() 过程中会触发这个回调。

这个回调会构建三棵树,为什么会有三棵树呢,因为 Widget 只是对 UI 元素的一个形象形容,咱们须要先将其 inflate 成 Element,而后生成对应的 RenderObject 来驱动渲染,如下所示:

  • Widget Tree:为 Element 形容须要的配置,调用 createElement 办法创立 Element,决定 Element 是否须要更新。Flutter 通过查分算法比对 Widget 树前后的变动,来决定 Element 的 State 是否扭转。
  • Element Tree:示意 Widget Tree 特定地位的一个实例,调用 createRenderObject 创立 RenderObject,同时持有 Widget 和 RenderObject,负责管理 Widget 的配置和 RenderObjec 的渲染。Element 的状态由 Flutter 保护,开发人员只须要保护 Widget 即可。
  • RenderObject Tree:RenderObject 绘制,测量和绘制节点,布局子节点,解决输出事件。

3 Layout

触发办法:由 PipelineOwner.flushLayout() 触发。

  • 相干文档:Understanding constraints
  • 相干源码:PipelineOwner.flushLayout()

Layout 是基于单向数据流来实现的,父节点向子节点传递束缚(Constraints),子节点向父节点传递大小(Size,保留在父节点的 parentData 变量中)。先深度遍历 RenderObject Tree,而后再递归遍历束缚。单向数据流让布局流程变得更简略,性能也更好。

对于 RenderObject 而言,它只是提供了一套根底的布局协定,没有定义子节点模型、坐标零碎和具体的布局协定。它的子类 RenderBox 则提供了一套笛卡尔坐标体系(和 Android&iOS 一样),大部分 RenderObject 类都是间接继承 RenderBox 来实现的。RenderBox 有几个不同的子类实现,它们各自对应了不同的布局算法。

  • RenderFlex:弹性布局,这是一种很常见的布局形式,它对应的是 Widget 组件 Flex、Row 和 Column。对于这一块的布局算法代码正文里有形容,也能够间接看这篇文章的解释。
  • RenderStack:栈布局。

咱们再来聊聊 Layout 流程中波及的两个概念边界束缚(Constraints)和从新布局边界(RelayoutBoundary)。

边界束缚(Constraints):边界束缚是父节点用来限度子节点的大小的一种形式,例如 BoxConstraints、SliverConstraints 等。

RenderBox 提供一套 BoxConstraints,如图所示,它会提供以下限度:

  • minWidth
  • maxWidth
  • minHeight
  • maxHeight

利用这种简略的盒模型束缚,咱们能够非常灵活的实现很多常见的布局,例如齐全和父节点一样的大小,垂直布局(宽度和父节点一样大)、程度布局(高度和父容器一样大)。

通过 Constraints 和子节点本人配置的大小信息,就能够最终算出子节点的大小,接下来就须要计算子节点的地位。子节点的地位是由父节点来决定的。

从新布局边界(RelayoutBoundary):为一个子节点设置从新布局边界,这样当它的大小发生变化时,不会导致父节点从新布局,这是个标记位,在标记 dirty 的 markNeedsLayout() 办法中会查看这个标记位来决定是否从新进行布局。

从新布局边界这种机制晋升了布局排版的性能。

通过 Layout,咱们理解了所有节点的地位和大小,接下来就会去绘制它们。

4 Compositing Bits

触发办法:由 PipelineOwner.flushCompositingBits() 触发。

在 Layout 之后,在 Paint 之前会先执行 Compositing Bits,它会查看 RenderObject 是否须要重绘,而后更新 RenderObject Tree 各个节点的 needCompositing 标记。如果为 true,则须要重绘。

5 Paint

触发办法:由 PipeOwner.flushPaint() 触发。

相干源码:

  • Dart 层调用入口:painting.dart
  • C++ 层实现:canvas.cc

咱们晓得古代的 UI 零碎都会进行界面的图层划分,这样能够进行图层复用,缩小绘制量,晋升绘制性能,因而 Paint(绘制)的外围问题还是解决绘制命令应该放到哪个图层的问题。

Paint 的过程也是单向数据流,先向下深度遍历 RenderObject Tree,再递归遍历子节点,遍历的过程中会决定每个子节点的绘制命令应该放在那一层,最终生成 Layer Tree。

和 Layout 一样,为了提到绘制性能,绘制阶段也引入了从新绘制边界。

从新绘制边界(RepaintBoundary):为一个子节点设置从新绘制边界,这样当它须要从新绘制时,不会导致父节点从新绘制,这是个标记位,在标记 dirty 的 markNeedsPaint() 办法中会查看这个标记位来决定是否从新进行重绘。

事实上这种重绘边界的机制绝对于把图层分层这个性能凋谢给了开发者,开发者能够本人决定本人的页面那一块在重绘时不参加重绘(例如滚动容器),以晋升整体页面的性能。从新绘制边界会扭转最终的图层树(Layer Tree)构造。

当然这些重绘边界并不都须要咱们手动搁置,大部分 Widget 组件会主动搁置重绘边界(主动分层)。

设置了 RepaintBoundary 的就会额定生成一个图层,其所有的子节点都会被绘制在这个新的图层上,Flutter 中应用图层来形容一个档次上(一个绘制指令缓冲区)的所有 RenderObject,根节点的 RenderView 会创立 Root Layer,并且蕴含若干个子 Layer,每个 Layer 又蕴含多个 RenderObject,这些 Layer 便造成了一个 Layer Tree。每个 RenderObject 在绘制时,会产生相干的绘制指令和绘制参数,并保留在对应的 Layer 上。

相干 Layer 都继承 Layer 类,如下所示:

  • ClipRectLayer:矩形裁剪层,能够指定裁剪和矩形行为参数。共有 4 种裁剪行为,none、hardEdge、antiAlias、antiAliashWithSaveLayer。
  • ClipRRectLayer:圆角矩形裁剪层,行为同上。
  • ClipPathLayer:门路裁剪层,能够指定门路和行为裁剪参数,行为同上。
  • OpacityLayer:通明层,能够指定透明度和偏移(画布坐标系原点到调用者坐标系原点的偏移)参数。
  • ShaderMaskLayer:着色层,能够指定着色器矩阵和混合模式参数。
  • ColorFilterLayer:色彩过滤层,能够指定色彩和混合模式参数。
  • TransformLayer:变换图层,能够指定变换矩阵参数。
  • BackdropFilterLayer:背景过滤层,能够指定背景图参数。
  • PhysicalShapeLayer:物理性状层,能够指定色彩等八个参数。

具体能够参考文章上方的 Flutter 类图。

聊完了绘制的基本概念,咱们再来看看绘制的具体流程,下面提到渲染第一帧的时候,会从根节点 RenderView 开始,一一遍历所有子节点进行操作。如下所示:

1)创立 Canvas 对象

Canvas 对象通过 PaintCotext 获取,它外部会创立一个 PictureLayer,并通过 ui.PictureRecorder 调用到 C ++ 层创立一个 Skia 的 SkPictureRecorder 的实例,并通过 SkPictureRecorder 创立 SkCanvas,而后将 SkCanvas 返回给 Dart Framework 应用。SkPictureRecorder 能够用来记录生成的绘制命令。

2)通过 Canvas 执行绘制

绘制命令会被 SkPictureRecorder 记录下来。

3)通过 Canvas 完结绘制,筹备进行栅格化

绘制完结后,会调用 Canvas.stopRecordingIfNeeded() 办法,它会接着去调用 C ++ 层的 SkPictureRecorder::endRecording() 办法生成一个 Picture 对象并保留在 PictureLayer 中,Picture 对象蕴含了所有的绘制指令。所有的 Layer 绘制实现,造成 Layer Tree。

绘制实现当前,接着就能够向 GPU Thread 提交 Layer Tree 了。

6 Submit(Compositing)

触发办法:由 renderView.compositeFrame() 办法触发。

  • Dart 层调用入口:compositing.dart widow.dart
  • C++ 层实现:scene.cc scene_builder.cc

注:这个中央官网的说法叫 Compositing,不过我感觉叫 Compositing 有歧义,因为它并不是在合成,而是把 Layer Tree 提交给 GPU Thread,因此我感觉叫 Submit 更适合。

void compositeFrame() {Timeline.startSync('Compositing', arguments: timelineArgumentsIndicatingLandmarkEvent);
    try {final ui.SceneBuilder builder = ui.SceneBuilder();
      final ui.Scene scene = layer.buildScene(builder);
      if (automaticSystemUiAdjustment)
        _updateSystemChrome();
      _window.render(scene);
      scene.dispose();
      assert(() {if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)
          debugCurrentRepaintColor = debugCurrentRepaintColor.withHue((debugCurrentRepaintColor.hue + 2.0) % 360.0);
        return true;
      }());
    } finally {Timeline.finishSync();
    }
  }
  • 创立 SceneBuilder 对象,并通过 SceneBuilder.addPicture() 将上文中生成的 Picture 增加到 SceneBuilder 对象对象中。
  • 通过 SceneBuilder.build() 办法生成 Scene 对象,接着会通过 window.render(scene) 将蕴含绘制指令的 Layer Tree 提交给 CPU 线程进行光栅化和合成。

在这个过程中 Dart Framework 层的 Layer 会被转换为 C ++ 层应用的 flow::layer,Flow 模块是一个基于 Skia 的简略合成器,运行在 GPU 线程,并向 Skia 上传指令信息。Flutter Engine 应用 flow 缓存 Paint 阶段生成的绘制指令和像素信息。咱们在 Paint 阶段的 Layer,它们都与 Flow 模块里的 Layer 一一对应。

Graphics Pipeline

7 Raster&Compositing

有了蕴含渲染指令的 Layer Tree 当前就能够进行光栅化和合成了。

光栅化是把绘制指令转换成对应的像素数据,合成是把各图层栅格化后的数据进行相干的叠加和个性解决。这个流程称为 Graphics Pipeline。

相干代码:rasterizer.cc

Flutter 采纳的是同步光栅化。什么是同步光栅化?

同步光栅化:

光栅化和合成在一个线程,或者通过线程同步等形式来保障光栅化和合成的的程序。

间接光栅化:间接执行可见图层的 DisplayList 中可见区域的绘制指令进行光栅化,在指标 Surface 的像素缓冲区上生成像素的色彩值。
间接光栅化:为指定图层调配额定的像素缓冲区(例如 Android 提供 View.setLayerType 容许利用为指定 View 提供像素缓冲区,Flutter 提供了 Relayout Boundary 机制来为特定图层调配额定缓冲区),该图层光栅化的过程中会先写入本身的像素缓冲区,渲染引擎再将这些图层的像素缓冲区通过合成输入到指标 Surface 的像素缓冲区。

异步分块光栅化:

图层会依照肯定的规定粉尘同样大小的图块,光栅化以图块为单位进行,每个光栅化工作执行图块区域内的指令,将执行后果写入分块的像素缓冲区,光栅化和合成不在一个线程内执行,并且不是同步的。如果合成过程中,某个分块没有实现光栅化,那么它会保留空白或者绘制一个棋盘格图形。

Android 和 Flutter 采纳同步光栅化策略,以间接光栅化为主,光栅化和合成同步执行,在合成的过程中实现光栅化。而 Chromium 采纳异步分块光栅化测量,图层会进行分块,光栅化和合成异步执行。

从文章上方的序列图能够看到,光栅化的入口是 Rasterizer::DoDraw() 办法。它会接着调用 ScopedFrame::Raster() 办法,这个办法就是光栅化的外围实现,它次要实现以下工作:

  • LayerTree::Preroll():解决绘制前的一些筹备工作。
  • LayerTree::Paint():嵌套调用不通 Layer 的绘制办法。
  • SkCanvas::Flush():将数据 flush 给 GPU。
  • AndroidContextGL::SwapBuffers():替换帧缓存给显示器显示。

到这里咱们 Flutter 整体的渲染实现咱们就剖析完了。

Android、Chromium 与 Flutter

Android、Chromium、Flutter 都作为 Google 家的明星级我的项目,它们在渲染机制的设计上既有类似又有不同,借着这个机会咱们对它们做个比拟。

古代渲染流水线的根本设计:

咱们再别离来看看 Android、Chromium 和 Flutter 是怎么实现的。

Android 渲染流水线:

Chromium 渲染流水线:

Flutter 渲染流水线:

互相比拟:

写在最初

最初的最初,谈一谈我对跨平台生态的了解。

跨平台容器生态至多能够分为三个方面:

前端框架生态

前端框架生态间接面向的是业务,它应该具备两个特点:

  • 拥抱 W3C 生态
  • 绝对稳定性

它应该是拥抱 W3C 生态的。W3C 生态是一个凋敝且充满活力的生态,它会倒退的更久更远。试图摈弃 W3C 生态,自建子集的做法很难走的久远。这从微信小程序、Flutter 都推出 for web 系列就能看出端倪。

它应该是绝对稳固的。不能说咱们每换一套容器,前端的业务就须要从新写一遍,例如咱们之前做 H5 容器,起初做小程序容器,因为 DSL 不通,前端要花大力量将业务重写。尽管小程序是一码多端,然而我认为这并没有解决效率问题,次要存在两个问题:

  • 前端的学习成本增加,小程序的 DSL 还算简略,Flutter 的 Widget 体系学习起来就须要花上一点工夫,这些对于团队来说都是老本。
  • 业务代码重写,大量逻辑须要梳理,而且老业务并不一定都适宜迁徙到新容器上,比方小程序原本就是个很轻量的解决方案,然而咱们在下面沉积了很多性能,造成了重大的体验问题。

在这种状况下,业务很难实现疾速奔跑。所以说不论底层容器怎么变,前端的框架肯定是绝对稳固的。而这种稳定性就有赖于容器对立层。

容器对立层

容器对立层是在前端框架和容器层之间的一个层级。它定义了容器提供的根本能力,这些能力就像协定一样,是绝对稳固的。

协定是十分重要的,就像 OpenGL 协定一样,有了 OpenGL 协定,不论底层的渲染计划如何实现,下层的调用是不必变的。对于咱们的业务也是一样,围绕着容器对立层,咱们须要积淀通用的解决方案。

  • 对立 API 解决方案
  • 对立性能解决方案
  • 对立组件解决方案
  • 对立配套设施解决方案
  • 等等

这些货色不能说每搞一套容器,咱们都要大刀阔斧重来一遍,这种做法是有问题的。曾经做过的货色,遇到新的技术就推倒重来,只能阐明以前定义的计划考虑不周全,没有思考积淀对立和扩大的状况。

如果咱们自顾自的一遍遍做着性能反复的技术计划,业务能等着咱们吗。

容器层

容器层的迭代外围是为了在解决效率问题的根底上最大化的解决性能和体验问题。

晚期的 ReactNative 模式解决了效率了问题,然而多了一个通信层(ReactNative 是依附将虚构 DOM 的信息传递给原生,而后原生依据这些布局信息构建对应的原生控件树来实现的原生渲染)存在性能问题,而且这种转译的形式须要适配零碎版本,带来更多的兼容性问题。

微信后续又推出了小程序计划,在我看来,小程序计划不像是一个技术计划,它更像是一个商业解决方案,解决了平台大流量标准治理和散发的问题,给业务方提供通用的技术解决方案,当然小程序底层的渲染计划也是多种多样的。

后起之秀 Flutter 解决的痛点是性能能力,它自建了一套 GUI 零碎,底层间接调用 Skia 图形库进行渲染(与 Android 的机制一样),进而实现了原生渲染。然而它基于开发效率、性能以及本身生态等因素的思考最终抉择了 Dart,这种做法无疑是间接摈弃了凋敝的前端生态,就跨平台容器的倒退历史来看,在解决效率与性能的根底上,最大化的拥抱 W3C 生态,可能是将来最好的方向。Flutter 目前也推出了 Flutter for Web,从它的思路来看,是先买通 Android 与 iOS,再逐渐向 Web 浸透,咱们期待它的体现。

容器技术是动静向前倒退的,咱们往年搞 Flutter,明年可能还会搞其余技术计划。在计划变迁的过程中,咱们须要保障业务疾速平滑的适度,而不是每次大刀阔斧的再来一遍。

随着手机性能的晋升,WebView 的性能也越来越好,Flutter 又为解决性能问题提供了新的思路,一个基础设施欠缺,体验至上,一码多端的跨平台容器生态值得期待。

最初的最初

欢送退出本地生存终端技术部!

本地生存终端技术部隶属于阿里本地生存用户技术部,从事客户端技术研发工作,次要负责本地生存饿了么 App 和 口碑 App 的客户端架构、根底中间件、跨平台技术解决方案,以及账号、首页、全局购物车、收银台、订单列表、红包卡券、直播、短视频等平台化外围业务链路。目前团队规模 50+ 人,咱们依靠阿里弱小的终端技术底盘,以及本地生存的业务土壤,致力于打造最优良的 O2O 技术团队。

招聘本地生存 - 客户端开发专家 / 高级技术专家 - 杭州 / 上海 / 北京,欢迎您的加盟!简历发送至 wushi@alibaba-inc.com

附录

相干平台
[1]Flutter pub.dev
(https://pub.dev/flutter/packages)
相干文档
[1]Flutter 官网文档
(https://flutter.dev/docs/get-started/install/macos)
[2]Flutter for Android developers
(https://flutter.dev/docs/get-started/flutter-for/android-devs)
[3]Flutter Widget Doc
(https://flutter.dev/docs/reference/widgets)
[4]Flutter API Doc(https://api.flutter.dev/)
[5]Dart Doc
(https://dart.dev/guides/language)
相干源码
[1]Dart Framework
(https://github.com/flutter/flutter/tree/master/packages)
[2]Flutter Engine
(https://github.com/flutter/engine)
相干资源
[1]Flutter Render Pipeline
(https://www.youtube.com/watch?v=UUfXWzp0-DU)
[2]How Flutter renders Widgets
(https://www.youtube.com/watch?v=996ZgFRENMs)
[3] 深刻理解 Flutter 的高性能图形渲染 video
(https://www.bilibili.com/video/av48772383)
[4] 深刻理解 Flutter 的高性能图形渲染 ppt
(https://files.flutter-io.cn/events/gdd2018/Deep_Dive_into_Flutter_Graphics_Performance.pdf)

正文完
 0