As the fifth article in this series, this article explores some interesting principles of Flutter to help us better understand and develop Flutter.
Article summary address:
A complete series of articles on Flutter
A series of articles on the world outside Flutter
1, Mixins
Into it (. ̄)! Yes, Flutter uses Dart to support mixins, and mixins are better at addressing multiple inheritance issues such as method priorities confusion, parameter conflicts, and class structure complexity.
Mixins can be tricky to explain, so let’s just code them out. In Dart, with is used for mixins, as shown in the following code. Class G extends B with A, A2; class G extends B with A, A2; class G extends B with A, A2; So the conclusion is simply that the same method is overwritten, and the one after “with” overwrites the one before.
class A {
a() {
print("A.a()");
}
b() {
print("A.b()");
}
}
class A2 {
a() {
print("A2.a()");
}
}
class B {
a() {
print("B.a()");
}
b() {
print("B.b()");
}
c() {
print("B.c()");
}
}
class G extends B with A, A2 {
}
testMixins() { G t = new G(); t.a(); t.b(); t.c(); } / / / * * * * * * * * * * * * * * * * * * * * * * * output * * * * * * * * * * * * * * * * * * * * * * * / / / I/flutter (13627) : A2. () / / / I/a flutter (13627) : A.b() ///I/flutter (13627): B.c()Copy the code
Let’s go ahead and modify the code. As shown below, we define A Base abstract class, which A, A2, and B inherit, and execute super() after print.
From the last input, we can see that all the methods in A, A2, and B are executed only once, in the same order as with. If you remove super from the class A.a() method in the code below, you won’t see the output of B.a() and base a().
abstract class Base {
a() {
print("base a()");
}
b() {
print("base b()");
}
c() {
print("base c()");
}
}
class A extends Base {
a() {
print("A.a()");
super.a();
}
b() {
print("A.b()");
super.b();
}
}
class A2 extends Base {
a() {
print("A2.a()");
super.a();
}
}
class B extends Base {
a() {
print("B.a()");
super.a();
}
b() {
print("B.b()");
super.b();
}
c() {
print("B.c()");
super.c();
}
}
class G extends B with A, A2 {
}
testMixins() {
G t = new G();
t.a();
t.b();
t.c();
}
///I/flutter (13627): A2.a()
///I/flutter (13627): A.a()
///I/flutter (13627): B.a()
///I/flutter (13627): base a()
///I/flutter (13627): A.b()
///I/flutter (13627): B.b()
///I/flutter (13627): base b()
///I/flutter (13627): B.c()
///I/flutter (13627): base c()
Copy the code
2, WidgetsFlutterBinding
Having said all that, what exactly is the use of Mixins in Flutter? This is when we look at the “glue class” of Flutter: WidgetsFlutterBinding.
WidgetsFlutterBinding runApp will be called when the Flutter starts. As the entry of the App, it must be responsible for various initialization and function configuration. In this case, the role of Mixins is reflected.
As you can see from the figure above, WidgetsFlutterBinding itself does not have any code. It mainly inherits BindingBase, and then pastes it with various BindingBase Binding, which also inherits BindingBase.
Each Binding can be used separately or “glued” to a WidgetsFlutterBinding.
Finally, we print the order of execution, as shown below.
Second, the InheritedWidget
InheritedWidget is an abstract class that plays an important role in Flutter, or you don’t use it directly, but you certainly use the wrapper associated with it.
As shown in the figure above, InheritedWidget primarily implements two methods:
-
[inheritedWidgets] create an InheritedElement, which belongs to a special Element, and add itself to the table. The notifyClients method is also used to update dependencies.
-
The updateShouldNotify method was added so that instances that depend on the Widget are updated when the method returns true.
[inheritedWidgets] [inheritedWidgets] [inheritedWidgets] [inheritedWidgets] [inheritedWidgets] [inheritedWidgets] [inheritedWidgets] [inheritedWidgets]
Note 1: Each Element has an _inheritedWidgets, which is a HashMap
,>
that holds the mapping between the inheritedWidgets that appear in the upper-layer node and their corresponding elements.
Then we look at BuildContext, as shown above. BuildContext is really just an interface, and Element implements it. InheritedElement is a subclass of Element, so every InheritedElement instance is a BuildContext instance. And the BuildContext we pass in everyday use is also an Element.
So when we get to the point where we need to share State, passing State layer by layer to implement the sharing would seem too cumbersome, but what about the InheritedWidget above?
Do we place all the states we need to share in an InheritedWidget and then use them in the widget we use? The answer is yes! So code like focus, theme colors, multiple languages, user information, etc. are all globally shared data within the App, and they are all retrieved by BuildContext (InheritedElement).
Focusscope.of (context).requestFocus(new FocusNode()); Of (context).vary. of(context).vary. of(context).vary. of(context) GSYLocalizations) /// Obtain user information using Redux storeprovider.of (context). UserInfo // Obtain user information using Redux Storeprovider.of (context).userInfo // obtain user information by Scope Model scopedModel.of < userInfo >(context).userinfoCopy the code
To sum up, let’s start with the Theme.
As shown in the following code, Theme. Of (context) can be used to obtain Theme data and bind it by setting Theme data to the MaterialApp. When the topic data of the MaterialApp changes, the corresponding Widget color will also change. ?
/// Add the theme new MaterialApp(theme: themedata.dark ()); New Container(color: Theme. Of (context).primarycolor,Copy the code
Through the source layer search, you can find such nesting: MaterialApp -> AnimatedTheme -> Theme -> _InheritedTheme extends InheritedWidget Is nested under the InheritedWidget.
As shown in the above, through the Theme of (context) to obtain the data to the subject, is by the context. InheritFromWidgetOfExactType (_InheritedTheme) to obtain, And realized BuildContext inheritFromWidgetOfExactType method in Element, as shown below:
So, remember the _inheritedWidgets mentioned above? Now that we already have an InheritedElement in _inheritedWidgets, we can use it.
[inheritedWidgets] [inheritedWidgets] [inheritedWidgets] [inheritedWidgets] [inheritedWidgets] [inheritedWidgets] [inheritedWidgets] [inheritedWidgets
Finally, within the InheritedElement, notifyClients uses the updateShouldNotify method on the InheritedWidget to determine whether the InheritedWidget is updated, as shown in the figure below. For example, the _InheritedTheme in the Theme is:
bool updateShouldNotify(_InheritedTheme old) => theme.data ! = old.theme.data;Copy the code
So essentially Theme, Redux, Scope Model, and Localizations are all at the core
InheritedWidget
.
Three, memory,
Recently, Idle Fish Technology published “The Zen of Flutter Memory Optimization”, which made an in-depth exploration of the memory of Flutter. One of the interesting findings is:
- A Flutter ImageCache caches an ImageStream object, that is, an asynchronously loaded image object.
- There is no way to know how much memory will be consumed until the image is loaded and decoded.
- Therefore, a large number of I/O operations are likely to occur, resulting in high memory peak values.
As shown in the figure above, is the process associated with image caching, and the current pinch is handled through:
- There is no need to post extra images when the page is not visible
- Limit the number of cached images
- At the right time CG
More details can be found in the text itself. Why is this mentioned here? It’s the limit on the number of images in the cache.
Remember the WidgetsFlutterBinding glue class? Mixins have a PaintingBinding as shown below. The binding that is “pasted” on is responsible for image caching
The PaintingBinding contains an ImageCache object, which is a singleton and is used by the ImageProvider at image loading time, so set the ImageCache size as follows:
Number 100 PaintingBinding. / / the cache instance. ImageCache. MaximumSize = 100; / / the cache size 50 m PaintingBinding instance. ImageCache. MaximumSizeBytes = 50 < < 20;Copy the code
Four, thread
In the deep understanding of Flutter Platform Channel, There are four main threads in Flutter: Platform Task Runner, UI Task Runner, GPU Task Runner, and IO Task Runner.
Platform Task Runner is the main thread of Android and iOS, while UI Task Runner is the UI thread of Flutter.
If the Dart communicates with the native end of the Flutter, you should know that Platform Task Runner and UI Task Runner communicate through the Platform Channel. The two ends of the Flutter can be summarized as follows:
-
Because the Platform Task Runner is the native main thread, try not to perform time-consuming operations on the Platform side.
-
Since Platform channels are not thread-safe, when message processing is sent back to the Flutter end, ensure that the callback function is executed in the Platform Thread (the main Thread of Android and iOS).
Hot update
Inescapable needs.
-
1. First we know that Flutter is still an iOS/Android project.
-
2, Flutter generates and emphases App. Framework and Flutter. Framework into IOS by adding shell (xcode_backend.sh) to BuildPhase.
-
Flutter uses Gradle to reference Flutter. Jar and add the compiled binaries to Android.
The compiled binary files of Android are stored under data/data/ package name /app_flutter/flutter_assets/. Those who have done Android should know that this path can be easily updated, so you know the  ̄ω ̄=.
⚠️ Note that Flutter under Android has been compiled to a pure SO file for versions 1.7.8 and later.
The IOS? From what I understand, it seems that references to the dynamic library Framework can’t be hot updated unless you don’t need an audit!
Since then, chapter 5 has finally come to an end! (/ / / del / / /)
Resources to recommend
- Making: github.com/CarGuo/
- Open Source Flutter complete project:Github.com/CarGuo/GSYG…
- Open Source Flutter Multi-case learning project:Github.com/CarGuo/GSYF…
- Open Source Fluttre Combat Ebook Project:Github.com/CarGuo/GSYF…
Full open source project recommendation:
- GSYGithubAppWeex
- GSYGithubApp React Native