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

简介: 通过进行了一系列的深度优化后,平均帧率已经达到50帧之上超越了原生的表现, 但卡顿率依然达不到最佳的体验效果,遇到了难以突破的瓶颈和技术挑战,需要进行技术尝试和突破。

一、前言

    淘特在很多业务场景都使用了 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

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

  • Element

    关联 WidgetRenderObject ,生成 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 childManager,
  }) : super(childManager: childManager);
  
  @override
  void performLayout(){
    ...
    //父节点对子节点的布局限制
    final SliverConstraints constraints = this.constraints;
    final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
    final double remainingExtent = constraints.remainingCacheExtent;
    final double targetEndScrollOffset = scrollOffset + remainingExtent;
    final BoxConstraints childConstraints = constraints.asBoxConstraints();
    ...
    insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true);
    ...
    insertAndLayoutChild(childConstraints,after: trailingChildWithLayout,parentUsesSize: true);
    ...
    collectGarbage(leadingGarbage, trailingGarbage);
    ...
  }
}

abstract class RenderSliverMultiBoxAdaptor extends RenderSliver ...{
  @protected
  RenderBox insertAndLayoutChild(BoxConstraints childConstraints, {@required RenderBox after,...}) {
    _createOrObtainChild(index, after: after);
    ...
  }
  
  RenderBox insertAndLayoutLeadingChild(BoxConstraints childConstraints, {@required RenderBox after,...}) {
    _createOrObtainChild(index, after: after);
    ...
  }
  
  @protected
  void collectGarbage(int leadingGarbage, int trailingGarbage) {
    _destroyOrCacheChild(firstChild);
    ...
  }
  
  void _createOrObtainChild(int index, { RenderBox after }) {
    _childManager.createChild(index, after: after);
    ...
  }
  
  void _destroyOrCacheChild(RenderBox child) {
    if (childParentData.keepAlive) {
      //为了更好的性能表现不会进行keepAlive,走else逻辑.
      ...
    } else {
      _childManager.removeChild(child);
      ...
    }
  }
}

查看 RenderSliverList 的源码发现,对于 child 的创建和移除都是通过其父类 RenderSliverMultiBoxAdaptor 进行。而 RenderSliverMultiBoxAdaptor 是通过 _childManagerSliverMultiBoxAdaptorElement 进行的,整个 SliverList 绘制过程中布局大小由父节点给出了限制。

在流式场景下:

  • 在滑动过程中是通过 SliverMultiBoxAdaptorElement.createChild 进行对进入可视区新的 child 的创建;(即业务场景的每一个item卡片)
  • 在滑动过程中是通过 SliverMultiBoxAdaptorElement.removeChild 进行对不在可视区旧的 child 的移除;

我们来看下 SliverMultiBoxAdaptorElement 的核心源码:

class SliverMultiBoxAdaptorElement extends RenderObjectElement implements RenderSliverBoxChildManager {
  final SplayTreeMap<int, Element> _childElements = SplayTreeMap<int, Element>();

  @override
  void createChild(int index, { @required RenderBox after }) {
    ...
    Element newChild = updateChild(_childElements[index], _build(index), index);
    ...
  }
  
  @override
  void removeChild(RenderBox child) {
    ...
    final Element result = updateChild(_childElements[index], null, index);
    ...
  }
  
  @override
  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    ...
    final Element newChild = super.updateChild(child, newWidget, newSlot);
    ...
  }
}

通过查看 SliverMultiBoxAdaptorElement 的源码可以发现,对于 child 的操作其实都是通过父类 ElementupdateChild 进行的。

接下来,我们来看下 Element 的核心代码:

