One of the key improvements to Flutter 1.17 is the internal logic of Navigator. This article will take you through the changes from 1.12 to 1.17. We also describe what performance is optimized for Flutter 1.17.

What does Navigator optimize?

The most interesting change in 1.17 is that “old pages in the route will no longer trigger build after opening new opaque pages”.

Dart and overlay.dart are two files for this optimized PR, although the build method itself is very light, but “not executing” when “not needed” is clearly more in line with our expectations.

  • The stack.dart file was modified only to RenderStack logic into shared static methods getIntrinsicDimension and layoutPositionedChild, It’s basically sharing part of the Stack’s layout capability with an Overlay.

  • The overlay. Dart file changes are the soul of the project.

Overlay for Navigator

In fact, the Navigator is a StatefulWidget, and the logic for pop, push and other methods is in NavigatorState. NavigatorState mainly uses Overlay to carry routing pages, so the management logic between navigation pages mainly lies in Overlay.

2.1. What is Overlay?

Overlay you might have used, in a Flutter you can use an Overlay to add global hover controls to the MaterialApp, because an Overlay is a Stack level control, But it can manage the presentation of internal controls independently through OverlayEntry.

For example, you can insert a layer by inserting an OverlayEntry with overlaystate. insert, and the OverlayEntry Builder method will be called at presentation time to produce the desired layout effect.

    var overlayState = Overlay.of(context);
    var _overlayEntry = new OverlayEntry(builder: (context) {
      return new Material(
        color: Colors.transparent,
        child: Container(
          child: Text(
            "${widget.platform} ${widget.deviceInfo} ${widget.language} ${widget.version}",
            style: TextStyle(color: Colors.white, fontSize: 10),
          ),
        ),
      );
    });
    overlayState.insert(_overlayEntry);
Copy the code

2.2. Overlay how to implement navigation?

Overlay is also used in the Navigator to implement page management. Each Route opened by default inserts two overlayentries into the Overlay.

Why there are two will be explained later.

In Overlay, the display logic of List

_entries was completed through _Theatre, for which there were onstage and offstage parameters:

  • onstageIs aStack, used for displayonstageChildren.reversed.toList(growable: false)That is, the part that can be seen;
  • offstageIs to showoffstageChildrenLists, that is, the parts that cannot be seen;
    return _Theatre(
      onstage: Stack(
        fit: StackFit.expand,
        children: onstageChildren.reversed.toList(growable: false),
      ),
      offstage: offstageChildren,
    );
Copy the code

[A], [B], [C]

  • C should be atonstage
  • A and B should be inoffstage

Of course, A, B, and C are all inserted into the Overlay in the form of OverlayEntry. By default, pages A, B, and C are inserted with two overlayentries. That is, [A, B, and C] should have six overlayentries.

So for example, when your application starts up by default, the first thing you see is page A, and then you see the Overlay

  • _entriesLength 2, 1, 2OverlayThe total length of the list is 2;
  • onstageChildrenThe length is 2, which is currently visibleOverlayEntryIs 2;
  • offstageChildrenThe length is 0, which means nothing is invisibleOverlayEntry;

At this point we open up the B page and see in the Overlay:

  • _entriesThe length is 4, which is equal to 4OverlayTwo more are inserted inOverlayEntry;
  • onstageChildrenLength 4, which is currently visibleOverlayEntryIs 4;
  • offstageChildrenThe length is 0, which means there are no currently invisible onesOverlayEntry.

So the Overlay is in the state of the page being opened, so page A is still visible, and page B is being animated.

Then you can see that the build in the Overlay is executed again:

  • _entriesThe length is still 4;
  • onstageChildrenThe length becomes 2, which is currently visibleOverlayEntryIt becomes 2;
  • offstageChildrenThe length is 1, that is, there is currently an invisibleOverlayEntry.

At this time, the page B has been opened, so onstageChildren is restored to the length of 2, that is, the two overlayentries corresponding to the page B; The A page is not visible, so the A page is placed offstage Hildren.

Why put only one OverlayEntry of A into Stage Child? We’ll talk about that later.

Then when you open the C page as shown below, you can see the same process:

  • _entriesThe length becomes 6;
  • onstageChildrenThe length first becomes 4, and then 2, because there are two pages B and C involved when opening, and only one PAGE C is left after opening.
  • offstageChildrenIt’s length 1, and then it’s length 2, because only A is invisible at the beginning, and then A and B are invisible at the end;

