Flutter渲染流程簡析

做者:騰訊NOW直播 - levinyang(楊亦偉)java

前言

Flutter是谷歌的移動UI框架,能夠快速在iOS和Android上構建高質量的原生用戶界面。 Flutter能夠與現有的代碼一塊兒工做。在全世界,Flutter正在被愈來愈多的開發者和組織使用,而且Flutter是徹底免費、開源的。本文主要講述Flutter整個渲染流程,重點關注在渲染過程當中Framework層,從setState到向Engine提交Layer整個過程是怎麼樣實現的,讓讀者更加深刻理解Flutter的渲染過程。node

渲染框架

Flutter的框架分爲Framework和Engine兩層,應用是基於Framework層開發的,Framework負責渲染中的Build,Layout,Paint,生成Layer等環節。Engine層是C++實現的渲染引擎,負責把Framework生成的Layer組合,生成紋理,而後經過Open GL接口向GPU提交渲染數據。canvas

渲染過程

當須要更新UI的時候,Framework通知Engine,Engine會等到下個Vsync信號到達的時候,會通知Framework,而後Framework會進行animations, build,layout,compositing,paint,最後生成layer提交給Engine。Engine會把layer進行組合,生成紋理,最後經過Open Gl接口提交數據給GPU, GPU通過處理後在顯示器上面顯示。整個流程以下圖:數組

從流程圖能夠看出來,只有當有UI更新的才須要從新渲染,固然程序啓動的是默認去渲染的。

渲染觸發

接下來咱們先分析一下當有UI須要更新的時候,是怎麼樣觸發渲染,從應用到Framework,再到Engine這個過程是怎麼樣的。在Flutter開發應用的時候,當須要更新的UI的時候,須要調用一下setState方法,而後就能夠實現了UI的更新,咱們接下來分析一下該方法作哪些事情。app

void setState(VoidCallback fn) {
   ...
    _element.markNeedsBuild(); //經過相應的element來實現更新,關於element,widget,renderOjbect這裏不展開討論
  }
  
  void markNeedsBuild() {
   ...
    if (dirty)
      return;
    _dirty = true;
    owner.scheduleBuildFor(this);
  }
  
   void scheduleBuildFor(Element element) {
    ...
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled(); //這是一個callback,調用的方法是下面的_handleBuildScheduled
    }
    _dirtyElements.add(element); //把當前element添加到_dirtyElements數組裏面,後面從新build會遍歷這個數組
    element._inDirtyList = true;
    
  }
    void _handleBuildScheduled() {
    ...
    ensureVisualUpdate();
  }
    void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
  }
    void scheduleFrame() {
    if (_hasScheduledFrame || !_framesEnabled)
      return;
    ...
    ui.window.scheduleFrame();
    _hasScheduledFrame = true;
  }
    void scheduleFrame() native 'Window_scheduleFrame';//這個方法是Engine實現的,把接口暴露給Framework,調用這個方法通知引擎,須要更新UI,引擎會在下一個vSync的到達的時候通知Framework
複製代碼

渲染過程

當應用調用setState後,通過Framework一連串的調用後,最終調用scheduleFrame來通知Engine須要更新UI,Engine就會在下個vSync到達的時候經過調用_drawFrame來通知Framework,而後Framework就會經過BuildOwner進行Build和PipelineOwner進行Layout,Paint,最後把生成Layer,組合成Scene提交給Engine。接下來咱們從代碼中分析一下,這些環節具體是怎麼樣實現的。首先從Engine回調Framework的入口開始。框架

void _drawFrame() { //Engine回調Framework入口 
  _invoke(window.onDrawFrame, window._onDrawFrameZone);
}
  //初始化的時候把onDrawFrame設置爲_handleDrawFrame
  void initInstances() {
    super.initInstances();
    _instance = this;
    ui.window.onBeginFrame = _handleBeginFrame;
    ui.window.onDrawFrame = _handleDrawFrame;
    SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
  }
  
  void _handleDrawFrame() {
    if (_ignoreNextEngineDrawFrame) {
      _ignoreNextEngineDrawFrame = false;
      return;
    }
    handleDrawFrame();
  }
  void handleDrawFrame() {

   
      _schedulerPhase = SchedulerPhase.persistentCallbacks;//記錄當前更新UI的狀態
      for (FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);
    }
  }
  void initInstances() {
    ....
    addPersistentFrameCallback(_handlePersistentFrameCallback);
  }
 void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
  }
    void drawFrame() {
    ...
     if (renderViewElement != null)
        buildOwner.buildScope(renderViewElement); //先從新build widget
      super.drawFrame();
      buildOwner.finalizeTree();
      
  }
    void drawFrame() { //這個方法完成Layout,CompositingBits,Paint,生成Layer和提交給Engine的工做
    assert(renderView != null);
    pipelineOwner.flushLayout(); 
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    renderView.compositeFrame(); //生成Layer並提交給Engine
    pipelineOwner.flushSemantics(); 
  }