abstract class Element extends DiagnosticableTree implements BuildContext {
  @protected
  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }
    Element newChild;
    if (child != null) {
      ...
      bool hasSameSuperclass = oldElementClass == newWidgetClass;;
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        newChild = child;
      } else {
        deactivateChild(child);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }
    ...
    return newChild;
  }
  
  @protected
  Element inflateWidget(Widget newWidget, dynamic newSlot) {
    ...
    final Element newChild = newWidget.createElement();
    newChild.mount(this, newSlot);
    ...
    return newChild;
  }
  
  @protected
  void deactivateChild(Element child) {
    child._parent = null;
    child.detachRenderObject(); 
    owner._inactiveElements.add(child); // this eventually calls child.deactivate() & child.unmount()
    ...
  }
}

可以看到主要调用 ElementmountdetachRenderObject,这里我们来看下 RenderObjectElement 的 这两个方法的源码:

abstract class RenderObjectElement extends Element {
  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    ...
    _renderObject = widget.createRenderObject(this);
    attachRenderObject(newSlot);
    ...
  }
  
  @override
  void attachRenderObject(dynamic newSlot) {
    ...
    _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
    _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
    ...
  }
  
  @override
  void detachRenderObject() {
    if (_ancestorRenderObjectElement != null) {
      _ancestorRenderObjectElement.removeChildRenderObject(renderObject);
      _ancestorRenderObjectElement = null;
    }
    ...
  }
}

通过查看上面源码的追溯,可知:

在流式场景下:

  • 在滑动过程中进入可视区新的 child 的创建,是通过创建全新的 Element 并 mount 挂载到 Element Tree;然后创建对应的 RenderObject,调用了 _ancestorRenderObjectElement?.insertChildRenderObject
  • 在滑动过程中不在可视区旧的 child 的移除,将对应的 Element 从 Element Tree unmount 移除挂载;然后调用了 _ancestorRenderObjectElement.removeChildRenderObject

其实这个 _ancestorRenderObjectElement 就是 SliverMultiBoxAdaptorElement,我们再来看下 SliverMultiBoxAdaptorElement:

class SliverMultiBoxAdaptorElement extends RenderObjectElement implements RenderSliverBoxChildManager {
  
  @override
  void insertChildRenderObject(covariant RenderObject child, int slot) {
    ...
    renderObject.insert(child as RenderBox, after: _currentBeforeChild);
    ...
  }
  
  @override
  void removeChildRenderObject(covariant RenderObject child) {
    ...
    renderObject.remove(child as RenderBox);
  }
}

其实调用的都是 ContainerRenderObjectMixin 的方法,我们再来看下 ContainerRenderObjectMixin:

mixin ContainerRenderObjectMixin<ChildType extends RenderObject, ... {
  void insert(ChildType child, { ChildType after }) {
        ...
    adoptChild(child);// attach render object
    _insertIntoChildList(child, after: after);
  }
  
  void remove(ChildType child) {
    _removeFromChildList(child);
    dropChild(child);// detach render object
  }
}

ContainerRenderObjectMixin 维护了一个双向链表来持有当前 children RenderObject,所以在滑动过程中创建和移除都会同步在 ContainerRenderObjectMixin 的双向链表中进行添加和移除。

最后总结下来:

  • 在滑动过程中进入可视区新的 child 的创建,是通过创建全新的 Element 并 mount 挂载到 Element Tree;然后创建对应的 RenderObject, 通过调用 SliverMultiBoxAdaptorElement.insertChildRenderObject attach 到 Render Tree,并同步将 RenderObject 添加到 SliverMultiBoxAdaptorElement 所 mixin 的双链表中;
  • 在滑动过程中不在可视区旧的 child 的移除,将对应的 Element 从 Element Tree unmount 移除挂载;然后通过用 SliverMultiBoxAdaptorElement.removeChildRenderObject 将对应的 RenderObject 从所 mixin 的双链表中移除并同步将 RenderObject 从 Render Tree detach 掉。

渲染原理

通过核心源码的分析,我们可以对流式场景的 Element 做如下分类:

