淘特 Flutter 流式场景的深度优化

作者:江泽军(眞意)

淘特在很多业务场景都使用了 Flutter,加上业务场景本身具有一定的复杂性,使得 Flutter 在低端机流式场景的滑动浏览过程中卡顿、跳帧对比使用原生(Android/iOS)开发明显。通过分析业务层在 Flutter 渲染流程中的每个阶段存在的性能问题进行了一系列的深度优化后,平均帧率已经达到50帧之上超越了原生的表现, 但卡顿率依然达不到最佳的体验效果,遇到了难以突破的瓶颈和技术挑战,需要进行技术尝试和突破。

本文会从底层原理、优化思路、实际场景的优化策略、核心技术实现、优化成果等方面进行讲述,期望可以为大家带来一定的启发和帮助,也欢迎多多交流与指正,共建美好的 Flutter 技术社区。

渲染机制

原生 vs Flutter

Flutter 本身是基于原生系统之上的,所以渲染机制和 Native 是非常接近的,引用 Google Flutter 团队 Xiao Yu分享[1],如下图所示:

渲染流程

如图左中,Flutter 从接收到 VSync 信号之后整体经历 8 个阶段,其中 Compositing 阶段后会将数据提交给GPU。

Semantics 阶段会将 RenderObject marked 需要做语义化更新的信息传递给系统,实现辅助功能,通过语义化接口可以帮助有视力障碍的用户来理解UI内容,和整体绘制流程关联不大。

Finalize Tree 阶段会将所有添加到 _inactiveElements 的不活跃 Element 全部 unmount 掉,和整体绘制流程关联不大。

所以,Flutter 整体渲染流程主要关注 上图图右 中的阶段:

GPU Vsync

Flutter Engine 在收到垂直同步信号后,会通知 Flutter Framework 进行 beginFrame,进入 Animation 阶段。

Animation

主要执行了 transientCallbacks 回调。Flutter Engine 会通知 Flutter Framework 进行 drawFrame,进入 Build 阶段。

Build

构建要呈现的UI组件树的数据结构,即创建对应的 Widget 以及对应的 Element。

Layout

目的是要计算出每个节点所占空间的真实大小进行布局,然后更新所有 dirty render objects 的布局信息。

Compositing Bits

对需要更新的 RenderObject 进行 update 操作。

Paint

生成 Layer Tree,生成 Layer Tree 并不能直接使用,还需要 Compositing 合成为一个 Scene 并进行 Rasterize 光栅化处理。层级合并的原因是因为一般 Flutter 的层级很多,直接把每一层传递给 GPU 效率很低,所以会先做Composite 提高效率。光栅化之后才会交给 Flutter Engine 处理。

Compositing

将 Layout Tree 合成为 Scene,并创建场景当前状态的栅格图像,即进行 Rasterize 光栅化处理,然后提交给Flutter Engine,最后 Skia 通过 Open GL or Vulkan 接口提交数据给 GPU, GPU经过处理后进行显示。

核心渲染阶段

Widget

我们平时在写的大都是 Widget,Widget 其实可以理解为是一个组件树的数据结构,是 Build 阶段的主要部分。其中 Widget Tree 的深度、 StatefulWidget 的 setState 合理性、build 函数中是否有不合理逻辑以及使用了调用saveLayer 的相关Widget往往会成为性能问题。

Element

关联 Widget 和 RenderObject ,生成 Widget 对应的 Element 存放上下文信息,Flutter 通过遍历 Element 来生成RenderObject 视图树支撑UI结构。

RenderObject

RenderObject 在 Layout 阶段确定布局信息,Paint 阶段生成为对应的 Layer,可见其重要程度。所以 Flutter 中大部分的绘图性能优化发生在这里。RenderObject 树构建的数据会被加入到 Engine 所需的 LayerTree 中。

性能优化思路

了解底层渲染机制和核心渲染阶段,可以将优化分为三层:

这里不具体展开讲每一层的优化细节,本文主要从实际的场景来讲述。

流式场景

流式组件原理

在原生开发下,通常使用 RecyclerView/UICollectionView 进行列表场景的开发;在Flutter开发下,Flutter Framework 也提供了ListView的组件,它的实质其实是 SliverList。

核心源码

我们从 SliverList 的核心源码来进行分析:

class SliverList extends SliverMultiBoxAdaptorWidget {

  @override
  RenderSliverList createRenderObject(BuildContext context) {
    final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement;
    return RenderSliverList(childManager: element);
  }
}

abstract class SliverMultiBoxAdaptorWidget extends SliverWithKeepAliveWidget {

  final SliverChildDelegate delegate;

  @override
  SliverMultiBoxAdaptorElement createElement() => SliverMultiBoxAdaptorElement(this);

  @override
  RenderSliverMultiBoxAdaptor createRenderObject(BuildContext context);
}

通过查看 SliverList 的源代码可知,SliverList 是一个 RenderObjectWidget ,结构如下:

我们首先看它的 RenderObject 的核心源码:

class RenderSliverList extends RenderSliverMultiBoxAdaptor {

  RenderSliverList({
    @required RenderSliverBoxChildManager
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值