複製代碼

從上面代碼分析得知,從Engine回調,Framework會build,Layout,Paint,生成Layer等環節。接下來具體分析一下,這些環節是怎麼實現的。less

Build

在Flutter應用開發中,無狀態的widget是經過StatelessWidget的build方法構建UI,有狀態的widget是經過State的build方法構建UI。如今具體分析一下從setState調用後到調用自定義State的build的流程是怎樣的(如今只分析有狀態的widget渲染過程)。ide

//這是官方的demo
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

//這裏就是構建UI,當調用setState後就會調用到這裏,從新生成新的widget
  @override
  Widget build(BuildContext context) {
   
    return new Scaffold(
    ...
    );
  }
}

//從上面代碼的分析到,在調用了setState後,最終會調用到buildScope來build
void buildScope(Element context, [VoidCallback callback]) {
    ...
      _dirtyElements.sort(Element._sort);
      _dirtyElementsNeedsResorting = false;
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      while (index < dirtyCount) {
       ...
         _dirtyElements[index].rebuild();
        index += 1;
      }
      for (Element element in _dirtyElements) {
        assert(element._inDirtyList);
        element._inDirtyList = false;
      }
      _dirtyElements.clear();
  }
    void rebuild() {
   ...
    if (!_active || !_dirty)
      return;
    performRebuild();
   
  }
  void performRebuild() {
    ...
     built = build();
    ...
  }
   Widget build() => widget.build(this);
複製代碼

從上面能夠看出,buildScope會遍歷_dirtyElements,對每一個在數組裏面的每一個element調用rebuild,最終就是調用到相應的widget的build方法。 其實當setState的時候會把相應的element添加到_dirtyElements數組裏,而且element標識dirty狀態。佈局

Layout

在Flutter中應用中,是使用支持layout的widget來實現佈局的,支持layout的wiget有Container,Padding,Align等等,強大又簡易。在渲染流程中,在widget build後會進入layout環節,下面具體分析一下layout的實現,layout入口是flushLayout。post

void flushLayout() {
 ...
 while (_nodesNeedingLayout.isNotEmpty) {
    final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
    _nodesNeedingLayout = <RenderObject>[];
    for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {//這裏是按照在node tree中的深度順序遍歷_nodesNeedingLayout,RenderObject的markNeedsLayout方法會把本身添加到_nodesNeedingLayout
      if (node._needsLayout && node.owner == this)//對於須要layout的RenderObject進行layout
        node._layoutWithoutResize();
    }
  }
  ...
}
void _layoutWithoutResize() {
  ...
  performLayout(); //這個方法是計算layout的實現,不一樣layout widget有不一樣的實現
  markNeedsSemanticsUpdate();
    ...
  _needsLayout = false;
  markNeedsPaint();
}
//這裏就是列出來RenderView的計算佈局的實現方式,這個比較簡單,就是讀取配置裏面的大小,而後調用child的layout,其餘widget layout的計算佈局的方式是很是繁瑣複雜的,能夠自行分析代碼
void performLayout() {
    assert(_rootTransform != null);
    _size = configuration.size;
    assert(_size.isFinite);
    
    if (child != null)
      child.layout(new BoxConstraints.tight(_size));//調用child的layout

}

//這個方法parent調用child的layout的入口,parent會把限制傳給child,child根據限制來layout
void layout(Constraints constraints, { bool parentUsesSize: false }) {
    ...
    RenderObject relayoutBoundary;
    if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
      relayoutBoundary = this;
    } else {
      final RenderObject parent = this.parent;
      relayoutBoundary = parent._relayoutBoundary;
    }
    
    if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {
     
      return;
    }
    _constraints = constraints;
    _relayoutBoundary = relayoutBoundary;
    
    if (sizedByParent) {
        performResize(); 
    }
    RenderObject debugPreviousActiveLayout;
    
    performLayout();//實際計算layout的實現
    markNeedsSemanticsUpdate();
    
    _needsLayout = false;
    markNeedsPaint();
}
void performResize() {
    ...
    size = constraints.biggest;
    
    switch (axis) {
      case Axis.vertical:
        offset.applyViewportDimension(size.height);
        break;
      case Axis.horizontal:
        offset.applyViewportDimension(size.width);
        break;
    }
}

//這是標記爲layout爲dirty,把本身添加到渲染管道(PipelineOwner)裏面
void markNeedsLayout() {
    
    if (_relayoutBoundary != this) {
      markParentNeedsLayout();
    } else {
      _needsLayout = true;
      if (owner != null) {
          return true;
        }());
        owner._nodesNeedingLayout.add(this);
        owner.requestVisualUpdate();
      }
    }
  }
複製代碼

從上面分析出來,layout的整個過程,首先是當RenderOjbect須要從新layout的時候,把本身添加到渲染管道里面,而後再觸發渲染到了layout環節,先從渲染管道里面遍歷找出須要渲染的RenderObject,而後調用performLayout進行計算layout,並且不一樣的對象實現不一樣的performLayout方法,計算layout的方式也不同,而後再調用child 的layout入口,同時把parent的限制也傳給child,child調用本身的performLayout。