下面我们来看用户向上滑动查看更多商品卡片并触发加载下一页数据进行展示时,整体的渲染流程和机制:

  • 向上滑动时,顶部 0 和 1 的卡片移出 Viewport 区域(Visible Area + Cache Area),我们定义它为进入 Detach Area,进入 Detach Area 后将对应的 RenderObject 从 Render Tree detach 掉,并且将对应的 Element 从 Element Tree unmount 移除挂载,并同步从双向链表中移除;
  • 通过监听 ScrollController 的滑动计算位置来判断是否需要开始加载下一页数据,然后底部 Loading Footer 组件会进入可视区 or 缓存区,需要对 SliverChildBuilderDelegate 的 childCount +1,最后一个 child 返回 Loading Footer 组件,同时调用 setState 对整个 SliverList 刷新。update 会调用 performRebuild 进行重构建,中间部分在用户可视区会全部进行 update 操作;然后创建 Loading Footer 组件对应新的 ElementRenderObject,并同步添加到双向链表中;
  • 当 loading 结束数据返回后,会再次调用 setState 对整个 SliverList 刷新,update 会调用 performRebuild 进行重构建,中间部分在用户可视区会全部进行 update 操作;然后将 Loading Footer 组件将对应的 RenderObject 从 Render Tree detach 掉,并且将对应的 Element 从 Element Tree unmount 移除挂载,并同步从双向链表中移除;
  • 底部新的 item 会进入可视区 or 缓存区,需要创建对应新的 ElementRenderObject,并同步添加到双向链表中;

优化策略

上面用户向上滑动查看更多商品卡片并触发加载下一页数据进行展示的场景,可以从五个方向进行优化:

  • Load More

    通过监听 ScrollController 的滑动不断进行计算,最好无需判断,自动识别到需要加载下一页数据然后发起 loadMore() 回调。新建 ReuseSliverChildBuilderDelegate 增加 loadMore 以及和 item Builder 同级的 footerBuilder,并默认包含 Loading Footer 组件,在 SliverMultiBoxAdaptorElement.createChild(int index,...) 判断是否需要动态回调 loadMore() 并自动构建 footer 组件。

  • 局部刷新

    参考了闲鱼之前在长列表的流畅度优化[2],在下一页数据回来之后调用 setState 对整个 SliverList 刷新,导致中间部分在用户可视区会全部进行 update 操作,实际只需刷新新创建的部分,优化 SliverMultiBoxAdaptorElement.update(SliverMultiBoxAdaptorWidget newWidget) 的部分实现局部刷新,如下图:

  • Element & RenderObject 复用

    参考了闲鱼之前在长列表的流畅度优化[2] 和 Google Android RecyclerView ViewHolder 复用设计[3],在有新的 item 创建时,可以做类似 Android RecyclerViewViewHolder 对组件进行持有并复用。基于对渲染机制原理分析,在 Flutter 中 Widget 其实可以理解为是一个组件树的数据结构,即更多是组件结构的数据表达。我们需要对移除的 item 的 ElementRenderObject 分组件类型进行缓存持有,在创建新的 item 的时候优先从缓存持有中取出进行复用。同时不破坏 Flutter 本身对 Key 的设计,当如果 item 有使用 Key 的时候,只复用和它 Key 相同的 ElementRenderObject。但在流式场景列表数据都是不同的数据,所以在流式场景中使用了 Key,也就无法进行任何的复用。如果对 ElementRenderObject 进行复用,item 组件不建议使用 Key

    我们在对原有流式场景下 Element 的分类增加一个缓存态:

