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:
onstage
Is aStack
, used for displayonstageChildren.reversed.toList(growable: false)
That is, the part that can be seen;offstage
Is to showoffstageChildren
Lists, 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 at
onstage
; - A and B should be in
offstage
。
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
_entries
Length 2, 1, 2Overlay
The total length of the list is 2;onstageChildren
The length is 2, which is currently visibleOverlayEntry
Is 2;offstageChildren
The length is 0, which means nothing is invisibleOverlayEntry
;
At this point we open up the B page and see in the Overlay:
_entries
The length is 4, which is equal to 4Overlay
Two more are inserted inOverlayEntry
;onstageChildren
Length 4, which is currently visibleOverlayEntry
Is 4;offstageChildren
The 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:
_entries
The length is still 4;onstageChildren
The length becomes 2, which is currently visibleOverlayEntry
It becomes 2;offstageChildren
The 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:
_entries
The length becomes 6;onstageChildren
The 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.offstageChildren
It’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
_entries
Insert twoOverlayEntry
; - And then you experience it first
onstageChildren
Page opening process with length 4; - The last to
onstageChildren
The 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_entries
I 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:
_buildModalBarrier
Create a general layer is a mask;_buildModalScope
To create theOverlayEntry
Is 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 pageOverlayEntry
But why insert intooffstageChildren
The 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 of
entry.opaque
为ture
When, the follow-upOverlayEntry
Just can’t get intoonstageChildren
; offstageChildren
Only in theentry.maintainState
为true
Before 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:
opaque
According to theOverlayEntry
Is it “blocking” the wholeOverlay
That is, complete coverage of opacity.maintainState
Said thisOverlayEntry
Must be added to_Theatre
In the.
So you can see that when the page is fully open, the first two overlayentries are:
- Montmorillonite layer
OverlayEntry
的opaque
Will be set to true, so that the followingOverlayEntry
I’m not going to enteronstageChildren
, that is, do not display; - page
OverlayEntry
的maintainState
Will betrue
So you can enter it even when you’re not visibleoffstageChildren
;
thenopaque
Where 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 opened
OverlayEntry
到Overlay
; - Open process
onstageChildren
Four, because the two pages are mixed; - Once you’ve opened it
onstageChildren
It’s 2 because it’s maskedopaque
Is set toture
, the following page is no longer visible; - have
maintainState
为true
的OverlayEntry
It 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:
OverlayEntry
There is aGlobalKey<_OverlayEntryState>
The user represents the uniqueness of the page;OverlayEntry
在_Theatre
There will be fromonstage
到offstage
In 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…