Flutter的setState原理详解

本文深入解析Flutter中setState方法的实现机制,阐述其如何响应状态变化,触发UI重建过程,包括元素标记、脏数据处理及渲染流程,揭示动画效果背后的垂直同步信号原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

用Flutter的开发的小伙伴知道,一般你想动态的修改小部件的的状态的话,那么你要实现小部件继承StatefulWidget,而不是StatelessWidget,这和ReactNative的方式如出一辙,你想改变组件的属性,只要调用SetState方法就可以了,至于SetState方法如何实现的,笔者接下来将会一一分解。

void setState(VoidCallback fn) {
      if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() called in constructor: $this'),
          ErrorHint(
            'This happens when you call setState() on a State object for a widget that '
            'hasn\'t been inserted into the widget tree yet. It is not necessary to call '
            'setState() in the constructor, since the state is already assumed to be dirty '
            'when it is initially created.'
          ),
        ]);
      }
      return true;
     }());
   //调用你设置部件属性的方法
    final dynamic result = fn() as dynamic;
  
    //调用element的markNeedsBuild方法来将当前的元素加入脏数据,以备后续的处理
    _element.markNeedsBuild();
  }

这个方法很简单,一是调用你设置属性的方法,二是调用StatefulElement的markNeedsBuild方法,标记这个元素会被用来重新构建RenderObject渲染树

 void markNeedsBuild() {
//当前元素不活跃的话将直接返回
    if (!_active)
      return;
//如果已经设置完_dirty则返回,让SetState方法串行执行
    if (dirty)
      return;
    _dirty = true;
//调用BuildOwner的scheduleBuildFor方法将当前元素加入脏数据中
    owner.scheduleBuildFor(this);
  }
void scheduleBuildFor(Element element) {
  
    //如果已经在脏数据集合中不需要重新添加
    if (element._inDirtyList) {
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      //确保已经注册了垂直同步信号
      onBuildScheduled();
    }
    //将当前要改变的元素加入到脏数据_dirtyElements中
    _dirtyElements.add(element);
  //标记元素已经放入脏数据集合中
    element._inDirtyList = true;
    
  }

看到这可以看出SetState方法就是将当前的element放入脏数据集合,而后标记它为脏元素,此次setState方法就处理完了,那么

是谁来处理的这个脏数据集合呢?这里的脏数据集合其实就是用来记录当前哪些元素需要重新构建RenderObject的属性,从而重新实现哪些RenderObject需要重新渲染。通俗的讲就是每当硬件层的垂直同步信号到来将重新实现View树的测量、布局、重绘,然后将数据交给Gpu渲染的屏幕上,也就是说每过约16.6666666....ms,脏数据元素会被处理一次,然后清空。

那么垂直同步信号是什么时候被注册的,Flutter sdk中的Dart部分最重要的就是binding类,而实现监听同步信号的Binding类为SchedulerBinding这个类,Flutter的UI渲染一篇中已经讲过Binding创建完之后,都会执行initInstances的方法,而最终注册的方法为initInstances-》readInitialLifecycleStateFromNativeWindow-》handleAppLifecycleStateChanged-》scheduleFrame

void scheduleFrame() {
    if (_hasScheduledFrame || !_framesEnabled)
      return;
    assert(() {
      if (debugPrintScheduleFrameStacks)
        debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
      return true;
    }());
    //为window垂直同步信号返回绘制的方法注册回调方法
    ensureFrameCallbacksRegistered();
    window.scheduleFrame();
    _hasScheduledFrame = true;
  }

 

void scheduleFrame() native 'Window_scheduleFrame';

scheduleFrame最终会调用底层引擎的Window_scheduleFrame方法,Window_scheduleFrame最终会调用java层的

flutter/shell/platform/android/io/flutter/view/VsyncWaiter.java的方法AsyncWaitForVsyncDelegate方法

private final FlutterJNI.AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate =
      new FlutterJNI.AsyncWaitForVsyncDelegate() {
        @Override
        public void asyncWaitForVsync(long cookie) {
          Choreographer.getInstance()
              .postFrameCallback(
                  new Choreographer.FrameCallback() {
                    @Override
                    public void doFrame(long frameTimeNanos) {
                      float fps = windowManager.getDefaultDisplay().getRefreshRate();
                      long refreshPeriodNanos = (long) (1000000000.0 / fps);
                      FlutterJNI.nativeOnVsync(
                          frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);
                    }
                  });
        }
      };