如下图:

  • GC 抑制

    Dart 自身有 GC 的机制,类似 Java 的分代回收,可以在滑动的过程中对 GC 进行抑制,定制 GC 回收的算法。针对这项和 Google 的 Flutter 专家讨论,其实 Dart 不像 Java 会存在多线程切换进行垃圾回收的情况,单线程(主isolate)垃圾回收更快更轻量级,同时需要对 Flutter Engine 做深度的改造,考虑收益不大暂不进行。

  • 异步化

    Flutter Engine 限制非 Main Isolate 调用 Platform 相关 Api,将非跟 Platform Thread 交互的逻辑全部放至新的 isolate 中,频繁 Isolate 的创建和回收也会对性能有一定的影响,Flutter compute<Q, R>(isolates.ComputeCallback<Q, R> callback, Q message, { String debugLabel }) 每次调用会创建新的 Isolate,执行完任务后会进行回收,实现一个类似线程池的 Isolate 来进行处理非视图任务。经过实际测试提升不明显,不展开讲述。

核心技术实现

​我们可以将调用链路的代码做如下分类:


所有渲染核心在继承自 RenderObjectElementSliverMultiBoxAdaptorElement 中,不破坏原有功能设计以及 Flutter Framework 的结构,新增了 ReuseSliverMultiBoxAdaptorElementElement 来进行优化策略的实现,并且可以直接搭配原有 SliverListRenderSliverList 使用或者自定义的流式组件(例如:瀑布流组件)的 RenderObject 使用。

局部刷新

  • 调用链路优化

ReuseSliverMultiBoxAdaptorElementupdate 方法做是否为局部刷新的判断,如果不是局部刷新依然走 performRebuild;如果是局部刷新,只创建新产生的 item。

  • 核心代码
@override
  void update(covariant ReuseSliverMultiBoxAdaptorWidget newWidget) {
    ...
    //是否进行局部刷新
    if(_isPartialRefresh(oldDelegate, newDelegate)) {
        ...
        Widget newWidget = _buildItem(index);
        ...
        _createChild(index, newWidget);
      } else {
         // need to rebuild
         performRebuild();
      }
  }

Element & RenderObject 复用

  • 调用链路优化

    • 创建

ReuseSliverMultiBoxAdaptorElementcreateChild 方法读取 _cacheElements 对应组件类型缓存的 Element 进行复用;如果没有同类型可复用的 Element 则创建对应新的 ElementRenderObject

  • 移除

ReuseSliverMultiBoxAdaptorElementremoveChild 方法将移除的 RenderObject 从双链表中移除,不进行 Element 的 deactive 和 RenderObject 的 detach,并将对应的 Element_slot 更新为null,使下次可以正常复用,然后将对应的 Element 缓存到 _cacheElements 对应组件类型的链表中。
注:不 deactive Element 其实不进行调用即可实现,但不 detach RenderObject 无法直接做到,需要在 Flutter Framework 层的 object.dart 文件中,新增一个方法 removeOnly 就是只将 RenderObject 从双链表中移除不进行 detach。

  • 核心代码

    • 创建
  //新增的方法,createChild会调用到这个方法
  _createChild(int index, Widget newWidget){
      ...
      Type delegateChildRuntimeType = _getWidgetRuntimeType(newWidget);
      child = _takeChild(delegateChildRuntimeType,index);
      ...
      newChild = updateChild(child, newWidget, index);
      ...
  }
  • 移除
  @override
  void removeChild(RenderBox child) {
     ...
     removeChildRenderObject(child); // call removeOnly
     ...
     removeElement = _childElements.remove(index);
     _performCacheElement(removeElement);
 }

Load More

  • 调用链路优化

createChild 时候判断是否是构建 footer 来进行处理。

  • 核心代码
  @override
  void createChild(int index, { @required RenderBox after }) {
      ...
      Widget newWidget;
      if(_isBuildFooter(index)){ // call footerBuilder & call onLoadMore
        newWidget = _buildFooter();
      }else{
        newWidget = _buildItem(index);
      }
      ...
      _createChild(index, newWidget);
      ...
  }