So you can see, one page at a time:

  • To the first_entriesInsert twoOverlayEntry;
  • And then you experience it firstonstageChildrenPage opening process with length 4;
  • The last toonstageChildrenThe page of length 2 is open and finished, while the bottom page is added because it is not visibleoffstageChildren;

2.3. Overlay and Route

Why is it that every time_entriesI inserted twoOverlayEntry

This is related to Route. For example, the default Navigator opens a new page using MaterialPageRoute, while generating OverlayEntry is done in ModalRoute, one of its base classes.

In the createOverlayEntries method of ModalRoute, two overlayentries are created with _buildModalBarrier and _buildModalScope, where:

  • _buildModalBarrierCreate a general layer is a mask;
  • _buildModalScopeTo create theOverlayEntryIs the carrier of the page;

Therefore, when a page is opened by default, there will be two overlayentries, one is the mask layer and the other is the page.

  @override
  Iterable<OverlayEntry> createOverlayEntries() sync* {
    yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
    yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
  }
Copy the code

So there are two on a pageOverlayEntryBut why insert intooffstageChildrenThe quantity in is incremented by 1 each time instead of by 2, right?

Logically speaking, according to the previous example of [A, B and C], there are six overlayentries in _entries, but pages B and C are not visible, so it would be A waste to add the mask to pages B and C as well.

As explained in code, when _entries are in a reverse for loop:

  • In case ofentry.opaquetureWhen, the follow-upOverlayEntryJust can’t get intoonstageChildren;
  • offstageChildrenOnly in theentry.maintainStatetrueBefore it is added to the queue;
  @override
  Widget build(BuildContext context) {
    final List<Widget> onstageChildren = <Widget>[];
    final List<Widget> offstageChildren = <Widget>[];
    bool onstage = true;
    for (int i = _entries.length - 1; i >= 0; i -= 1) {
      final OverlayEntry entry = _entries[i];
      if (onstage) {
        onstageChildren.add(_OverlayEntry(entry));
        if (entry.opaque)
          onstage = false;
      } else if (entry.maintainState) {
        offstageChildren.add(TickerMode(enabled: false, child: _OverlayEntry(entry))); }}return _Theatre(
      onstage: Stack(
        fit: StackFit.expand,
        children: onstageChildren.reversed.toList(growable: false),
      ),
      offstage: offstageChildren,
    ); 
  }
Copy the code

And in OverlayEntry:

  • opaqueAccording to theOverlayEntryIs it “blocking” the wholeOverlayThat is, complete coverage of opacity.
  • maintainStateSaid thisOverlayEntryMust be added to_TheatreIn the.

So you can see that when the page is fully open, the first two overlayentries are:

  • Montmorillonite layerOverlayEntryopaqueWill be set to true, so that the followingOverlayEntryI’m not going to enteronstageChildren, that is, do not display;
  • pageOverlayEntrymaintainStateWill betrueSo you can enter it even when you’re not visibleoffstageChildren;

thenopaqueWhere is it set up?

In the TransitionRoute of the other base class of MaterialPageRoute, opaque is set to false at the beginning. After that, completed is set to opaque, and the opaque parameter in PageRoute is @override bool get Opaque => true.

In PopupRoute opaque is false because PopupRoute usually has a transparent background and needs to be displayed mixed with the previous page.

 void _handleStatusChanged(AnimationStatus status) {
    switch (status) {
      case AnimationStatus.completed:
        if (overlayEntries.isNotEmpty)
          overlayEntries.first.opaque = opaque;
        break;
      case AnimationStatus.forward:
      case AnimationStatus.reverse:
        if (overlayEntries.isNotEmpty)
          overlayEntries.first.opaque = false;
        break;
      case AnimationStatus.dismissed:
        if(! isActive) { navigator.finalizeRoute(this); assert(overlayEntries.isEmpty); }break;
    }
    changedInternalState();
  }
Copy the code

At this point we have clarified the work logic for the Overlay when the page opens. By default:

  • Two are inserted for each page openedOverlayEntryOverlay
  • Open processonstageChildrenFour, because the two pages are mixed;
  • Once you’ve opened itonstageChildrenIt’s 2 because it’s maskedopaqueIs set toture, the following page is no longer visible;
  • havemaintainStatetrueOverlayEntryIt will enter after it is invisibleoffstageChildren;