Paint

當須要描繪自定義的圖像的時候,能夠經過繼承CustomPainter,實現paint方法,而後在paint方法裏面使用Flutter提供接口能夠實現複雜的圖像。 下面具體分析一下paint流程是怎麼實現的。

//這是官方的paint demo
 class Sky extends CustomPainter {
  
  @override
  void paint(Canvas canvas, Size size) {
    var rect = Offset.zero & size;
    var gradient = new RadialGradient(
      center: const Alignment(0.7, -0.6), radius: 0.2, colors: [const Color(0xFFFFFF00), const Color(0xFF0099FF)], stops: [0.4, 1.0], );
    canvas.drawRect(
      rect,
      new Paint()..shader = gradient.createShader(rect),
    );
  }

  @override
  bool shouldRepaint(Sky oldDelegate) => false;
}
//這是在渲染管道中paint的入口
void flushPaint() {
  final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
  _nodesNeedingPaint = <RenderObject>[];
  // Sort the dirty nodes in reverse order (deepest first).
  for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) { //這是實現的方式和layout過程基本相似,不過排序是反序的
    assert(node._layer != null);
    if (node._needsPaint && node.owner == this) {
      if (node._layer.attached) {
        PaintingContext.repaintCompositedChild(node);
      } else {
        node._skippedPaintingOnLayer();
      }
    }

    }
}

static void repaintCompositedChild(RenderObject child, { bool debugAlsoPaintedParent: false }) {
    ...
    if (child._layer == null) {  
      child._layer = new OffsetLayer();
    } else {
      child._layer.removeAllChildren();
    }
    
    final PaintingContext childContext = new PaintingContext._(child._layer, child.paintBounds); //經過layer生成 painting context
    child._paintWithContext(childContext, Offset.zero);
    childContext._stopRecordingIfNeeded();
}
  
void _paintWithContext(PaintingContext context, Offset offset) {
  ...
  paint(context, offset); 
 ...
}
void paint(PaintingContext context, Offset offset) {
    if (_painter != null) { //只有持有CustomPainter狀況下,才繼續往下調用自定義的CustomPainter的paint方法,把canvas傳過去
      _paintWithPainter(context.canvas, offset, _painter);
      _setRasterCacheHints(context);
    }
    super.paint(context, offset); //調用父類的paint的方法
        if (_foregroundPainter != null) {
          _paintWithPainter(context.canvas, offset, _foregroundPainter);
          _setRasterCacheHints(context);
    }
}
//super paint 在父類的paint裏面繼續調用child的paint,實現父子遍歷
void paint(PaintingContext context, Offset offset) {
    if (child != null){
      context.paintChild(child, offset); 
}



void _paintWithPainter(Canvas canvas, Offset offset, CustomPainter painter) {
    int debugPreviousCanvasSaveCount;
    canvas.save();
    if (offset != Offset.zero)
      canvas.translate(offset.dx, offset.dy);
    painter.paint(canvas, size);//,在調用paint的時候,通過一串的轉換後,layer->PaintingContext->Canvas,最終paint就是描繪在Canvas上
    ...
    canvas.restore();
}
  
  
複製代碼

總結來講,paint過程當中,渲染管道中首先找出須要重繪的RenderObject,而後若是有實現了CustomPainter,就是調用CustomPainter paint方法,再去調用child的paint方法。

Composited Layer

void compositeFrame() {
    Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);
    try {
      final ui.SceneBuilder builder = new ui.SceneBuilder();
      layer.addToScene(builder, Offset.zero);
      final ui.Scene scene = builder.build();
      ui.window.render(scene);
      scene.dispose();
      assert(() {
        if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)
          debugCurrentRepaintColor = debugCurrentRepaintColor.withHue(debugCurrentRepaintColor.hue + 2.0);
        return true;
      }());
    } finally {
      Timeline.finishSync();
    }
  }
    void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    addChildrenToScene(builder, offset + layerOffset);
  }
  void addChildrenToScene(ui.SceneBuilder builder, Offset childOffset) {
    Layer child = firstChild;
    while (child != null) {
      child.addToScene(builder, childOffset);
      child = child.nextSibling;
    }
  }
複製代碼

Composited Layer就是把全部layer組合成Scene,而後經過ui.window.render方法,把scene提交給Engine,到這一步,Framework向Engine提交數據基本完成了。Engine會把全部的layer根據大小,層級,透明度計算出最終的顯示效果,經過Openg Gl接口渲染到屏幕上。

總結

本文結合Flutter的官方描繪的框架和渲染流程,簡要介紹了渲染的過程實現方式,讓讀者對Flutter在渲染方面有基本的理解,便於之後的開發和探索。Now直播終端團隊致力於爲Flutter生態做出一點本身的貢獻,期待Flutter愈來愈好!

相關文章
相關標籤/搜索