整体结构设计

  • 将核心的优化能力内聚在 Element 层,提供底层能力;
  • ReuseSliverMultiBoxAdaptorWidget 做为基类默认返回优化后的 Element
  • 将 loadMore 和 FooterBuilder 的能力统一由继承自 SliverChildBuilderDelegateReuseSliverChildBuilderDelegate 对上层暴露;

  • 如有自己单独定制的流式组件 Widget ,直接把继承关系从 RenderObjectWidget 换为 ReuseSliverMultiBoxAdaptorWidget 即可,例如自定义的单列表组件(ReuseSliverList)、瀑布流组件(ReuseWaterFall)等。

五、优化成果

基于在之前的一系列深度优化以及切换 Flutter Engine 为UC Hummer 之上,单独控制流式场景的优化变量,使用 PerfDog 获取流畅度数据,进行了流畅度测试对比:


可以看到整体性能数据都有优化提升,结合替换 Engine 之前的测试数据平均来看,对帧率有 2-3 帧的提升,卡顿率下降 1.5 个百分点。

六、总结

使用方式

和原生 SliverList 的使用方式一样,Widget 换成对应可以进行复用的组件 (ReuseSliverList/ReuseWaterFall/ CustomSliverList),delegate 如果需要 footerloadMore 使用 ReuseSliverChildBuilderDelegate;如果不需要直接使用原生的 SliverChildBuilderDelegate 即可。

  • 需要分页场景
return ReuseSliverList( // ReuseWaterFall or CustomSliverList
  delegate: ReuseSliverChildBuilderDelegate(
    (BuildContext context, int index) {
      return getItemWidget(index);
    }, 
    //构建footer
    footerBuilder: (BuildContext context) {
      return DetailMiniFootWidget();
    },
    //添加loadMore监听
    addUnderFlowListener: loadMore,
    childCount: dataOfWidgetList.length
  )
);
  • 无需分页场景
return ReuseSliverList( // ReuseWaterFall or CustomSliverList
  delegate: SliverChildBuilderDelegate(
    (BuildContext context, int index) {
      return getItemWidget(index);
    }, 
    childCount: dataOfWidgetList.length
  )
);

注意点

使用的时候 item/footer 组件不要加 Key,否则认为只对同 Key 进行复用。因为复用了 Element,虽然表达组件树数据结果的 Widget 会每次进行更新,但 StatefulElementState 是在 Element 创建的时候生成的,同时也会被复用下来,和 Flutter 本身设计保持一致,所以需要在 didUpdateWidget(covariant T oldWidget)State 缓存的数据重新从 Widget 获取即可。

Reuse Element Lifecycle

将每个 item 的状态进行回调,上层可以做逻辑处理和资源释放等,例如之前在 didUpdateWidget(covariant T oldWidget)State 缓存的数据重新从 Widget 获取可以放置在 onDisappear里或者自动播放的视频流等;

/// 复用的生命周期
mixin ReuseSliverLifeCycle{

  // 前台可见的
  void onAppear() {}

  // 后台不可见的
  void onDisappear() {}
}

七、参考资料

