GSYFlutterBook/Flutter-5.md

332 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

作为系列文章的第五篇,本篇主要探索下 Flutter 中的一些有趣原理,帮助我们更好的去理解和开发。
## 文章汇总地址:
> [Flutter 完整实战实战系列文章专栏](https://juejin.im/collection/5db25bcff265da06a19a304e)
>
> [Flutter 番外的世界系列文章专栏](https://juejin.im/collection/5db25d706fb9a069f422c374)
#### 1、Mixins
混入其中( ̄. ̄)是的Flutter 使用的是 Dart 支持 Mixin ,而 Mixin 能够更好的解决**多继承**中容易出现的问题,如:**方法优先顺序混乱、参数冲突、类结构变得复杂化**等等。
Mixin 的定义解释起来会比较绕,我们直接代码从中出吧。如下代码所示,在 Dart 中 `with` 就是用于 mixins。可以看出`class G extends B with A, A2` ,在执行 G 的 a、b、c 方法后,输出了 `A2.a()、A.b() 、B.c()` 。所以结论上简单来说,就是**相同方法被覆盖了,并且 with 后面的会覆盖前面的**。
```
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();
}
/// ***********************输出***********************
///I/flutter (13627): A2.a()
///I/flutter (13627): A.b()
///I/flutter (13627): B.c()
```
接下来我们继续修改下代码。如下所示,我们定义了一个 `Base` 的抽象类,而`A、A2、B` 都继承它,同时再 `print` 之后执行 `super()` 操作。
从最后的输入我们可以看出,`A、A2、B `中的**所有方法都被执行了,且只执行了一次,同时执行的顺序也是和 with 的顺序有关**。如果你把下方代码中 class A.a() 方法的 super 去掉,那么你将看不到 `B.a()` 和 `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()
```
#### 2、WidgetsFlutterBinding
说了那么多,那 Mixins 在 Flutter 中到底有什么用呢?这时候我们就要看 Flutter 中的“胶水类”: `WidgetsFlutterBinding`
**WidgetsFlutterBinding** 在 Flutter启动时`runApp`会被调用作为App的入口它肯定需要承担各类的初始化以及功能配置这种情况下Mixins 的作用就体现出来了。
![](http://img.cdn.guoshuyu.cn/20190604_Flutter-5/image1)
![](http://img.cdn.guoshuyu.cn/20190604_Flutter-5/image2)
从上图我们可以看出, **WidgetsFlutterBinding** 本身是并没有什么代码,主要是继承了 `BindingBase`,而后通过 with 黏上去的各类 **Binding**,这些 **Binding** 也都继承了 `BindingBase`
看出来了没,这里每个 **Binding** 都可以被单独使用,也可以被“黏”到 **WidgetsFlutterBinding** 中使用,这样做的效果,是不是比起一级一级继承的结构更加清晰了?
最后我们打印下执行顺序,如下图所以,不出所料ヽ( ̄▽ ̄)ノ。
![](http://img.cdn.guoshuyu.cn/20190604_Flutter-5/image3)
### 二、InheritedWidget
**InheritedWidget** 是一个抽象类,在 Flutter 中扮演者十分重要的角色,或者你并未直接使用过它,但是你肯定使用过和它相关的封装。
![](http://img.cdn.guoshuyu.cn/20190604_Flutter-5/image4)
如上图所示,**InheritedWidget** 主要实现两个方法:
* 创建了 `InheritedElement` ,该 **Element** 属于特殊 Element 主要增加了将自身也添加到映射关系表 **`_inheritedWidgets`**【注1】方便子孙 element 获取;同时通过 `notifyClients` 方法来更新依赖。
* 增加了 `updateShouldNotify` 方法,当方法返回 true 时,那么依赖该 Widget 的实例就会更新。
所以我们可以简单理解:**InheritedWidget 通过 `InheritedElement` 实现了由下往上查找的支持(因为自身添加到 `_inheritedWidgets`),同时具备更新其子孙的功能。**
> 注1每个 Element 都有一个 `_inheritedWidgets` ,它是一个 `HashMap<Type, InheritedElement>`,它保存了上层节点中出现的 **InheritedWidget** 与其对应 element 的映射关系。
![](http://img.cdn.guoshuyu.cn/20190604_Flutter-5/image5)
接着我们看 **BuildContext**,如上图,**BuildContext** 其实只是接口, **Element** 实现了它。`InheritedElement` 是 **Element** 的子类,所以**每一个 InheritedElement 实例是一个 BuildContext 实例**。同时我们日常使用中传递的 BuildContext 也都是一个 Element 。
所以当我们遇到需要共享 State 时,如果逐层传递 state 去实现共享会显示过于麻烦,那么了解了上面的 **InheritedWidget** 之后呢?
是否**将需要共享的 State都放在一个 InheritedWidget 中,然后在使用的 widget 中直接取用**就可以呢?答案是肯定的!所以如下方这类代码:通常如 *焦点、主题色、多语言、用户信息* 等都属于 App 内的全局共享数据,他们都会通过 BuildContextInheritedElement 获取。
```
///收起键盘
FocusScope.of(context).requestFocus(new FocusNode());
/// 主题色
Theme.of(context).primaryColor
/// 多语言
Localizations.of(context, GSYLocalizations)
/// 通过 Redux 获取用户信息
StoreProvider.of(context).userInfo
/// 通过 Redux 获取用户信息
StoreProvider.of(context).userInfo
/// 通过 Scope Model 获取用户信息
ScopedModel.of<UserInfo>(context).userInfo
```
综上所述,我们从先 `Theme` 入手。
如下方代码所示,通过给 `MaterialApp` 设置主题数据,通过 `Theme.of(context)` 就可以获取到主题数据并绑定使用。当 `MaterialApp` 的主题数据变化时,对应的 Widget 颜色也会发生变化,这是为什么呢(キ`゚Д゚´)!!
```
///添加主题
new MaterialApp(
theme: ThemeData.dark()
);
///使用主题色
new Container( color: Theme.of(context).primaryColor,
```
通过源码一层层查找,可以发现这样的嵌套: ` MaterialApp -> AnimatedTheme -> Theme -> _InheritedTheme extends InheritedWidget ` ,所以通过 `MaterialApp` 作为入口,其实就是嵌套在 **InheritedWidget** 下。
![](http://img.cdn.guoshuyu.cn/20190604_Flutter-5/image6)
如上图所示,通过 ` Theme.of(context)` 获取到的主题数据,其实是通过 `context.inheritFromWidgetOfExactType(_InheritedTheme)` 去获取的,而 **Element** 中实现了 **BuildContext**`inheritFromWidgetOfExactType` 方法,如下所示:
![](http://img.cdn.guoshuyu.cn/20190604_Flutter-5/image7)
那么,还记得上面说的 **`_inheritedWidgets`** 吗?既然 `InheritedElement` 已经存在于 _inheritedWidgets 中,拿出来用就对了。
> 前文InheritedWidget 内的 `InheritedElement` ,该 Element 属于特殊 Element 主要增加了将自身也添加到映射关系表 _inheritedWidgets
最后,如下图所示,在 **InheritedElement** 中,`notifyClients` 通过 `InheritedWidget``updateShouldNotify` 方法判断是否更新,比如在 **Theme**的 `_InheritedTheme` 是:
```
bool updateShouldNotify(_InheritedTheme old) => theme.data != old.theme.data;
```
![](http://img.cdn.guoshuyu.cn/20190604_Flutter-5/image8)
> **所以本质上 Theme、Redux 、 Scope Model、Localizations 的核心都是 `InheritedWidget`。**
### 三、内存
最近闲鱼技术发布了 [《Flutter之禅 内存优化篇》](https://yq.aliyun.com/articles/651005) ,文中对于 Flutter 的内存做了深度的探索,其中有一个很有趣的发现是:
> * Flutter 中 ImageCache 缓存的是 ImageStream 对象,也就是缓存的是一个异步加载的图片的对象。
> * 在图片加载解码完成之前,无法知道到底将要消耗多少内存。
> * 所以容易产生大量的IO操作导致内存峰值过高。
![图片来自闲鱼技术](http://img.cdn.guoshuyu.cn/20190604_Flutter-5/image9)
如上图所示,是图片缓存相关的流程,而目前的拮据处理是通过:
* 在页面不可见的时候没必要发出多余的图片
* 限制缓存图片的数量
* 在适当的时候CG
更详细的内容可以阅读文章本体,这里为什么讲到这个呢?是因为 `限制缓存图片的数量` 这一项。
还记得 `WidgetsFlutterBinding` 这个胶水类吗其中Mixins 了 `PaintingBinding` 如下图所示,被"黏“上去的这个 binding 就是负责图片缓存
![](http://img.cdn.guoshuyu.cn/20190604_Flutter-5/image10)
`PaintingBinding` 内有一个 `ImageCache` 对象,该对象全局一个单例的,同时再图片加载时的 `ImageProvider` 所使用,所以设置图片缓存大小如下:
```
//缓存个数 100
PaintingBinding.instance.imageCache.maximumSize=100;
//缓存大小 50m
PaintingBinding.instance.imageCache.maximumSizeBytes= 50 << 20;
```
### 四、线程
在闲鱼技术的 [深入理解Flutter Platform Channel](https://www.jianshu.com/p/39575a90e820) 中有讲到:**Flutter中有四大线程Platform Task Runner 、UI Task Runner、GPU Task Runner 和 IO Task Runner。**
其中 `Platform Task Runner` 也就是 Android 和 iOS 的主线程,而 `UI Task Runner` 就是Flutter的 UI 线程。
如下图,如果做过 Flutter 中 Dart 和原生端通信的应该知道,通过 `Platform Channel` 通信的两端就是 `Platform Task Runner``UI Task Runner`,这里主要总结起来是:
* 因为 Platform Task Runner 本来就是原生的主线程,所以尽量不要在 Platform 端执行耗时操作。
* 因为Platform Channel并非是线程安全的所以消息处理结果回传到Flutter端时需要确保回调函数是在Platform Thread也就是Android和iOS的主线程中执行的。
![图片来自闲鱼技术](http://img.cdn.guoshuyu.cn/20190604_Flutter-5/image11)
### 五、热更新
*逃不开的需求。*
* 1、首先我们知道 Flutter 依然是一个 **iOS/Android** 工程。
* 2、Flutter通过在 BuildPhase 中添加 shell xcode_backend.sh来生成和嵌入**App.framework** 和 **Flutter.framework** 到 IOS。
* 3、Flutter通过 Gradle 引用 **flutter.jar** 和把编译完成的**二进制文件**添加到 Android 中。
其中 Android 的编译后二进制文件存在于 `data/data/包名/app_flutter/flutter_assets/`下。做过 Android 的应该知道,这个路径下是可以很简单更新的,所以你懂的  ̄ω ̄=。
> **⚠注意1.7.8 之后的版本Android 下的 Flutter 已经编译为纯 so 文件。**
IOS据我了解貌似动态库 framework 等引用是不能用热更新的,除非你不需要审核!
>自此,第五篇终于结束了!(///▽///)
### 资源推荐
* Github [https://github.com/CarGuo/](https://github.com/CarGuo)
* **开源 Flutter 完整项目https://github.com/CarGuo/GSYGithubAppFlutter**
* **开源 Flutter 多案例学习型项目: https://github.com/CarGuo/GSYFlutterDemo**
* **开源 Fluttre 实战电子书项目https://github.com/CarGuo/GSYFlutterBook**
##### 完整开源项目推荐:
* [GSYGithubAppWeex](https://github.com/CarGuo/GSYGithubAppWeex)
* [GSYGithubApp React Native](https://github.com/CarGuo/GSYGithubApp )
![我们还会再见吗?](http://img.cdn.guoshuyu.cn/20190604_Flutter-5/image12)