可以看出,Flutter在android中最终通过Choreographer注册了垂直时钟信号的监听方法,而注册了垂直信号之后,每过16.6毫秒最终会调用到Dart层的

void ensureFrameCallbacksRegistered() {
    //给垂直同步信号注册回调方法
    window.onBeginFrame ??= _handleBeginFrame;
    window.onDrawFrame ??= _handleDrawFrame;
  }

onBeginFrame方法和onDrawFrame方法,先执行onBeginFrame再 执行onDrawFrame,这么一来就实现了,每次垂直同步信号的到来都会引起Flutter小部件树的部分重新的渲染,因此达到了动态的效果,其实动画(动画的回调在onBeginFrame执行)也是基于垂直同步信号的原理。

那么最后再来看一下渲染是怎么处理脏数据的

 void drawFrame() {
  
    if (_needToReportFirstFrame && _reportFirstFrame) {
      assert(!_firstFrameCompleter.isCompleted);

      TimingsCallback firstFrameCallback;
      firstFrameCallback = (List<FrameTiming> timings) {
        if (!kReleaseMode) {
          developer.Timeline.instantSync('Rasterized first useful frame');
          developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
        }
        SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback);
        _firstFrameCompleter.complete();
      };
//添加时钟回调接口
      SchedulerBinding.instance.addTimingsCallback(firstFrameCallback);
    }

    try {
      if (renderViewElement != null)
        //重新构造脏数据的View树
        buildOwner.buildScope(renderViewElement);
      super.drawFrame();
      buildOwner.finalizeTree();
    } finally {
      assert(() {
        debugBuildingDirtyElements = false;
        return true;
      }());
    }
    if (!kReleaseMode) {
      if (_needToReportFirstFrame && _reportFirstFrame) {
        developer.Timeline.instantSync('Widgets built first useful frame');
      }
    }
    _needToReportFirstFrame = false;
  }

这个方法里面最重要的就是 buildOwner.buildScope(开始根据脏数据元素重新构建渲染树),super.drawFrame重新测量布局和渲染

 void buildScope(Element context, [ VoidCallback callback ]) {
    if (callback == null && _dirtyElements.isEmpty)
      return;
  
    try {
      _scheduledFlushDirtyElements = true;
      if (callback != null) {
        assert(_debugStateLocked);
        Element debugPreviousBuildTarget;
   
        _dirtyElementsNeedsResorting = false;
        try {
          //将根元素下的所有子元素都生成Elment元素
          callback();
        } finally {
          
        }
      }
      _dirtyElements.sort(Element._sort);
      _dirtyElementsNeedsResorting = false;
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      while (index < dirtyCount) {
    //脏数据树的重新构建
        try {
          _dirtyElements[index].rebuild();
        } catch (e, stack) {
         
          );
        }
        index += 1;
        if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
          _dirtyElements.sort(Element._sort);
          _dirtyElementsNeedsResorting = false;
          dirtyCount = _dirtyElements.length;
          while (index > 0 && _dirtyElements[index - 1].dirty) {
 
            index -= 1;
          }
        }
      }
    
    } finally {
//清空脏数据集合
      for (Element element in _dirtyElements) {
        assert(element._inDirtyList);
        element._inDirtyList = false;
      }
      _dirtyElements.clear();
      _scheduledFlushDirtyElements = false;
      _dirtyElementsNeedsResorting = null;
      Timeline.finishSync();
    
  }

buildScope重新实现了脏数据元素的重构方法rebuild,也就是从当前的节点的元素开始构建子元素以及子元素的RenderObject的创建,当然子元素并不一定重新创建,这需要和旧的元素去做比较。

重新构建渲染树之后就是真正的渲染的开始了。如下

  void drawFrame() {
    assert(renderView != null);
    //确定布局的位置
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    //开始执行画画
    pipelineOwner.flushPaint();
    //合并图层将绘制的数据发送给Gpu渲染
    renderView.compositeFrame(); // this sends the bits to the GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
  }

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值