[[1]:Google Flutter 团队 Xiao Yu:Flutter Performance Profiling and Theory](https://files.flutter-io.cn/events/gdd2018/Profiling_your_Flutter_Apps.pdf)
[[2]:闲鱼 云从:他把闲鱼APP长列表流畅度翻了倍](https://mp.weixin.qq.com/s/dlOQ3Hw_U3CFQM91vcTGWQ)
[[3]:Google Android RecyclerView.ViewHolder:RecyclerView.Adapter#onCreateViewHolder](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.Adapter#onCreateViewHolder(android.view.ViewGroup,%20int))

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
8月前
|
缓存 监控 前端开发
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
【4月更文挑战第30天】本文探讨了Flutter应用启动优化策略,包括理解启动过程、资源加载优化、减少初始化工作、界面布局简化、异步初始化、预加载关键数据、性能监控分析以及案例和未来优化方向。通过这些方法,可以缩短启动时间,提升用户体验。使用Flutter DevTools等工具可助于识别和解决性能瓶颈,实现持续优化。
302 0
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
|
8月前
|
JSON Dart 安全
Flutter App混淆加固、保护与优化原理
Flutter App混淆加固、保护与优化原理
135 0
|
存储 JSON 数据库
Flutter必备技能:轻松掌握本地存储与数据库优化技巧!
Flutter必备技能:轻松掌握本地存储与数据库优化技巧!
187 0
|
2月前
|
缓存 前端开发 数据安全/隐私保护
Flutter 框架提供了丰富的机制和方法来优化键盘处理和输入框体验
在移动应用开发中,Flutter 框架提供了丰富的机制和方法来优化键盘处理和输入框体验。本文深入探讨了键盘的显示与隐藏、输入框的焦点管理、键盘类型的适配、输入框高度自适应、键盘遮挡问题处理及性能优化等关键技术,结合实例分析,旨在帮助开发者提升应用的用户体验。
51 6
|
4月前
|
传感器 缓存 监控
Stream 组件在 Flutter 中的应用场景有哪些?
Stream 组件在 Flutter 中的应用场景有哪些?
200 58
|
6月前
|
机器人 开发工具 Android开发
flutter web 优化和flutter_admin_template
flutter web 优化和flutter_admin_template
|
8月前
|
前端开发 UED 开发者
【Flutter前端技术开发专栏】Flutter中的列表与滚动视图优化
【4月更文挑战第30天】Flutter开发中,优化列表和滚动视图至关重要。本文介绍了几种优化方法:1) 使用`ListView.builder`和`GridView.builder`实现懒加载;2) 复用子组件以减少实例创建;3) 利用`CustomScrollView`和`Slivers`提升滚动性能;4) 通过`NotificationListener`监听滚动事件;5) 使用`KeepAlive`保持列表项状态。掌握这些技巧能提升应用性能和用户体验。
115 1
【Flutter前端技术开发专栏】Flutter中的列表与滚动视图优化
|
8月前
|
存储 缓存 前端开发
【Flutter前端技术开发专栏】Flutter中的图片加载与缓存优化
【4月更文挑战第30天】本文探讨了 Flutter 中如何优化图片加载与缓存,以提升移动应用性能。通过使用图片占位符、压缩裁剪、缓存策略(如`cached_network_image`插件)以及异步加载和预加载图片,可以显著加快加载速度。此外,利用`FadeInImage`、`FutureBuilder`和图片库等工具,能进一步改善用户体验。优化图片处理是提升Flutter应用效率的关键,本文为开发者提供了实用指导。
710 0
【Flutter前端技术开发专栏】Flutter中的图片加载与缓存优化
|
8月前
|
缓存 前端开发 数据安全/隐私保护
【Flutter 前端技术开发专栏】Flutter 中的键盘处理与输入框优化
【4月更文挑战第30天】本文探讨了Flutter中键盘处理与输入框优化的关键技术,包括监听键盘显示隐藏、焦点管理、键盘类型适配、输入框高度自适应、处理键盘遮挡问题及性能优化。通过使用WidgetsBindingObserver、FocusNode和TextInputType等工具,开发者能提升用户体验,确保输入框在各种场景下的良好表现。实例分析和实践建议有助于开发者将这些方法应用于实际项目。
391 0
【Flutter 前端技术开发专栏】Flutter 中的键盘处理与输入框优化
|
8月前
|
开发框架 前端开发 JavaScript
【专栏】对比分析两种流行的跨平台开发框架——Flutter和React Native,探讨它们的优势、劣势以及适用场景
【4月更文挑战第27天】本文对比分析了Flutter和React Native两大跨平台移动开发框架。Flutter,由Google推出,以其接近原生的性能、快速启动和流畅滚动受青睐,适合高性能和高度定制的项目。React Native,Facebook维护,依赖JavaScript,虽性能受限,但热重载优势和丰富第三方库使其适合快速迭代的项目。两者都在拓展多平台应用,Flutter在桌面和Web,React Native在Windows。选择框架需考虑项目需求、团队技能和性能效率平衡。
382 1