Flutter架构初探(二)
Widget
Element
BuildContext
等基础组件,这次主要介绍- 构造函数
factory
关键字extension
关键字InheritWidget
的含义和基础用法
构造函数
在dart
中如果不提供构造函数则默认提供无参构造函数,构造函数有两种
- 普通构造函数
- 命名构造函数
普通构造函数
class Point{
num x,y;
Point(num x,num y){
// 可以认为在函数刚开始实例对象就已经创建成功了
// 所以可以在下面👇使用this关键字获取当前实例对象
this.x = x ;
this.y = y ;
}
}
还可以这样写,代码简化了一些
class Point{
num x,y;
Point(this.x,this.y);
}
还可以这样写
class Point{
num? x,y;
//冒号后面可以用this说明此时实例对象已经创建成功了
Point(num a,num b):this.x=a,y=b;
}
还可以包含初始化列表
class Point{
num? x,y;
Point(num a,num b):x=a,y=b{
x=b;
y=a;
}
}
Point p = Point(1,2);
print('x=${p.x} '+'y=${p.y}');
//执行结果
x=2 y=1
可以看到:
后面的代码先执行{}
里面的代码后执行
注意一个类里面最多只能有一个普通构造函数
class Point{
num? x,y;
Point(num x);
Point(num y); //这里会报错
}
命名构造函数
在一个类里面普通构造函数不能定义多个,如果我们想实现多个构造函数怎么办呢???使用命名构造函数!
class Point{
num? x,y;
Point(num x);
Point.initY(num y); //定义一个命名构造函数
}
命名构造函数也有上面的几种写法,相比于普通构造函数,命名构造函数更加直观,代码可读性更高,例如我们平时使用频率很高的
Point.fromJson(Map<String, num> json){
...
}
还可以实现构造函数重定向
class Rect{
num x ,y , wid , hei ;
Rect(this.x , this.y ,this.wid ,this.hei);
Rect.withSize(num width,num height) : this(0 , 0 , width ,height) ;
}
通过上面我们介绍的构造函数相关的内容我们引出下一节的内容,就是factory
关键字
factory
Use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class. For example, a factory constructor might return an instance from a cache, or it might return an instance of a subtype.
当你使用factory关键词时,你能控制在使用构造函数时,并不总是创建一个新的该类的对象,比如它可能会从缓存中返回一个已有的实例,或者是返回子类的实例。
有三个使用场景
-
- A factory constructor can check if it has a prepared reusable instance in an internal cache and return this instance or otherwise create a new one. 避免创建过多的重复实例,如果已创建该实例,则从缓存中拿出来。其实也是一种单例模式,只不过将容器搞成了单例而不是某一个实例对象。
-
- You can for example have an abstract class A (which can't be instantiated) with a factory constructor that returns an instance of a concrete subclass of A depending for example on the arguments passed to the factory constructor. 调用子类的构造函数(工厂模式 factory pattern)
-
- singleton pattern 实现单例模式
单例模式
class Singleton {
//我们看到普通的构造函数的写法不许有返回值
Singleton();
}
//执行代码
var s1 = Singleton();
var s2 = Singleton();
//`identical`会对两个对象的地址进行比较,相同返回true,
// 等同于 == ,好处是如果重写了==,那用`identical`会更方便。
print(identical(s1, s2));
//执行结果
false
每次执行构造函数都会重新创建一个对象
class Singleton {
// 私有命名构造函数
Singleton._();
// 创建静态变量_singleton
static final Singleton _singleton = Singleton._();
factory Singleton(){
// 注意这里不能使用this关键字
return _singleton;
}
}
//执行代码
var s1 = Singleton();
var s2 = Singleton();
print(identical(s1, s2));
//执行结果
true
当你需要构造函数不是每次都创建一个新的对象时,使用factory关键字
缓存
class Logger {
final String name;
// 缓存已创建的对象
static final Map<String, Logger> _cache = <String, Logger>{};
factory Logger(String name) {
// 不理解putIfAbsent可以查看文末的简述
return _cache.putIfAbsent(
name, () => Logger._internal(name));
}
// 私有的构造函数
Logger._internal(this.name){
print("生成新实例:$name");
}
}
var p1 = new Logger("1");
var p2 = new Logger('22');
var p3 = new Logger('1');// 相同对象直接访问缓存
print('p1和p2是否相同:'+identical(p1,p2).toString());
print('p1和p3是否相同:'+identical(p1,p3).toString());
// 执行结果
生成新实例:1
生成新实例:22
p1和p2是否相同:false
p1和p3是否相同:true
工厂模式
abstract class Animal {
String name = '';
void getNoise();
factory Animal(String type,String name) {
switch(type) {
case "猫":
return new Cat(name);
case "狗":
return new Dog(name);
default:
throw "你输入的:'$type' 不知道是个什么品种";
}
}
}
class Cat implements Animal {
String name;
Cat(this.name);
@override
void getNoise() {
print("${this.name}: 喵~~");
}
}
class Dog implements Animal {
String name;
Dog(this.name);
@override
void getNoise() {
print("${this.name}: 汪汪~");
}
}
var cat = new Animal("猫","狸花猫");
var dog = new Animal("狗","旺财");
cat.getNoise();
dog.getNoise();
// 执行结果
狸花猫: 喵~~
旺财: 汪汪~
通过这个例子我们知道返回的确实是子类的实例对象,因为父类没有实现getNoise
方法
通过上面三个例子我们简单思考🤔一下factory
这个关键字究竟起到了什么作用??
factory
修饰的构造函数必须得有对应类型或者子类的实例对象作为返回值- 如果要实现单例模式,那么返回值的唯一性需要开发者自己来保证
所以
factory
只是表明他有那个带返回值的特性且死死的绑定了构造函数,如果我们实现下面的代码
class SingleInstance {
static final SingleInstance _singleInstance = SingleInstance._internal();
factory SingleInstance(){
return SingleInstance._internal();
}
SingleInstance._internal();
}
那么此时factory
球用都没有!!!!
前两部分有承接关系,第三部分extension
跟前两节没啥关系
extension
我在研究Provider
过程中发现有这种用法
context.read<T>
context.watch<T>
点进去发现是通过extension
实现的
extension ReadContext on BuildContext {
T read<T>() {
return Provider.of<T>(this, listen: false);
}
}
注意:这个关键字只有在 Dart 版本 2.7 及其以上才支持。
extension
在OC
中很常用,但是dart
中的extension
更类似于OC
中的category
,是在不改变某一个类本身的情况下给其添加一些功能,例如我们想添加一个通过字符串色值设置颜色的属性,想给导航控制器设置一个全屏幕测滑返回的手势等等,在Dart
中我们同样可以实现类似功能。
例一
想给String
添加一个转换为Color
的方法
extension StringExtension1 on String {
//字符转换成Color对象
toColor() {
var hexColor = this.replaceAll("#", "");
if (hexColor.length == 6) {
hexColor = "FF" + hexColor;
}
if (hexColor.length == 8) {
return Color(int.parse("0x$hexColor"));
}
}
}
使用方法
Container(color: '#333333'.toColor())
但是如果声明为dynamic
的变量使用起来会有问题
dynamic c = '#ffffff';
Container(color: c.toColor())
报错
NoSuchMethodError: Class 'String' has no instance method 'toColor'.
Receiver: "#ffffff"
Tried calling: toColor()
例二
extension DateTimeExtension on DateTime {
// 还可以传入时间格式 yyyy-MM......
toFormatString() {
DateFormat dateFormat = new DateFormat("yyyy-MM-dd HH:mm:ss");
return dateFormat.format(this);
}
}
在OC
中我们可以给某个类添加方法,但是没有办法添加成员变量,这是因为一个实例对象在内存中的布局已经确定了,而通过类扩展添加成员变量会改变这种内存布局,假如类的布局规则改变了,那么之前创建的实例对象他就不认了,这是很不好的设计。那么我盲猜dart
中也不可以通过extension
添加成员变量
果然添加成员变量报错了!!!但是在OC
中可以通过关联对象的方式实现类似添加成员变量的操作,在dart
中我还不知道如何实现。
InheritWidget
先看一个小问题
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp>{
GlobalKey _globalKey = GlobalKey();
int count = 0;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
key: _globalKey,
body: CustomerInheritedWidget(
count: count,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MaterialButton(
onPressed: () {
setState(() {
count++;
});
},
child: Text('模拟添加购物车操作'+count.toString()+'次',style: TextStyle(color: Colors.black,fontSize: 20),)),
MaterialButton(
onPressed: () {
Navigator.of(_globalKey.currentContext).push(
MaterialPageRoute(builder: (context) => SecondPage()));
},
child: Text('跳转到购物车页面',style: TextStyle(color: Colors.black,fontSize: 20),))
],
),
),
),
),
);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text('购物车中共有'+CustomerInheritedWidget.of(context).count.toString()+'件商品'),
),
);
}
}
class CustomerInheritedWidget<T> extends InheritedWidget{
final int count;
CustomerInheritedWidget({Key key,this.count,Widget child}):super(key: key,child: child);
static CustomerInheritedWidget<T> of<T>(BuildContext context){
return context.dependOnInheritedWidgetOfExactType<CustomerInheritedWidget<T>>();
}
@override
bool updateShouldNotify(covariant CustomerInheritedWidget oldWidget) {
return oldWidget.count != count;
}
}
点击跳转按钮SecondPage
直接报错
The getter 'count' was called on null.
Receiver: null
Tried calling: count
猜测应该是
CustomerInheritedWidget.of(context).count
这行代码没有获取到CustomerInheritedWidget
对象,相当于null.count
,我们分析一下这棵UI树
为什么在SecondPage
获取不到CustomerInheritedWidget
对象呢??再来看一个正确的小例子
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter InheritWidget',
home: Scaffold(
appBar: AppBar(),
body: GestureDetector(
onTap: (){
Navigator.of(context);
},
child: BodyWidget(),
),
),
);
}
}
class BodyWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return BodyWidgetState();
}
}
class BodyWidgetState extends State<BodyWidget> {
int count = 0;
@override
Widget build(BuildContext context) {
print("BodyWidgetState build:$hashCode");
return CustomInheritedWidget(
data: count,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
DependOnInheritedWidget<int>(),
Builder(builder: (context) {
return CustomRaisedButton(
onPressed: () {
setState(() {
count++;
});
},
child: Text("数字+1"),
);
})
],
),
);
}
}
class CustomRaisedButton extends MaterialButton {
const CustomRaisedButton({
@required VoidCallback onPressed,
Widget child,
}) : super(onPressed: onPressed, child: child);
@override
Widget build(BuildContext context) {
print("CustomRaisedButton build:$hashCode");
return super.build(context);
}
}
class DependOnInheritedWidget<T> extends StatefulWidget {
DependOnInheritedWidget(){
print("DependOnInheritedWidget:$hashCode");
}
@override
State<StatefulWidget> createState() {
return DependOnInheritedWidgetState<T>();
}
}
class DependOnInheritedWidgetState<T> extends State<DependOnInheritedWidget> {
@override
Widget build(BuildContext context) {
print("DependOnInheritedWidgetState build:$hashCode");
return Text(CustomInheritedWidget.of<T>(context).data.toString());
}
@override
void didChangeDependencies() {
print("DependOnInheritedWidgetState didChangeDependencies:$hashCode");
super.didChangeDependencies();
}
}
class CustomInheritedWidget<T> extends InheritedWidget {
CustomInheritedWidget({Key key, this.data, Widget child}) : super(key: key, child: child);
final T data;
//定义一个便捷方法,方便子树中的widget获取共享数据
static CustomInheritedWidget<T> of<T>(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget<T>>();
}
@override
bool updateShouldNotify(CustomInheritedWidget oldWidget) {
return oldWidget.data != data;
}
}
这是一个简单小例子,点击按钮数字+1
此时这颗UI树是这样的
按钮是如何触发数据的修改的
class BodyWidgetState extends State<BodyWidget> {
int count = 0;
@override
Widget build(BuildContext context) {
print("BodyWidgetState build:$hashCode");
return CustomInheritedWidget(
data: count, //将数据赋值给InheritedWidget组件
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
DependOnInheritedWidget<int>(),
Builder(builder: (context) {
return CustomRaisedButton(
onPressed: () {
setState(() {//通过setState就可以实现数据刷新
count++;
});
},
child: Text("数字+1"),
);
})
],
),
);
}
}
我们看到这里就是简单的将创建的数据赋值给InheritedWidget
组件,然后在setState
中修改数据就可以了。
如何获取到InheritedWidget实例
static CustomInheritedWidget<T> of<T>(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget<T>>();
}
是通过dependOnInheritedWidgetOfExactType
方法获取到的,我们后面分析
是否刷新页面的判断条件
@override
bool updateShouldNotify(CustomInheritedWidget oldWidget) {
return oldWidget.data != data;
}
默认的这种写法
- 如果是值类型则判断值是否相同
- 如果是引用类型则判断对象地址是否相同 当然我们也可以根据需要随意修改这个判断条件
@override
bool updateShouldNotify(CustomInheritedWidget oldWidget) {
return oldWidget.data.xxx != data.xxx;
}
原理解析
要想了解原理我们首先需要了解以下几个概念
mount()
是首次把element
添加到element Tree
后,然后将当前element
状态变为active
状态,active
状态表示已经准备好了可以被加载了。activate()
是把deactivate element
重新标记为active
状态_inheritedWidgets
存储当前UI树中所有的InheritedWidget
结点,子节点的_inheritedWidgets
指针默认指向父节点的_inheritedWidgets
,但是InheritedWidget
中对此做了一些修改,我们下面会讲到
Element源码
看源码之前首先确定我们要解决的问题是什么??
// 1、这行代码是怎么查找到InheritedElement对象的?
context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget<T>>();
// 2、组件为什么可以监听到InheritedElement数据的改变?
CustomInheritedWidget.of<T>(context).data
abstract class Element extends DiagnosticableTree implements BuildContext {
Element? _parent;
//这里我们看到了identical其实也是==的符号重定义
@nonVirtual
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(Object other) => identical(this, other);
@mustCallSuper
void mount(Element? parent, Object? newSlot) {
...
_parent = parent;
...
_updateInheritance();
}
@mustCallSuper
void activate() {
...
_dependencies?.clear();
...
_updateInheritance();
...
}
void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
@mustCallSuper
void deactivate() {
if (_dependencies != null && _dependencies!.isNotEmpty) {
for (final InheritedElement dependency in _dependencies!)
dependency._dependents.remove(this);
}
}
Map<Type, InheritedElement>? _inheritedWidgets;
Set<InheritedElement>? _dependencies;
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies!.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
}
查找InheritedElement
的流程就是按照_inheritedWidgets
指针的流程向上查找的
那些节点需要监听InheritedElement
数据的改变就将InheritedElement
添加到那些节点的_dependencies
集合中
在这里我们只是看到了_inheritedWidgets
的查找流程,但是从这里我们并没有看到_inheritedWidgets
中的值是那里来的,继续看源码
InheritedElement源码
class InheritedElement extends ProxyElement {
// InheritedElement定义了一个_dependents
// 这里要和Element中的_dependencies分清楚
final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
// 这里覆盖了父类的方法
@override
void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.active);
final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
// 这里_inheritedWidgets不再指向父类的_inheritedWidgets
// 将父类中的_inheritedWidgets中的值拷贝了一份过来
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
// 然后将自己添加进去
_inheritedWidgets![widget.runtimeType] = this;
}
// 下面这三个方法都是对_dependents的操作
@protected
Object? getDependencies(Element dependent) {
return _dependents[dependent];
}
@protected
void setDependencies(Element dependent, Object? value) {
_dependents[dependent] = value;
}
@protected
void updateDependencies(Element dependent, Object? aspect) {
setDependencies(dependent, null);
}
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
@override
void notifyClients(InheritedWidget oldWidget) {
for (final Element dependent in _dependents.keys) {
notifyDependent(oldWidget, dependent);
}
}
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
}
先分析一下_inheritedWidgets
的传递过程,在Element
执行activate
和mount
的时候,会调用_updateInheritance
方法,把自己的_inheritedWidgets
指向_parent
的_inheritedWidgets
,而InheritedElement
覆盖了该方法,将_parent
的_inheritedWidgets
拷贝了一份过来,并且将自己也添加了进去,代码如下
//Element
void _updateInheritance() {
assert(_active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
//InheritedElement
@override
void _updateInheritance() {
assert(_active);
final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
_inheritedWidgets[widget.runtimeType] = this;
}
这了就回答了上面的第一个问题
// 1、这行代码是怎么查找到InheritedElement对象的?
context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget<T>>();
沿着_inheritedWidgets
指针向上查找,直到节点是InheritedElement
节点,InheritedElement
节点拷贝了父节点中的_inheritedWidgets
并将自己添加进去了,所以可以查到。
在update(...)
方法中判断了是否需要执行super.update(...)
,先看一下父类中的update
// InheritedElement
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
// Element
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
// InheritedElement
@override
void notifyClients(InheritedWidget oldWidget) {
for (final Element dependent in _dependents.keys) {
notifyDependent(oldWidget, dependent);
}
}
// InheritedElement
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
这就回答了第二个问题
// 2、组件为什么可以监听到InheritedElement数据的改变?
CustomInheritedWidget.of<T>(context).data
当InheritedElement
中数据更新的时候会遍历InheritedElement
中的_denpendents
集合,并执行集合中所有节点的didChangeDependencies()
函数,之后就是刷新的流程了 (吧?)
关于内存泄漏的疑虑?会不会循环引用?
在
Element
中有如下代码
@mustCallSuper
void deactivate() {
if (_dependencies != null && _dependencies!.isNotEmpty) {
for (final InheritedElement dependency in _dependencies!)
dependency._dependents.remove(this);
}
_inheritedWidgets = null;
}
@mustCallSuper
void unmount() {
_dependencies = null;
}
在unmount()
时会清空_dependencies
。
在deactivate()
时会清空InheritedElement
中的_dependents
。所以不会造成内存泄漏问题
InheritedWidget
应用很广泛,例如Theme
、Provider
、MediaQuery
等等。这里只是简单介绍了一点儿原理,很多细节都还没有深入了解,对于页面刷新的具体流程也没有研究。
参考文章
转载自:https://juejin.cn/post/7044528113077714975