Flutter完整开发实战详解(五、 深入探索)

作为系列文章的第五篇,本篇主要探索下 Flutter 中的一些有趣原理,帮助我们更好的去理解和开发。

文章汇总地址:

Flutter 完整实战实战系列文章专栏

Flutter 番外的世界系列文章专栏

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 的作用就体现出来了。

image
image

从上图我们可以看出, WidgetsFlutterBinding 本身是并没有什么代码,主要是继承了 BindingBase,而后通过 with 黏上去的各类 Binding,这些 Binding 也都继承了 BindingBase

看出来了没,这里每个 Binding 都可以被单独使用,也可以被“黏”到 WidgetsFlutterBinding 中使用,这样做的效果,是不是比起一级一级继承的结构更加清晰了?

最后我们打印下执行顺序,如下图所以,不出所料ヽ( ̄▽ ̄)ノ。

image

二、InheritedWidget

InheritedWidget 是一个抽象类,在 Flutter 中扮演者十分重要的角色,或者你并未直接使用过它,但是你肯定使用过和它相关的封装。

image

如上图所示,InheritedWidget 主要实现两个方法:

  • 创建了 InheritedElement ,该 Element 属于特殊 Element, 主要增加了将自身也添加到映射关系表 _inheritedWidgets【注1】,方便子孙 element 获取;同时通过 notifyClients 方法来更新依赖。

  • 增加了 updateShouldNotify 方法,当方法返回 true 时,那么依赖该 Widget 的实例就会更新。

所以我们可以简单理解:InheritedWidget 通过 InheritedElement 实现了由下往上查找的支持(因为自身添加到 _inheritedWidgets),同时具备更新其子孙的功能。

注1:每个 Element 都有一个 _inheritedWidgets ,它是一个 HashMap<Type, InheritedElement>,它保存了上层节点中出现的 InheritedWidget 与其对应 element 的映射关系。

image

接着我们看 BuildContext,如上图,BuildContext 其实只是接口, Element 实现了它。InheritedElementElement 的子类,所以每一个 InheritedElement 实例是一个 BuildContext 实例。同时我们日常使用中传递的 BuildContext 也都是一个 Element 。

所以当我们遇到需要共享 State 时,如果逐层传递 state 去实现共享会显示过于麻烦,那么了解了上面的 InheritedWidget 之后呢?

是否将需要共享的 State,都放在一个 InheritedWidget 中,然后在使用的 widget 中直接取用就可以呢?答案是肯定的!所以如下方这类代码:通常如 焦点、主题色、多语言、用户信息 等都属于 App 内的全局共享数据,他们都会通过 BuildContext(InheritedElement) 获取。

///收起键盘
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 下。

image

如上图所示,通过 Theme.of(context) 获取到的主题数据,其实是通过 context.inheritFromWidgetOfExactType(_InheritedTheme) 去获取的,而 Element 中实现了 BuildContextinheritFromWidgetOfExactType 方法,如下所示:

image

那么,还记得上面说的 _inheritedWidgets 吗?既然 InheritedElement 已经存在于 _inheritedWidgets 中,拿出来用就对了。

前文:InheritedWidget 内的 InheritedElement ,该 Element 属于特殊 Element, 主要增加了将自身也添加到映射关系表 _inheritedWidgets

最后,如下图所示,在 InheritedElement 中,notifyClients 通过 InheritedWidgetupdateShouldNotify 方法判断是否更新,比如在 Theme_InheritedTheme 是:

bool updateShouldNotify(_InheritedTheme old) => theme.data != old.theme.data;
image

所以本质上 Theme、Redux 、 Scope Model、Localizations 的核心都是 InheritedWidget

三、内存

最近闲鱼技术发布了 《Flutter之禅 内存优化篇》 ,文中对于 Flutter 的内存做了深度的探索,其中有一个很有趣的发现是:

  • Flutter 中 ImageCache 缓存的是 ImageStream 对象,也就是缓存的是一个异步加载的图片的对象。
  • 在图片加载解码完成之前,无法知道到底将要消耗多少内存。
  • 所以容易产生大量的IO操作,导致内存峰值过高。
图片来自闲鱼技术

如上图所示,是图片缓存相关的流程,而目前的拮据处理是通过:

  • 在页面不可见的时候没必要发出多余的图片
  • 限制缓存图片的数量
  • 在适当的时候CG

更详细的内容可以阅读文章本体,这里为什么讲到这个呢?是因为 限制缓存图片的数量 这一项。

还记得 WidgetsFlutterBinding 这个胶水类吗?其中Mixins 了 PaintingBinding 如下图所示,被"黏“上去的这个 binding 就是负责图片缓存

image

PaintingBinding 内有一个 ImageCache 对象,该对象全局一个单例的,同时再图片加载时的 ImageProvider 所使用,所以设置图片缓存大小如下:

//缓存个数 100
PaintingBinding.instance.imageCache.maximumSize=100;
//缓存大小 50m
PaintingBinding.instance.imageCache.maximumSizeBytes= 50 << 20;

四、线程

在闲鱼技术的 深入理解Flutter Platform Channel 中有讲到: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 RunnerUI Task Runner,这里主要总结起来是:

  • 因为 Platform Task Runner 本来就是原生的主线程,所以尽量不要在 Platform 端执行耗时操作。

  • 因为Platform Channel并非是线程安全的,所以消息处理结果回传到Flutter端时,需要确保回调函数是在Platform Thread(也就是Android和iOS的主线程)中执行的。

图片来自闲鱼技术

五、热更新

逃不开的需求。

  • 1、首先我们知道 Flutter 依然是一个 iOS/Android 工程。

  • 2、Flutter通过在 BuildPhase 中添加 shell (xcode_backend.sh)来生成和嵌入App.frameworkFlutter.framework 到 IOS。

  • 3、Flutter通过 Gradle 引用 flutter.jar 和把编译完成的二进制文件添加到 Android 中。

其中 Android 的编译后二进制文件存在于 data/data/包名/app_flutter/flutter_assets/下。做过 Android 的应该知道,这个路径下是可以很简单更新的,所以你懂的  ̄ω ̄=。

⚠️注意,1.7.8 之后的版本,Android 下的 Flutter 已经编译为纯 so 文件。

IOS?据我了解,貌似动态库 framework 等引用是不能用热更新的,除非你不需要审核!

自此,第五篇终于结束了!(///▽///)

资源推荐

完整开源项目推荐:
我们还会再见吗?
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容