Flutter - 升级到3.24后页面还会多次rebuild吗?🧐
# 一、问题回顾
在今年 2月份
,Flutter
官方正式了 3.19.0
版本,当时我们并没有着急尝鲜,直到 3月中旬
,为了解决安卓端的崩溃率飙升问题,被迫无奈升级到 3.19.3
, 结果踩了个坑,页面居然会多次 rebuild
~
在一番探索之后定位到根本原因,给官方提了 PR
,以及跟大家分享了补丁来临时处理这个问题。探索的相关内容总结和补丁在《Flutter - 升级3.19之后页面多次rebuild?🤨 (opens new window)》中,有兴趣的小伙伴可以看看。
# 二、波折
不过当时(日期:3.19
)所提 PR
并不被接受,因为其修改内容是直接给了个选项 createDependency
,代码如下:
static ModalRoute<T>? of<T extends Object?>(
BuildContext context, {
bool createDependency = true,
}) {
_ModalScopeStatus? widget;
if (createDependency) {
widget = context.dependOnInheritedWidgetOfExactType<_ModalScopeStatus>();
} else {
widget = context
.getElementForInheritedWidgetOfExactType<_ModalScopeStatus>()
?.widget as _ModalScopeStatus?;
}
return widget?.route as ModalRoute<T>?;
}
如果将 createDependency
参数设置为 false
,则不与 BuildContext
建立依赖关系,这被认定为是 footgun
。
footgun
: 指在尝试解决问题时,无意中导致更多问题或损害自己利益的行为
因为审核人担心如果允许了这个参数的加入,则后续可能会有开发者提出因为这个参数没有正确设置为 true
,而导致在确实需要重新构建时却无法正常自动刷新视图的 bug
问题。最后建议提供类似 ModalRoute.argumentsOf(context)
的方式,进而尽量避免造成 rebuild
。
这种方式的好处就跟 MediaQuery.of(context).viewInsets
对比 MediaQuery.viewInsetsOf(context)
一样,后者只会在 viewInsets
发生变化时才会 rebuild
, 而前者只要是 MediaQueryData
的值(如: size
)有变化就会 rebuild
。
很好的提议 👍,不过我当时(日期:4.16
)因为太忙,表明自己没时间去处理,并将 PR
关闭了,暂时靠补丁苟着。
当时有人提出另外一种较为安全的临时处理方案,就是遍历找出 _ModalScopeStatus
,从而拿到 ModalRoute
,以此来绕过绑定 BuildContext
的过程,相关代码如下。
/// replace ModalRoute.of(context)
class MyModalRoute {
static ModalRoute<dynamic>? of(BuildContext context) {
ModalRoute<dynamic>? route;
context.visitAncestorElements((element) {
if(element.widget.runtimeType.toString() == '_ModalScopeStatus') {
dynamic widget = element.widget;
route = widget.route as ModalRoute;
return false;
}
return true;
});
return route;
}
}
使用方式也很简单,只需要将原来的 ModalRoute.of(context)
替换为 MyModalRoute.of(context)
即可。
不过后面有人发现,在使用 代码混淆
功能时,该方案会失效。因为他是按字符串找到的 _ModalScopeStatus
,但是在混淆的时候会重命名相关类名,所以还在 3.19.x
小伙伴们,请自行根据自身情况,考虑是否使用。
时间过的很快,转眼来到 5月份
,一看这个问题还没有处理,于是抽了点时间按照建议,做了些调整,于 5.24
提交了 PR
(https://github.com/flutter/flutter/pull/149022 (opens new window)) ,并在 5.30
完成合并。
# 三、使用
此 PR
的改动也终于随 Flutter 3.24.0
正式版发布了,新增了如下三个方法:
isCurrentOf
: 对应isCurrent
,用于判断当前路由是否为栈顶路由。canPopOf
: 对应canPop
,用于判断当前路由是否可以弹出/关闭。settingsOf
: 对应settings
,当前路由的配置(路由名、参数)。
之前取路由参数的方式如下代码所示
final arguments = ModalRoute.of(context)?.settings.arguments;
需调整为
final arguments = ModalRoute.settingsOf(context)?.arguments;
对,这样就可以了,不调整的话还是会造成 rebuild
的 ~
# 四、最后
其实对于我个人而言,我还是比较钟意添加 createDependency
参数这种方式的,让开发者自由选择。
而且在做路由监听的时候依旧还是得用到 ModalRoute.of(context)
~
void main() {
runApp(MyApp());
}
RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
...
navigatorObservers: [routeObserver],
...,
);
}
}
class _MyPageState extends State<MyPage> with RouteAware {
...
void didChangeDependencies() {
super.didChangeDependencies();
// 这里用到了 ModalRoute.of(context)
routeObserver.subscribe(this, ModalRoute.of(context)!);
}
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
...
}
好吧,本篇到此结束,感谢大家的支持,我们下次再见! 👋
- 01
- Flutter - 危!3.24版本苹果审核被拒!11-13
- 02
- Flutter - 轻松搞定炫酷视差(Parallax)效果09-21
- 03
- Flutter - 轻松实现PageView卡片偏移效果09-08