As an added bonus, routes are inserted in a position that is dependent on the OverlayEntry passed in by route.install, for example: push is passed in last of _history.

Overlay in new version 1.17

Why is it that before 1.17, old pages were built when new pages were opened? There are two main points:

  • OverlayEntryThere is aGlobalKey<_OverlayEntryState>The user represents the uniqueness of the page;
  • OverlayEntry_TheatreThere will be fromonstageoffstageIn the process;

3.1 why rebuild

Because OverlayEntry in the Overlay is converted to _OverlayEntry for work, and the GlobalKey in the OverlayEntry is used for _OverlayEntry. When a Widget uses a GlobalKey, its corresponding Element is “Global”.

When Element executes the inflateWidget method, the _retakeInactiveElement method is called to return an “existing” Element object if the Key value is GlobalKey. This allows the Element to be “reused” to other locations, a process in which the Element is removed from the original parent and added to the new parent.

This process triggers the update to Element, and _OverlayEntry itself is a StatefulWidget, so the update to the corresponding StatefulElement triggers rebuild.

3.2. Why 1.17 will not rebuild

On 1.17, the onstage for _Theatre and offstage were removed, replaced with skipCount and children parameters, so that old pages would not be rebuilt each time a page was opened.

And _Theatre from RenderObjectWidget into MultiChildRenderObjectWidget, the layout of the sharing and reuse in _RenderTheatre RenderStack ability.

  @override
  Widget build(BuildContext context) {
    // This list is filled backwards and then reversed below before
    // it is added to the tree.
    final List<Widget> children = <Widget>[];
    bool onstage = true;
    int onstageCount = 0;
    for (int i = _entries.length - 1; i >= 0; i -= 1) {
      final OverlayEntry entry = _entries[i];
      if (onstage) {
        onstageCount += 1;
        children.add(_OverlayEntryWidget(
          key: entry._key,
          entry: entry,
        ));
        if (entry.opaque)
          onstage = false;
      } else if (entry.maintainState) {
        children.add(_OverlayEntryWidget(
          key: entry._key,
          entry: entry,
          tickerEnabled: false)); }}return _Theatre(
      skipCount: children.length - onstageCount,
      children: children.reversed.toList(growable: false)); }Copy the code

This time is equal to the Overlay of all _entries processing to a MultiChildRenderObjectWidget is always an Element, Instead of the controls that needed to be switched back and forth on the onstage Stack and offstage list.

Merge the two arrays into a children array in the new _Theatre, then set the part outside of onstageCount to skipCount, get _firstOnstageChild for layout, And when the children change, trigger is MultiChildRenderObjectElement insertChildRenderObject, rather than “interference” to the previous page, so won’t create a page on the rebuild.

  RenderBox get _firstOnstageChild {
    if (skipCount == super.childCount) {
      return null;
    }
    RenderBox child = super.firstChild;
    for(int toSkip = skipCount; toSkip > 0; toSkip--) { final StackParentData childParentData = child.parentData as StackParentData; child = childParentData.nextSibling; assert(child ! = null); }return child;
  }

  RenderBox get _lastOnstageChild => skipCount == super.childCount ? null : lastChild;
Copy the code

Finally, as shown in the figure below, after opening the page, children will experience changes from 4 to 3, and onstageCount will also change from 4 to 2, which also confirms that the logic after opening and closing the page has not undergone essential changes.

As a result, this change did provide a nice performance boost. Of course, this improvement mainly takes effect between opaque pages. If it is a transparent page effect such as PopModal, it still needs to be rebuilt.

Other optimizations

Metal is a low-level GRAPHICAL programming interface (GUI) similar to OpenGL ES on iOS. It allows you to operate gpus on iOS devices through apis.

Starting with 1.17, Flutter will use Metal to render on iOS devices that support Metal, so according to official data, this will improve performance by 50%. More visible: github.com/flutter/flu…

On Android, the volume can be reduced by approximately 18.5% due to optimization of the Dart VM.

1.17 Optimized the processing of loading a large number of images to achieve better performance in the process of fast sliding (by cleaning the IO Thread Context after a delay), which can theoretically save 70% of the original memory.

All right, that’s all I want to talk about in this issue. Finally, LET me shamelessly promote my new book, Detailed Description of Flutter Development, which has just been published on the shelves. If you are interested in Flutter, please visit the following address:

  • Jingdong:Item.jd.com/12883054.ht…

  • Down-down:Product.dangdang.com/28558519.ht…