注:本文代码基于Flutter SDK 3.13.5

一、前言

在之前解读Flutter源码之Navigator(命令式)一文中,已分析过Navigator的相关方法API与底层原理,知道了Navigator内部通过Overlay组件来管理路由页面堆栈。

Flutter1.22版本发布之后,可以发现本次对路由相关API的改动较大,官方表示由于传统的命令式并没有给开发者一种灵活的方式去直接管理路由栈,甚至觉得已经过时了,一点也不Flutter

1
As mentioned by a participant in one of Flutter's user studies, the API also feels outdated and not very Flutter-y.

Navigator 2.0引入了一套全新的声明式API,与以往不同,这类API可以实现用一组声明式的不可变的Page页面列表表示应用中的历史路由页面,从而转换成实际代码中NavigatorRoutes

因此,本文会带你逐步了解Navigator 2.0的实现方式以及它的底层原理。

二、为什么需要新的API

Flutter团队为什么要不惜这些代价对Navigator API做这么大的重构,肯定不只是为了让Navigator API的使用更像声明式那么简单,可能有如下几点原因:

1、原始Navigator API中的initialRoute参数,即系统默认的初始页面,在应用运行后就不能再更改了。这种情况下,如果用户接收到一个系统通知,点击后想要从当前的路由栈状态 [Main -> Profile -> Settings] 切换到新的 Main -> List -> Detail[id=24]Navigator 1.0并没有一种优雅的实现方式实现这种效果。

2、原始的命令式Navigator API只提供给了开发者一些非常针对性的接口,如pushpop等方法,而没有给出一种更灵活的方式让我们直接操作路由栈,这种做法其实与Flutter理念相违背。

3、在嵌套路由的情况下,手机设备自带的回退按钮只能由根Navigator响应。在目前的应用中,我们很多场景都需要在某个子tab内单独管理一个子路由栈,假设有这个场景,用户在子路由栈中做一系列路由操作之后,点击系统回退按钮,消失的将是整个上层的根路由,我们当然可以使用某种措施来避免这种状况,但归咎起来,这也不应该是应用开发者应该考虑的问题。

4、还有更重要的一点就是对Web的支持,声明式Navigator API支持浏览器的前进按钮、后退按钮以及地址栏索引,这是原始Navigator API做不到的。

三、分析关键API

Navigator 2.0提供了一系列全新的接口,可以实现将路由状态成为应用状态的一部分,并能够通过底层API实现参数解析的功能,新增的API一些是在Navigator中配置,另一些是在MaterialApp.router中配置。

3.1、在Navigator中配置

3.1.1、pages属性

pages属性需要传入一组Page对象,它与Navigator底层路由栈一一对应。换句话说,当你修改pages列表时其实也是对Navigator底层路由栈的修改。

Page继承自RouteSettings,它用来描述Route的配置,包含路由名称(name,如"/settings"),参数(arguments)等信息。如果你想push一个Route,不再是调用Navigatorpush方法,而是往pages列表添加一个Page对象。与此相反,如果你想pop一个Route,不再是调用Navigatorpop方法,而是移除pages列表最后一个Page对象。当修改pages列表后,底层会对之前pages与当前pages进行diff算法比较,然后更新Navigator底层路由栈。

Page的实现类有两个,一个是MaterialPage,另一个是CupertinoPage。一般情况下,这两个实现类已经够用了,当然了你也可以自定义Page

从下面源码也可以发现,PageWidget的相似度很高。Widget只保存组件配置信息,框架层内置了一个createElement()可以创建与之对应的Element实例。Page同样只保存页面路由相关信息,框架层也存在一个createRoute()方法可以创建与之对应的Route实例。

WidgetPage中也都有一个canUpdate()方法,用于底层判断Page是否已更新或改变,甚至连比较的条件都是runtimeTypekey

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
abstract class Page<T> extends RouteSettings {
  const Page({
    this.key,
    super.name,
    super.arguments,
    this.restorationId,
  });

  final LocalKey? key;

  final String? restorationId;

  bool canUpdate(Page<dynamic> other) {
    return other.runtimeType == runtimeType &&
           other.key == key;
  }

  @factory
  Route<T> createRoute(BuildContext context);

  @override
  String toString() => '${objectRuntimeType(this, 'Page')}("$name", $key, $arguments)';
}

除此之外,Navigator 2.0Navigator 1.0也可以一起工作,但是对于Navigator 1.0来说,使用push等方法添加到历史记录中的这些Route不对应于Page对象。

不对应于Page对象的Route称为无页路由,并绑定到与历史中位于其下方的Page对象对应的Route。如果一个页面被移除,而该页面上有其它使用push方法推送的无页路由,那么这些无页路由也会被移除。

最后,在Navigator中使用pages属性,例如下面代码。

1
2
3
4
5
6
Navigator(
  pages: [
    MaterialPage(child: HomePage(), key: ValueKey('/'), name: '/'),
    MaterialPage(child: SecondPage(), key: ValueKey('/secondPage'), name: '/secondPage'),
  ],
);

3.1.2、onPopPage属性

如果你使用了上面的pages属性,那么还必须提供onPopPage回调,否则在NavigatorinitState方法中进行assert校验时会抛出错误:

1
The Navigator.onPopPage must be provided to use the Navigator.pages API

onPopPage回调的作用是系统在pop页面时给你清理pages列表中该页面的机会。在onPopPage方法中,如果你同意关闭该页面,就可以调用route.didPop(result),该方法默认返回true。在Navigator中使用onPopPage属性,例如下面代码。

1
2
3
4
5
6
7
8
9
bool _onPopPage(Route<dynamic> route, result) {
  if (!route.didPop(result)) {
    return false;
  }

  // 这里做一些工作,例如:清理pages列表中栈顶page与通知pages更新等

  return true;
}

那么问题来了,当你接收到onPopPage回调并且同意关闭该页面(onPopPage回调返回true),但是你没有从pages中移除相应的Page对象时,这会发生什么现象呢?

首先Flutter底层依然会移除路由栈中该页面的历史记录,其次是当下次pages列表更新时,如果该Route对应的Page仍然存在,则会被解释为新的路由进行显示。

很明显,onPopPage回调已经把底层页面关闭的逻辑下放给开发者了,也就是说,某个页面是否能够关闭完全由你掌控,而不是单纯交给系统的Navigator.pop()。在onPopPage回调中,如果你不想关闭某个页面可以做相应的判断并返回false

至此,Navigator有了pagesonPopPage两大属性,就可以实现声明式的路由管理了。只是如果还要兼容Web端的话,例如处理浏览器的前进与后退按钮,同步浏览器地址栏中的链接等,就需要在MaterialApp.router中配置了。

3.2、在MaterialApp.router中配置

3.2.1、routeInformationProvider属性

routeInformationProvider属性需要传入一个RouteInformationProvider对象,可以为null,也可以使用自定义的RouteInformationProvider对象。如果不传入时Flutter会使用默认的PlatformRouteInformationProvider对象。

它向WidgetsBinding注册了一个WidgetsBindingObserver监听回调,当在某系统平台上触发了与导航相关的操作时,例如在Web端中,点击了浏览器的前进按钮与后退按钮等,WidgetsBinding就会把来自某系统平台的导航信息MethodCall转换为RouteInformation,然后分发给RouteInformationProvider

最后,RouteInformationProvider对象将持有的RouteInformation给到RouteInformationParser去解析处理。

如果该Router不依赖路由信息来构建其内容,则该RouteInformationProvider可以为空。在这种情况下,RouteInformationParser也必须为null

3.2.2、routeInformationParser属性

routeInformationProvider属性需要传入一个RouteInformationParser对象,它用于将来自routeInformationProvider的路由信息​​解析为通用数据类型,以便稍后由routerDelegate处理。

routeInformationProvider对象需要开发者自己去实现,它有两个要实现的方法:

  • parseRouteInformation方法

该方法的入参RouteInformation来自之前的RouteInformationProvider,在这里可以去将RouteInformation解析成你想要的数据类型。

该方法要求返回一个Future对象,如果可以同步计算结果,请考虑使用SynchronousFuture,这样Router就不需要等待下一个微任务将数据传递给RouterDelegate

除此之外,还有一个parseRouteInformationWithDependencies方法,与parseRouteInformation方法一样,只不过多了一个BuildContext入参。如果解析依赖于BuildContext中的其它依赖项,则可以实现parseRouteInformationWithDependencies

  • restoreRouteInformation方法

该方法可以从RouterDelegate对象的currentConfiguration返回的当前配置恢复RouteInformation

等后续再需要用到该RouteInformation对象,就可以重新解析使用,例如当执行了RouterStatedidChangeDependencies方法。

3.2.3、routerDelegate属性

routerDelegate属性需要传入一个RouterDelegate对象,它是一个委托,用于配置Navigator,并使用来自RouteInformationParser的解析结果。

另外,该委托也是Router的核心部分,用来响应来自引擎的推送路由和弹出路由意图,并通知Router进行重建。

routerDelegate对象需要开发者自己去实现,一般情况下,它有三个要实现的方法&属性:

  • setNewRoutePath方法

Router.routeInformationProvider报告操作系统已将新路由推送到应用程序时,该方法由Router调用,一般情况下,可以在setNewRoutePath方法中根据条件去更新你的pages

对于该方法的返回值,如果可以同步计算结果,请考虑使用SynchronousFuture,这样Router就不需要等待下一个微任务来安排构建。

  • build方法

通常,此方法返回一个适当配置的Navigator

  • currentConfiguration属性

Router检测到路由信息可能因重建而发生更改时调用。如果此getter返回非空,则Router将开始向引擎报告新的路由信息​​。在Web应用程序中,新的路由信息​​用于填充浏览器历史记录,以支持前进和后退按钮。

当重写此方法时,此getter返回的配置必须能够构造当前的应用程序状态,并在build方法中使用相同的配置来构建Widget。否则,浏览器的后退和前进按钮将无法正常工作。

默认情况下,该getter返回null,这会阻止Router上报路由信息。要选择加入,子类可以重写此getter以返回当前配置。

最多一个Router可以选择加入路由信息报告。通常,只有WidgetsApp.router创建的最顶层Router才应选择路由信息报告。

3.2.4、backButtonDispatcher属性

backButtonDispatcher属性决定是否处理Android后退按钮意图的委托。如果未提供,应用程序将默认创建RootBackButtonDispatcher。一般情况下,当用到BackButtonListener时才会与此关联。

3.3、MaterialApp.router的两种配置方式

MaterialApp.router有两种配置方式,一种是直接传入routeInformationParserrouterDelegate等属性。另一种是传入一个routerConfig

上面两种方式没区别,但是如果提供了routerConfig,则其它与路由器相关的委托、 routeInformationParserrouteInformationProviderrouterDelegatebackButtonDispatcher必须全部为null

3.4、Router交互原理

如果上面文字看不明白,可以看下面这张图,它展示了RouterDelegateRouterRouteInformationParser以及应用状态的交互原理。

结合上面图示,大致流程如下:

1、当系统打开新页面(如 “books/ 2”)时,RouteInformationParser会将其转换为应用中的具体数据类型T(如BooksRoutePath)。

2、该数据类型会被传递给RouterDelegatesetNewRoutePath方法,我们可以在这里更新路由状态(如通过设置selectedBookId)并调用notifyListeners响应该操作。

3、notifyListeners会通知Router重建RouterDelegate(通过 build() 方法)。

4、RouterDelegate.build()返回一个新的Navigator实例,并最终展示出我们想要打开的页面(如selectedBookId)。

四、Navigator 2.0实战

Navigator 2.0需要做到以下几点实现:

1、 实现解析路由信息,其中包括系统初始路由、浏览器前进按钮、后退按钮以及地址栏索引,这个对应于RouteInformationParserparseRouteInformation方法。

2、 实现pages的管理,对于push页面情况,如果要打开的页面在栈中已存在,则将该页面之上的所有页面进行出栈(这里可按需求处理,这是其中一个方案)。对于pop页面情况,将栈顶页面出栈即可。

3、 对于Page的创建,统一采用MaterialPage,这里不需要用到自定义Page,其所关联的Widget通过路由表配置后来获取,并且页面传参通过Widget的构造方法来完成。

4、 实现onPopPage方法,更新pages列表,最后通知RouterDelegate去更新Navigator

5、 实现恢复路由信息,对于Web端是很有必要的。这个对应于RouteInformationParserrestoreRouteInformation方法。

6、 实现统一的跳转逻辑,例如提供一个统一的pushNamed方法。这时可能有人会感到疑惑,说好的声明式,现在怎么又变回命令式了。其实要明白,声明式的尽头还是命令式。

不像之前Navigator 1.0那样的push去操作RoutepushNamed方法还是去更新pages,所以本质上也还是声明式的,为了方便更新pages不得已而为之。这样做也有一个好处,就是不需要通过Widget去传入大量回调。

7、 实现页面监听onResumeonPause方法,这也是一个比较有意思的功能,也只有Navigator 2.0才可以做到(注意:手机HOME退到后台,再切换到前台,这种情况不支持)。

8、 实现统一的页面弹出控制,把result回传给上一个页面,这里和Navigator 1.0通过Future去等待结果不同,因为无法获取Route.popped,所以这里通过监听回调来完成。

9、 …

OK,这里就不带大家一步步实现了,直接给出最终源码,一些欠考虑的地方请看注释,最后运行效果UI请自行运行程序查看。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      routeInformationParser: CustomRouteInformationParser(),
      routerDelegate: CustomRouterDelegate(),
    );
  }
}

class CustomRouteInformationParser extends RouteInformationParser<RouteSettings> {
  @override
  Future<RouteSettings> parseRouteInformation(RouteInformation routeInformation) {
    final Uri uri = routeInformation.uri;
    final String location = Uri.decodeComponent(uri.toString()); // == uri.toString
    debugPrint('CustomRouteInformationParser->parseRouteInformation:$location');
    // 这里只对移动端做了路由解析,例如:"/"、"/secondPage"这样的地址
    // 如果要适配Web端,这里路由解析不会那么简单,例如:"/details/3"这样的地址,这段path后面的3可以是传给详情页面的ID
    // 所以在本案例中,Web端通过地址栏输入地址来跳转到要创建的新页面或者点击浏览器前进后退按钮时,
    // 这里RouteSettings因为没有传入参数,此时会导致跳转后的页面获取不了参数
    final RouteSettings routeSettings = RouteSettings(name: uri.path.isEmpty ? '/' : uri.path);
    return SynchronousFuture<RouteSettings>(routeSettings);
  }

  @override
  RouteInformation? restoreRouteInformation(RouteSettings configuration) {
    debugPrint('CustomRouteInformationParser->restoreRouteInformation:${configuration.name}');
    return RouteInformation(uri: Uri.parse(configuration.name!));
  }
}

class CustomRouterDelegate extends RouterDelegate<RouteSettings>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<RouteSettings>, _PagesHandler {
  CustomRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>() {
    NavigatorHelper()
      ..registerOnJumpListener((RouteSettings routeSettings) {
        debugPrint('跳转回调:$routeSettings');
        _routeSettings = routeSettings;
        _updatePages();
      })
      ..registerOnRoutePopListener(_onRoutePop);

    // 问题:Hot Reload时,_pages为空,导致报错:The Navigator.pages must not be empty to use the Navigator.pages API
    // bug位置:NavigatorState->initState
    // 最初fix方案:当_pages为空时,给_pages赋值为const <Page<dynamic>>[]
    // 但是产生新问题:Hot Reload时,_pages为空,导致报错:Navigator.onGenerateRoute was null, but the route named
    // bug位置:NavigatorState->restoreState->defaultGenerateInitialRoutes->_routeNamed
    // 最终fix:在NavigatorState->initState执行之前,给_pages一个初始值即可
    _pages.add(_buildPage(name: Routes.rootPage));
  }

  Object? _result; // 从栈顶页面返回的数据

  void _onRoutePop([Object? result]) {
    _result = result;
    popRoute();
  }

  List<Page> _pages = [];

  RouteSettings _routeSettings = const RouteSettings(name: Routes.rootPage);

  @override
  Widget build(BuildContext context) {
    debugPrint('CustomRouterDelegate->build');
    return Navigator(
      key: navigatorKey,
      // 问题:如果_pages用final修饰,会导致oldWidget.pages != widget.pages判断不成立,
      // 从而无法执行_updatePages方法,也就触发不了路由栈映射,所以页面UI就无法跳转。
      // bug位置:NavigatorState->didUpdateWidget
      // fix:在每次build时,这里的_pages引用需要不相等,也就是要创建一个_pages新对象
      pages: _pages,
      onPopPage: _onPopPage,
    );
  }

  bool _onPopPage(Route<dynamic> route, result) {
    debugPrint('CustomRouterDelegate->_onPopPage');
    if (!route.didPop(result)) {
      return false;
    }
    _routeSettings = _pages[_pages.length - 2]; // 前一个页面的_routeSettings
    _updatePages(pop: true);

    if (_result != null) {
      NavigatorHelper().notifyRoutePop(_routeSettings, _result!);
      _result = null;
    }
    return true;
  }

  @override
  GlobalKey<NavigatorState>? navigatorKey;

  @override
  RouteSettings? get currentConfiguration => _routeSettings;

  @override
  Future<void> setNewRoutePath(RouteSettings configuration) {
    debugPrint('CustomRouterDelegate->setNewRoutePath: ${configuration.name}');
    _routeSettings = configuration;
    _updatePages();
    return SynchronousFuture<void>(null);
  }

  void _updatePages({bool pop = false}) {
    List<Page> tempPages = [..._pages]; // 用副本处理
    if (pop) {
      tempPages.removeLast();
    } else {
      final int index = _getCurPageIndex(tempPages, _routeSettings);
      if (index != -1) {
        if (tempPages[index] is MaterialPage) {
          MaterialPage materialPage = tempPages[index] as MaterialPage;
          debugPrint('将${materialPage.child}页面之上的所有页面进行出栈');
        }
        // 这里可根据实际情况处理,这里给个一般方案:
        // 要打开的页面在栈中已存在,则将该页面之上的所有页面进行出栈
        tempPages = tempPages.sublist(0, index + 1); // [0, index]
      } else {
        Page newPage = _buildPage(name: _routeSettings.name!, arguments: _routeSettings.arguments);
        if (newPage is MaterialPage) {
          debugPrint('创建新页面:${newPage.child}');
        }
        tempPages.add(newPage);
      }
    }

    NavigatorHelper().notifyRouteChange(_pages, tempPages);

    _pages = tempPages;

    List<Widget> widgets = _pages.whereType<MaterialPage>().map((page) => page.child).toList();
    debugPrint('此时路由栈:$widgets');

    notifyListeners();
  }
}

/// Pages处理相关方法
mixin _PagesHandler {
  int _getCurPageIndex(List<Page> pages, RouteSettings routeSettings) {
    return pages.indexWhere((page) => page.name == routeSettings.name); // 找不到返回-1
  }

  Page _buildPage({required String name, Object? arguments}) {
    return MaterialPage(
      child: _buildWidget(name: name, arguments: arguments),
      key: ValueKey(name),
      name: name,
      arguments: arguments,
    );
  }

  Widget _buildWidget({required String name, Object? arguments}) {
    dynamic widgetBuilder = Routes.routeMap[name];
    if (widgetBuilder == null) {
      return const UnknownPage();
    }
    if (arguments == null) {
      return widgetBuilder(name);
    }
    return widgetBuilder(name, arguments: arguments);
  }
}

/// 路由表
class Routes {
  static const String rootPage = '/';
  static const String secondPage = '/secondPage';
  static const String thirdPage = '/thirdPage';

  static var routeMap = {
    rootPage: (String name) => const HomePage(),
    secondPage: (String name, {Object? arguments}) => SecondPage(arguments: arguments),
    thirdPage: (String name, {Object? arguments}) => ThirdPage(arguments: arguments),
  };
}

class NavigatorHelper {
  static final NavigatorHelper _singleton = NavigatorHelper._internal();

  NavigatorHelper._internal();

  factory NavigatorHelper() => _singleton;

  OnRouteJumpListener? _onRouteJumpListener;

  void registerOnJumpListener(OnRouteJumpListener onRouteJumpListener) {
    _onRouteJumpListener = onRouteJumpListener;
  }

  void pushNamed({required String name, Object? arguments}) {
    _onRouteJumpListener?.call(RouteSettings(name: name, arguments: arguments));
  }

  final Map<String, OnRouteChangeListener> onRouteChangeListeners = {};

  void registerOnRouteChangeListener(String name, OnRouteChangeListener onRouteChangeListener) {
    onRouteChangeListeners[name] = onRouteChangeListener;
  }

  void unregisterOnRouteChangeListener(String name) => onRouteChangeListeners.remove(name);

  RouteSettings? previous;

  /// 遗憾之处:新建Page的onResume无法监听,因为notifyRouteChange方法先于
  /// 新建Page的initState方法执行,所以新建Page还没注册OnRouteChangeListeners
  /// fix:SchedulerBinding.instance.addPostFrameCallback
  /// 但是,当页面很复杂并且渲染出现丢帧时,不知道此处回调是否在build方法之后执行,如果不在,则此处该处理无意义
  void notifyRouteChange(List<Page> previousPages, List<Page> currentPages) {
    if (previousPages == currentPages) {
      return;
    }
    // 此方法不请求新帧。如果帧已经在进行中并且帧后回调的执行尚未开始,则注册的回调将在当前帧结束时执行。否则,注册的回调将在下一帧之后执行(无论何时,如果有的话)
    // 帧后回调无法取消注册。它们只被调用一次
    SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
      debugPrint('分发RouteChange回调:$onRouteChangeListeners');
      onRouteChangeListeners.forEach((key, value) {
        if (previous?.name != key && currentPages.last.name == key) {
          value(RoutePageStatus.resume);
        } else if (previous?.name == key && currentPages.last.name != key) {
          value(RoutePageStatus.pause);
        }
      });
      previous =
          RouteSettings(name: currentPages.last.name, arguments: currentPages.last.arguments);
    });
  }

  OnRoutePopListener? _onRoutePopListener;

  void registerOnRoutePopListener(OnRoutePopListener onRoutePopListener) {
    _onRoutePopListener = onRoutePopListener;
  }

  void pop<T extends Object?>([T? result]) {
    _onRoutePopListener?.call(result);
  }

  final Map<String, OnRoutePopListener> _onRoutePopListeners = {};

  void registerOnRoutePopListenerForResult(String name, OnRoutePopListener onRoutePopListener) {
    _onRoutePopListeners[name] = onRoutePopListener;
  }

  void unregisterOnRoutePopListenerForResult(String name) {
    _onRoutePopListeners.remove(name);
  }

  void notifyRoutePop(RouteSettings settings, Object result) {
    _onRoutePopListeners.forEach((key, value) {
      if (key == settings.name) value(result);
    });
  }
}

typedef OnRouteJumpListener = void Function(RouteSettings routeSettings);

typedef OnRouteChangeListener = void Function(RoutePageStatus routePageStatus);

enum RoutePageStatus { resume, pause }

typedef OnRoutePopListener<T extends Object?> = void Function([T? result]);

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  void initState() {
    super.initState();
    debugPrint('HomePage->initState');
    NavigatorHelper().registerOnRouteChangeListener(Routes.rootPage,
        (RoutePageStatus routePageStatus) {
      if (routePageStatus == RoutePageStatus.resume) {
        debugPrint('HomePage->onResume');
      } else if (routePageStatus == RoutePageStatus.pause) {
        debugPrint('HomePage->onPause');
      }
    });
    NavigatorHelper().registerOnRoutePopListenerForResult(Routes.rootPage, ([result]) {
      if (!mounted) return;
      ScaffoldMessenger.of(context)
        ..removeCurrentSnackBar()
        ..showSnackBar(SnackBar(content: Text('$result')));
    });
  }

  @override
  void dispose() {
    super.dispose();
    NavigatorHelper().unregisterOnRouteChangeListener(Routes.rootPage);
    NavigatorHelper().unregisterOnRoutePopListenerForResult(Routes.rootPage);
  }

  @override
  Widget build(BuildContext context) {
    debugPrint('HomePage->build');
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('HomePage'),
      ),
      body: Center(
        child: OutlinedButton(
          onPressed: () => NavigatorHelper().pushNamed(name: Routes.secondPage, arguments: 20),
          child: const Text('带参数跳转SecondPage'),
        ),
      ),
    );
  }
}

class SecondPage extends StatefulWidget {
  final Object? arguments;

  const SecondPage({super.key, this.arguments});

  @override
  State<SecondPage> createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage> {
  @override
  void initState() {
    super.initState();
    debugPrint('SecondPage->initState');
    NavigatorHelper().registerOnRouteChangeListener(Routes.secondPage,
        (RoutePageStatus routePageStatus) {
      if (routePageStatus == RoutePageStatus.resume) {
        debugPrint('SecondPage->onResume');
      } else if (routePageStatus == RoutePageStatus.pause) {
        debugPrint('SecondPage->onPause');
      }
    });
    NavigatorHelper().registerOnRoutePopListenerForResult(Routes.secondPage, ([result]) {
      if (!mounted) return;
      ScaffoldMessenger.of(context)
        ..removeCurrentSnackBar()
        ..showSnackBar(SnackBar(content: Text('$result')));
    });
  }

  @override
  void dispose() {
    super.dispose();
    NavigatorHelper().unregisterOnRouteChangeListener(Routes.secondPage);
    NavigatorHelper().unregisterOnRoutePopListenerForResult(Routes.secondPage);
  }

  @override
  Widget build(BuildContext context) {
    debugPrint('SecondPage->build');
    int itemCount = 0;
    if (widget.arguments != null && widget.arguments is int) {
      itemCount = widget.arguments as int;
    }
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('SecondPage'),
      ),
      body: ListView.builder(
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(
              '第$index条',
              style: const TextStyle(fontSize: 24),
            ),
            trailing: OutlinedButton(
              onPressed: () {
                if (index == 0) {
                  NavigatorHelper().pop(999999);
                } else {
                  NavigatorHelper().pushNamed(name: Routes.thirdPage, arguments: index);
                }
              },
              child: index == 0 ? const Text('带参数999999返回HomePage') : const Text('带参数跳转ThirdPage'),
            ),
          );
        },
        itemCount: itemCount,
      ),
    );
  }
}

class ThirdPage extends StatelessWidget {
  final Object? arguments;

  const ThirdPage({super.key, this.arguments});

  @override
  Widget build(BuildContext context) {
    debugPrint('ThirdPage->build');
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('ThirdPage'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              '获取SecondPage的传参:$arguments',
              style: const TextStyle(fontSize: 24),
            ),
            const SizedBox(height: 10),
            OutlinedButton(
              onPressed: () => NavigatorHelper().pushNamed(name: Routes.rootPage),
              child: const Text('返回首页'),
            ),
            const SizedBox(height: 10),
            OutlinedButton(
              onPressed: () => NavigatorHelper().pop(888888),
              child: const Text('带参数666666返回SecondPage'),
            ),
          ],
        ),
      ),
    );
  }
}

class UnknownPage extends StatelessWidget {
  const UnknownPage({super.key});

  @override
  Widget build(BuildContext context) {
    return const ColoredBox(
      color: Colors.redAccent,
      child: Center(
        child: Text(
          '未知页面',
          style: TextStyle(
            fontSize: 24,
            color: Colors.white,
          ),
        ),
      ),
    );
  }
}

五、源码分析

5.1、系统平台Navigation信息传递

不禁让人联想到WidgetsBinding,因为它是widgets layerFlutter engine之间的粘合剂,看下它的initInstances方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@override
void initInstances() {
  super.initInstances();
  _instance = this;
  ...
  // 注册了用于在此通道上接收方法调用的回调
  // 当系统平台触发导航操作时,例如点击Web端的前进按钮等,就会回调到_handleNavigationInvocation方法
  SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
  ...
}

看下WidgetsBinding_handleNavigationInvocation 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Future<dynamic> _handleNavigationInvocation(MethodCall methodCall) {
  switch (methodCall.method) {
    // 当系统希望删除当前路由时(例如,如果用户点击系统级后退按钮),就会调用它。
    case 'popRoute':
      return handlePopRoute();
    // 当操作系统指示应用程序打开特定页面时,使用单个字符串参数调用它。
    case 'pushRoute':
      return handlePushRoute(methodCall.arguments as String);
    // 当操作系统指示应用程序打开特定页面时,将使用包含位置字符串和状态对象的Map来调用它。这些参数存储在Map中的键“location”和“state”下。
    case 'pushRouteInformation':
      return _handlePushRouteInformation(methodCall.arguments as Map<dynamic, dynamic>);
  }
  return Future<dynamic>.value();
}

这里以handlePushRoute方法为例,看下它的源码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
final List<WidgetsBindingObserver> _observers = <WidgetsBindingObserver>[];

void addObserver(WidgetsBindingObserver observer) => _observers.add(observer);

bool removeObserver(WidgetsBindingObserver observer) => _observers.remove(observer);

@protected
@mustCallSuper
Future<void> handlePushRoute(String route) async {
  // 将route字符串转化为RouteInformation
  final RouteInformation routeInformation = RouteInformation(uri: Uri.parse(route));
  // 遍历_observers,只要有对象注册了WidgetsBindingObserver,这里就会通过didPushRouteInformation方法分发RouteInformation
  for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) {
    if (await observer.didPushRouteInformation(routeInformation)) {
      return;
    }
  }
}

看下WidgetsBindingObserverdidPushRouteInformation方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Future<bool> didPushRouteInformation(RouteInformation routeInformation) {
  final Uri uri = routeInformation.uri;
  return didPushRoute(
    Uri.decodeComponent(
      Uri(
        path: uri.path.isEmpty ? '/' : uri.path,
        queryParameters: uri.queryParametersAll.isEmpty ? null : uri.queryParametersAll,
        fragment: uri.fragment.isEmpty ? null : uri.fragment,
      ).toString(),
    ),
  );
}

@Deprecated(
  'Use didPushRouteInformation instead. '
  'This feature was deprecated after v3.8.0-14.0.pre.'
)
Future<bool> didPushRoute(String route) => Future<bool>.value(false);

可以看到,在WidgetsBindingObserverdidPushRouteInformation方法中执行了didPushRoute方法,什么也没做。

其实这里的didPushRouteInformation方法由注册了WidgetsBindingObserver的对象去实现,这里是PlatformRouteInformationProvider

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// PlatformRouteInformationProvider混入了WidgetsBindingObserver,ChangeNotifier
class PlatformRouteInformationProvider extends RouteInformationProvider with WidgetsBindingObserver, ChangeNotifier {

  @override
  RouteInformation get value => _value;
  RouteInformation _value;

  RouteInformation _valueInEngine = RouteInformation(uri: Uri.parse(WidgetsBinding.instance.platformDispatcher.defaultRouteName));

  void _platformReportsNewRouteInformation(RouteInformation routeInformation) {
    if (_value == routeInformation) {
      return;
    }
    // 将RouteInformation缓存一下,后续通过_value可以获取
    _value = routeInformation;
    _valueInEngine = routeInformation;
    // 这里非常关键,因为混入了ChangeNotifier,所以会触发addListener传入的VoidCallback回调
    // 这里调用地方等下讲
    notifyListeners();
  }

  // 外部调用PlatformRouteInformationProvider的addListener方法时,就会注册WidgetsBindingObserver回调
  // 这里调用地方等下讲
  @override
  void addListener(VoidCallback listener) {
    if (!hasListeners) {
      WidgetsBinding.instance.addObserver(this);
    }
    super.addListener(listener);
  }

  @override
  void removeListener(VoidCallback listener) {
    super.removeListener(listener);
    if (!hasListeners) {
      WidgetsBinding.instance.removeObserver(this);
    }
  }

  @override
  Future<bool> didPushRouteInformation(RouteInformation routeInformation) async {
    assert(hasListeners);
    _platformReportsNewRouteInformation(routeInformation);
    return true;
  }
}

至此,系统Navigation信息传递给了PlatformRouteInformationProvider

5.2、_MaterialAppStatebuild方法

MaterialApp入手,因为它是一个StatefulWidget,所以看下 _MaterialAppStatebuild方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@override
Widget build(BuildContext context) {
  // 执行_buildWidgetApp方法
  Widget result = _buildWidgetApp(context);
  ...
  return ScrollConfiguration(
    behavior: widget.scrollBehavior ?? const MaterialScrollBehavior(),
    child: HeroControllerScope(
      controller: _heroController,
      child: result,
    ),
  );
}

看下 _MaterialAppState_buildWidgetApp 方法,可以发现它根据 _usesRouter 来选择不同的WidgetsApp构造方法进行创建WidgetsApp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// _usesRouter表示是否使用Router,此处为true
bool get _usesRouter => widget.routerDelegate != null || widget.routerConfig != null;

Widget _buildWidgetApp(BuildContext context) {
  ...
  if (_usesRouter) {
    // Router方式实现路由导航(声明式)
    return WidgetsApp.router(
      key: GlobalObjectKey (this),
      routeInformationProvider: widget.routeInformationProvider,
      routeInformationParser: widget.routeInformationParser,
      routerDelegate: widget.routerDelegate,
      routerConfig: widget.routerConfig,
      backButtonDispatcher: widget.backButtonDispatcher,
      ...
    );
  }
  // 直接Navigator方式实现路由导航(命令式)
  return WidgetsApp(
    ...
  );
}

先看下 _WidgetsAppStateinitState方法。

1
2
3
4
5
6
@override
void initState() {
  super.initState();
  _updateRouting();
  ...
}

看下 _WidgetsAppState_updateRouting 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

String get _initialRouteName => WidgetsBinding.instance.platformDispatcher.defaultRouteName != Navigator.defaultRouteName
  ? WidgetsBinding.instance.platformDispatcher.defaultRouteName
  : widget.initialRoute ?? WidgetsBinding.instance.platformDispatcher.defaultRouteName;

// 此处_usesRouterWithDelegates为true
bool get _usesRouterWithDelegates => widget.routerDelegate != null;

void _updateRouting({WidgetsApp? oldWidget}) {
  if (_usesRouterWithDelegates) {
    assert(!_usesNavigator && !_usesRouterWithConfig);
    _clearNavigatorResource();
    // 如果没有传入routeInformationProvider,那么创建一个默认的PlatformRouteInformationProvider
    if (widget.routeInformationProvider == null && widget.routeInformationParser != null) {
      _defaultRouteInformationProvider ??= PlatformRouteInformationProvider(
        // 此处创建的RouteInformation会赋值给PlatformRouteInformationProvider中的_value
        initialRouteInformation: RouteInformation(
          // 设置初始路由_initialRouteName,一般为'/'
          uri: Uri.parse(_initialRouteName),
        ),
      );
    } else {
      _defaultRouteInformationProvider?.dispose();
      _defaultRouteInformationProvider = null;
    }
    // 如果没有传入backButtonDispatcher,那么创建一个默认的RootBackButtonDispatcher
    if (widget.backButtonDispatcher == null) {
      _defaultBackButtonDispatcher ??= RootBackButtonDispatcher();
    }

  } else if (_usesNavigator) {
    ...
  } else {
    ...
  }
  ...
}

再看下 _WidgetsAppStatebuild 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// 此处_usesRouterWithDelegates为true
bool get _usesRouterWithDelegates => widget.routerDelegate != null;
bool get _usesRouterWithConfig => widget.routerConfig != null;
bool get _usesNavigator => widget.home != null
    || (widget.routes?.isNotEmpty ?? false)
    || widget.onGenerateRoute != null
    || widget.onUnknownRoute != null;

@override
Widget build(BuildContext context) {
  Widget? routing;
  if (_usesRouterWithDelegates) {
    // Router方式实现路由导航(声明式)
    routing = Router<Object>(
      restorationScopeId: 'router',
      routeInformationProvider: _effectiveRouteInformationProvider,
      routeInformationParser: widget.routeInformationParser,
      routerDelegate: widget.routerDelegate!,
      backButtonDispatcher: _effectiveBackButtonDispatcher,
    );
  } else if (_usesNavigator) {
    // 直接Navigator方式实现路由导航(命令式)
    assert(_navigator != null);
    routing = FocusScope(
      debugLabel: 'Navigator Scope',
      autofocus: true,
      child: Navigator(
        clipBehavior: Clip.none,
        restorationScopeId: 'nav',
        key: _navigator,
        initialRoute: _initialRouteName,
        onGenerateRoute: _onGenerateRoute,
        onGenerateInitialRoutes: widget.onGenerateInitialRoutes == null
          ? Navigator.defaultGenerateInitialRoutes
          : (NavigatorState navigator, String initialRouteName) {
            return widget.onGenerateInitialRoutes!(initialRouteName);
          },
        onUnknownRoute: _onUnknownRoute,
        observers: widget.navigatorObservers!,
        reportsRouteUpdateToEngine: true,
      ),
    );
  } else if (_usesRouterWithConfig) {
    // 带config的Router方式实现路由导航(声明式)
    routing = Router<Object>.withConfig(
      restorationScopeId: 'router',
      config: widget.routerConfig!,
    );
  }

  ...

  return RootRestorationScope(
    restorationId: widget.restorationScopeId,
    child: SharedAppData(
      child: Shortcuts(
        debugLabel: '<Default WidgetsApp Shortcuts>',
        shortcuts: widget.shortcuts ?? WidgetsApp.defaultShortcuts,
        child: DefaultTextEditingShortcuts(
          child: Actions(
            ...
            child: FocusTraversalGroup(
              policy: ReadingOrderTraversalPolicy(),
              child: TapRegionSurface(
                child: ShortcutRegistrar(
                  child: Localizations(
                    locale: appLocale,
                    delegates: _localizationsDelegates.toList(),
                    // 最后将包装多层的routing,放到这里
                    child: title,
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    ),
  );
}

上面已知道 _WidgetsAppStatebuild 方法中用到了Router,而Router是一个StatefulWidget,看下创建的 _RouterStateinitState方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@override
void initState() {
  super.initState();
  // routeInformationProvider注册了监听,也就是routeInformationProvider内部执行notifyListeners方法时,_handleRouteInformationProviderNotification方法就会被回调
  widget.routeInformationProvider?.addListener(_handleRouteInformationProviderNotification);
  widget.backButtonDispatcher?.addCallback
  (_handleBackButtonDispatcherNotification);
  // routerDelegate注册了监听,也就是routerDelegate内部执行notifyListeners方法时,_handleRouterDelegateNotification方法就会被回调
  widget.routerDelegate.addListener(_handleRouterDelegateNotification);
}

再看下 _RouterStatebuild方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@override
Widget build(BuildContext context) {
  return UnmanagedRestorationScope(
    bucket: bucket,
    // _RouterScope是一个InheritedWidget,外部可以获取它的属性routerState,比如Route的navigate方法
    child: _RouterScope(
      routeInformationProvider: widget.routeInformationProvider,
      backButtonDispatcher: widget.backButtonDispatcher,
      routeInformationParser: widget.routeInformationParser,
      routerDelegate: widget.routerDelegate,
      // 此处传入了routerState
      routerState: this,
      child: Builder(
        // Use a Builder so that the build method below will have a
        // BuildContext that contains the _RouterScope. This also prevents
        // dependencies look ups in routerDelegate from rebuilding Router
        // widget that may result in re-parsing the route information.
        // 此处调用的是routerDelegate的build方法,也就是说只要Router通过setState更新UI,routerDelegate的build方法就会被调用
        builder: widget.routerDelegate.build,
      ),
    ),
  );
}

在上面实战中,Navigator定义在routerDelegatebuild方法里。Navigator是一个StatefulWidget,看下它创建的 NavigatorStateinitState方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@override
void initState() {
  super.initState();
  assert(() {
    // 初始化时,如果pages为空,就会抛出异常
    if (widget.pages != const <Page<dynamic>>[]) {
      // This navigator uses page API.
      if (widget.pages.isEmpty) {
        FlutterError.reportError(
          FlutterErrorDetails(
            exception: FlutterError(
              'The Navigator.pages must not be empty to use the '
              'Navigator.pages API',
            ),
            library: 'widget library',
            stack: StackTrace.current,
          ),
        );
      } else if (widget.onPopPage == null) {
        // 如果使用了pages,那么onPopPage也必须不为null,否则抛出异常
        FlutterError.reportError(
          FlutterErrorDetails(
            exception: FlutterError(
              'The Navigator.onPopPage must be provided to use the '
              'Navigator.pages API',
            ),
            library: 'widget library',
            stack: StackTrace.current,
          ),
        );
      }
    }
    return true;
  }());
  ...
}

关于Navigator的更多细节,这里就不展开讲解了,可参考前言中提到的一文。

5.3、根布局路由信息解析

当执行到 _RouterStatedidChangeDependencies方法,看下它的源码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@override
void didChangeDependencies() {
  _routeParsePending = true;
  // 分析一
  super.didChangeDependencies();
  // super.didChangeDependencies可能已经解析了路由信息。如果didChangeDependencies是由状态恢复或首次构建触发的,则可能会发生这种情况。
  // The super.didChangeDependencies may have parsed the route information.
  // This can happen if the didChangeDependencies is triggered by state
  // restoration or first build.
  // 分析二
  if (widget.routeInformationProvider != null && _routeParsePending) {
    _processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setNewRoutePath);
  }
  _routeParsePending = false;
  // 分析三
  _maybeNeedToReportRouteInformation();
}

分析一:

因为 _RouterState 混入了RestorationMixin,所以执行RestorationMixindidChangeDependencies方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@override
void didChangeDependencies() {
  super.didChangeDependencies();

  final RestorationBucket? oldBucket = _bucket;
  final bool needsRestore = restorePending;
  _currentParent = RestorationScope.maybeOf(context);

  final bool didReplaceBucket = _updateBucketIfNecessary(parent: _currentParent, restorePending: needsRestore);

  // 执行这里
  if (needsRestore) {
    _doRestore(oldBucket);
  }
  if (didReplaceBucket) {
    assert(oldBucket != _bucket);
    oldBucket?.dispose();
  }
}

看下RestorationMixin_doRestore 方法。

1
2
3
4
5
6
void _doRestore(RestorationBucket? oldBucket) {
  ...
  restoreState(oldBucket, _firstRestorePending);
  _firstRestorePending = false;
  ...
}

RestorationMixin_doRestore 方法中,执行了restoreState方法,该方法由子类 _RouterState 实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
  // 注册RestorableProperty以进行状态恢复
  registerForRestoration(_routeInformation, 'route');

  // 此处_routeInformation.value为null
  if (_routeInformation.value != null) {
    assert(widget.routeInformationParser != null);
    _processRouteInformation(_routeInformation.value!, () => widget.routerDelegate.setRestoredRoutePath);
  } else if (widget.routeInformationProvider != null) {
    // 执行这里,其中routeInformationProvider!.value已经在_WidgetsAppState的 _updateRouting方法中赋值了,后面则是routerDelegate的setInitialRoutePath方法
    _processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setInitialRoutePath);
  }
}

看下 _RouterState_processRouteInformation 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void _processRouteInformation(RouteInformation information, ValueGetter<_RouteSetter<T>> delegateRouteSetter) {
  assert(_routeParsePending);
  // 此处_routeParsePending赋值为false,表示路由不需要延迟解析。
  _routeParsePending = false;
  _currentRouterTransaction = Object();
  // 执行routeInformationParser的parseRouteInformationWithDependencies方法,
  // 该方法返回一个Future,等到该Future执行完成,就会执行_processParsedRouteInformation方法
  widget.routeInformationParser!
    .parseRouteInformationWithDependencies(information, context)
    .then<void>(_processParsedRouteInformation(_currentRouterTransaction, delegateRouteSetter));
}

看下RouteInformationParserparseRouteInformationWithDependencies方法。

1
2
3
Future<T> parseRouteInformationWithDependencies(RouteInformation routeInformation, BuildContext context) {
  return parseRouteInformation(routeInformation);
}

RouteInformationParserparseRouteInformationWithDependencies方法中,执行了parseRouteInformation方法,该方法由子类CustomRouteInformationParser实现。

至此,CustomRouteInformationParserparseRouteInformation方法被触发,上面实战示例中返回了一个SynchronousFuture,表示同步等待结果。

继续看下 _RouterState_processParsedRouteInformation 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 此处delegateRouteSetter为routerDelegate的setInitialRoutePath方法
_RouteSetter<T> _processParsedRouteInformation(Object? transaction, ValueGetter<_RouteSetter<T>> delegateRouteSetter) {
  return (T data) async {
    if (_currentRouterTransaction != transaction) {
      return;
    }
    // 等待routerDelegate的setInitialRoutePath方法执行完成
    await delegateRouteSetter()(data);
    if (_currentRouterTransaction == transaction) {
      // 执行这里
      _rebuild();
    }
  };
}

看下RouterDelegatesetInitialRoutePath方法。

1
2
3
Future<void> setInitialRoutePath(T configuration) {
  return setNewRoutePath(configuration);
}

RouterDelegatesetInitialRoutePath方法中,执行了setNewRoutePath方法,该方法由子类CustomRouterDelegate实现。

至此,CustomRouterDelegatesetNewRoutePath方法被触发,上面实战示例中返回了一个SynchronousFuture,表示同步等待结果。

在上面实战示例中,CustomRouterDelegatesetNewRoutePath方法里执行了 _updatePages 方法,在 _updatePages 方法中又会执行notifyListeners方法。

之前讲的 _RouterStateinitState中,routerDelegate添加了监听回调方法 _handleRouterDelegateNotification,此时就会执行该方法。

1
2
3
4
5
void _handleRouterDelegateNotification() {
  // 此处触发Router的UI更新,那么CustomRouterDelegate的build方法也会更新
  setState(() {/* routerDelegate wants to rebuild */});
  _maybeNeedToReportRouteInformation();
}

_RouterState_handleRouterDelegateNotification 方法中,执行了 _maybeNeedToReportRouteInformation 方法。

1
2
3
4
5
6
void _maybeNeedToReportRouteInformation() {
  // 这里讲RouteInformation赋值给_routeInformation.value
  _routeInformation.value = _retrieveNewRouteInformation();
  _currentIntentionToReport ??= RouteInformationReportingType.none;
  _scheduleRouteInformationReportingTask();
}

看下 _RouterState_maybeNeedToReportRouteInformation 方法。

1
2
3
4
5
6
7
8
9
RouteInformation? _retrieveNewRouteInformation() {
  // 执行了routerDelegate的currentConfiguration,获取当前配置
  final T? configuration = widget.routerDelegate.currentConfiguration;
  if (configuration == null) {
    return null;
  }
  // 执行routeInformationParser的restoreRouteInformation方法,返回一个RouteInformation,这是第一次执行restoreRouteInformation方法
  return widget.routeInformationParser?.restoreRouteInformation(configuration);
}

等待routerDelegatesetInitialRoutePath方法执行完成,就会执行then之后的方法,也就是 _processParsedRouteInformation 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
_RouteSetter<T> _processParsedRouteInformation(Object? transaction, ValueGetter<_RouteSetter<T>> delegateRouteSetter) {
  return (T data) async {
    if (_currentRouterTransaction != transaction) {
      return;
    }
    await delegateRouteSetter()(data);
    if (_currentRouterTransaction == transaction) {
      // 执行这里
      _rebuild();
    }
  };
}

继续看下 _RouterState_rebuild 方法。

1
2
3
4
5
6
Future<void> _rebuild([void value]) {
  // 此处触发Router的UI更新,那么CustomRouterDelegate的build方法也会更新
  setState(() {/* routerDelegate is ready to rebuild */});
  _maybeNeedToReportRouteInformation();
  return SynchronousFuture<void>(value);
}

_RouterState_maybeNeedToReportRouteInformation 方法中,接着又会执行routeInformationParserrestoreRouteInformation方法,这是第二次执行。

分析二:

回到 _RouterStatedidChangeDependencies方法,前面分析一super.didChangeDependencies中已经解析了路由信息,并且 _routeParsePending 被赋值为false,所以这里if条件不成立。

1
2
3
if (widget.routeInformationProvider != null && _routeParsePending) {
  _processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setNewRoutePath);
}

分析三:

回到 _RouterStatedidChangeDependencies方法,继续执行了 _RouterState_maybeNeedToReportRouteInformation 方法,这与分析一中的逻辑一样,也就是说会再次执行routeInformationParserrestoreRouteInformation方法,这是第三次执行。

5.4、Navigatorpages转化

CustomRouterDelegatebuild被触发后,因为此时pages发生了变化,所以Navigator会执行NavigatorStatedidUpdateWidget方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@override
void didUpdateWidget(Navigator oldWidget) {
  super.didUpdateWidget(oldWidget);
  ...
  if (oldWidget.pages != widget.pages && !restorePending) {
    assert(() {
      if (widget.pages.isEmpty) {
        FlutterError.reportError(
          FlutterErrorDetails(
            exception: FlutterError(
              'The Navigator.pages must not be empty to use the '
              'Navigator.pages API',
            ),
            library: 'widget library',
            stack: StackTrace.current,
          ),
        );
      }
      return true;
    }());
    _updatePages();
  }

  // Route进行重建
  for (final _RouteEntry entry in _history) {
    entry.route.changedExternalState();
  }
}

NavigatorStatedidUpdateWidget方法中,执行了 _updatePages 方法。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
void _updatePages() {
  ...
  // 这尝试将新页面列表 (widget.pages) 与旧 _RouteEntry(s) 列表 (_history) 进行比较,并生成新的 _RouteEntry(s) 列表作为新的 _history 列表。
  // 此方法大致遵循 RenderObjectElement.updateChildren 的相同轮廓。
  // This attempts to diff the new pages list (widget.pages) with
  // the old _RouteEntry(s) list (_history), and produces a new list of
  // _RouteEntry(s) to be the new list of _history. This method roughly
  // follows the same outline of RenderObjectElement.updateChildren.
  //
  // 它尝试优化的情况是:
  // The cases it tries to optimize for are:
  //  - 旧列表为空
  //  - 新列表中的所有页面都可以匹配旧列表中的基于页面的路由,并且它们的顺序相同。
  //  - 新列表中的所有页面都可以匹配旧列表中的基于页面的路由,并且它们的顺序相同。
  //  - the old list is empty
  //  - All the pages in the new list can match the page-based routes in the old
  //    list, and their orders are the same.
  //  - there is an insertion or removal of one or more page-based route in
  //    only one place in the list
  // 如果带有键的基于页面的路由同时存在于两个列表中,则它将同步。没有密钥的基于页面的路由可能会同步,但不能保证。
  // If a page-based route with a key is in both lists, it will be synced.
  // Page-based routes without keys might be synced but there is no guarantee.

  // 一般的做法是向后同步整个新列表,如下所示:
  // The general approach is to sync the entire new list backwards, as follows:
  // 1. 从底部开始遍历列表,同步节点并记录无页路由,直到不再有匹配的节点。
  // 1. Walk the lists from the bottom, syncing nodes, and record pageless routes,
  //    until you no longer have matching nodes.
  // 2. 从顶部开始遍历列表,不同步节点,直到不再有匹配的节点。我们将在最后同步这些节点。我们现在不同步它们,因为我们想要从头到尾按顺序同步所有节点。
  // 2. Walk the lists from the top, without syncing nodes, until you no
  //    longer have matching nodes. We'll sync these nodes at the end. We
  //    don't sync them now because we want to sync all the nodes in order
  //    from beginning to end.
  // 此时,我们将新旧列表缩小到节点不再匹配的程度。
  // At this point we narrowed the old and new lists to the point
  // where the nodes no longer match.
  // 3. 遍历旧列表的缩小部分以获取键列表。
  // 3. Walk the narrowed part of the old list to get the list of
  //    keys.
  // 4. 向前移动新列表的缩小部分:
  // 4. Walk the narrowed part of the new list forwards:
  //     * 为非键控项创建一个新的_RouteEntry,并为transitionDelegate记录它们。
  //     * Create a new _RouteEntry for non-keyed items and record them for
  //       transitionDelegate.
  //     * 将键控项与源同步(如果存在)。
  //     * Sync keyed items with the source if it exists.
  // 5. 再次遍历旧列表的缩小部分以记录需要为transitionDelegate删除的_RouteEntry(s)以及无页路由。
  // 5. Walk the narrowed part of the old list again to records the
  //    _RouteEntry(s), as well as pageless routes, needed to be removed for
  //    transitionDelegate.
  // 5. 再次走到列表顶部,同步节点并记录无页路由。
  // 5. Walk the top of the list again, syncing the nodes and recording
  //    pageless routes.
  // 6. 使用transitionDelegate 明确决定 _RouteEntry(s) 如何在屏幕上过渡或过渡
  // 6. Use transitionDelegate for explicit decisions on how _RouteEntry(s)
  //    transition in or off the screens.
  // 7. 将无页路由填回新历史记录中。
  // 7. Fill pageless routes back into the new history.

  bool needsExplicitDecision = false;
  int newPagesBottom = 0;
  int oldEntriesBottom = 0;
  int newPagesTop = widget.pages.length - 1;
  int oldEntriesTop = _history.length - 1;

  final List<_RouteEntry> newHistory = <_RouteEntry>[];
  final Map<_RouteEntry?, List<_RouteEntry>> pageRouteToPagelessRoutes = <_RouteEntry?, List<_RouteEntry>>{};

  // 更新列表底部。
  // Updates the bottom of the list.
  _RouteEntry? previousOldPageRouteEntry;
  while (oldEntriesBottom <= oldEntriesTop) {
    final _RouteEntry oldEntry = _history[oldEntriesBottom];
    assert(oldEntry.currentState != _RouteLifecycle.disposed);
    // 记录无页路由。最底部的无页路由将存储在 key = null 中。
    // Records pageless route. The bottom most pageless routes will be
    // stored in key = null.
    if (!oldEntry.pageBased) {
      final List<_RouteEntry> pagelessRoutes = pageRouteToPagelessRoutes.putIfAbsent(
        previousOldPageRouteEntry,
        () => <_RouteEntry>[],
      );
      pagelessRoutes.add(oldEntry);
      oldEntriesBottom += 1;
      continue;
    }
    if (newPagesBottom > newPagesTop) {
      break;
    }
    final Page<dynamic> newPage = widget.pages[newPagesBottom];
    if (!oldEntry.canUpdateFrom(newPage)) {
      break;
    }
    previousOldPageRouteEntry = oldEntry;
    oldEntry.route._updateSettings(newPage);
    newHistory.add(oldEntry);
    newPagesBottom += 1;
    oldEntriesBottom += 1;
  }

  final List<_RouteEntry> unattachedPagelessRoutes=<_RouteEntry>[];
  // 扫描列表顶部,直到找到无法更新的基于页面的路由。
  // Scans the top of the list until we found a page-based route that cannot be
  // updated.
  while ((oldEntriesBottom <= oldEntriesTop) && (newPagesBottom <= newPagesTop)) {
    final _RouteEntry oldEntry = _history[oldEntriesTop];
    assert(oldEntry.currentState != _RouteLifecycle.disposed);
    if (!oldEntry.pageBased) {
      unattachedPagelessRoutes.add(oldEntry);
      oldEntriesTop -= 1;
      continue;
    }
    final Page<dynamic> newPage = widget.pages[newPagesTop];
    if (!oldEntry.canUpdateFrom(newPage)) {
      break;
    }

    // 我们找到了下面所有连续无页路由的页面。将这些无页路由附加到页面。
    // We found the page for all the consecutive pageless routes below. Attach these
    // pageless routes to the page.
    if (unattachedPagelessRoutes.isNotEmpty) {
        pageRouteToPagelessRoutes.putIfAbsent(
        oldEntry,
        () =>  List<_RouteEntry>.from(unattachedPagelessRoutes),
      );
      unattachedPagelessRoutes.clear();
    }

    oldEntriesTop -= 1;
    newPagesTop -= 1;
  }
  // 恢复无法更新的无页路由。
  // Reverts the pageless routes that cannot be updated.
  oldEntriesTop += unattachedPagelessRoutes.length;

  // 扫描旧条目的中间并将页面密钥记录到旧条目映射。
  // Scans middle of the old entries and records the page key to old entry map.
  int oldEntriesBottomToScan = oldEntriesBottom;
  final Map<LocalKey, _RouteEntry> pageKeyToOldEntry = <LocalKey, _RouteEntry>{};
  // 该集合包含正在转出但仍在路由堆栈中的条目。
  // This set contains entries that are transitioning out but are still in
  // the route stack.
  final Set<_RouteEntry> phantomEntries = <_RouteEntry>{};
  while (oldEntriesBottomToScan <= oldEntriesTop) {
    final _RouteEntry oldEntry = _history[oldEntriesBottomToScan];
    oldEntriesBottomToScan += 1;
    assert(
      oldEntry.currentState != _RouteLifecycle.disposed,
    );
    // 当我们更新旧列表的中间部分时,将记录无页路由。
    // Pageless routes will be recorded when we update the middle of the old
    // list.
    if (!oldEntry.pageBased) {
      continue;
    }

    final Page<dynamic> page = oldEntry.route.settings as Page<dynamic>;
    if (page.key == null) {
      continue;
    }

    if (!oldEntry.willBePresent) {
      phantomEntries.add(oldEntry);
      continue;
    }
    assert(!pageKeyToOldEntry.containsKey(page.key));
    pageKeyToOldEntry[page.key!] = oldEntry;
  }

  // 更新列表的中间部分。
  // Updates the middle of the list.
  while (newPagesBottom <= newPagesTop) {
    final Page<dynamic> nextPage = widget.pages[newPagesBottom];
    newPagesBottom += 1;
    if (
      nextPage.key == null ||
      !pageKeyToOldEntry.containsKey(nextPage.key) ||
      !pageKeyToOldEntry[nextPage.key]!.canUpdateFrom(nextPage)
    ) {
      // 旧历史记录中没有匹配的键,我们需要创建一个新路由并等待转换委托决定如何将其添加到历史记录中。
      // There is no matching key in the old history, we need to create a new
      // route and wait for the transition delegate to decide how to add
      // it into the history.
      final _RouteEntry newEntry = _RouteEntry(
        nextPage.createRoute(context),
        pageBased: true,
        initialState: _RouteLifecycle.staging,
      );
      needsExplicitDecision = true;
      assert(
        newEntry.route.settings == nextPage,
        'The settings getter of a page-based Route must return a Page object. '
        'Please set the settings to the Page in the Page.createRoute method.',
      );
      newHistory.add(newEntry);
    } else {
      // 从 pageKeyToOldEntry 中删除键以指示它已被占用。
      // Removes the key from pageKeyToOldEntry to indicate it is taken.
      final _RouteEntry matchingEntry = pageKeyToOldEntry.remove(nextPage.key)!;
      assert(matchingEntry.canUpdateFrom(nextPage));
      matchingEntry.route._updateSettings(nextPage);
      newHistory.add(matchingEntry);
    }
  }

  // 任何剩余的不匹配的旧路由都需要被删除。
  // Any remaining old routes that do not have a match will need to be removed.
  final Map<RouteTransitionRecord?, RouteTransitionRecord> locationToExitingPageRoute = <RouteTransitionRecord?, RouteTransitionRecord>{};
  while (oldEntriesBottom <= oldEntriesTop) {
    final _RouteEntry potentialEntryToRemove = _history[oldEntriesBottom];
    oldEntriesBottom += 1;

    if (!potentialEntryToRemove.pageBased) {
      assert(previousOldPageRouteEntry != null);
      final List<_RouteEntry> pagelessRoutes = pageRouteToPagelessRoutes
        .putIfAbsent(
          previousOldPageRouteEntry,
          () => <_RouteEntry>[],
        );
      pagelessRoutes.add(potentialEntryToRemove);
      if (previousOldPageRouteEntry!.isWaitingForExitingDecision && potentialEntryToRemove.willBePresent) {
        potentialEntryToRemove.markNeedsExitingDecision();
      }
      continue;
    }

    final Page<dynamic> potentialPageToRemove = potentialEntryToRemove.route.settings as Page<dynamic>;
    // 如果此旧页面没有密钥、在更新新页面的中间期间未获取或已过渡出去,则需要删除转换委托的标记。
    // Marks for transition delegate to remove if this old page does not have
    // a key, was not taken during updating the middle of new page, or is
    // already transitioning out.
    if (potentialPageToRemove.key == null ||
        pageKeyToOldEntry.containsKey(potentialPageToRemove.key) ||
        phantomEntries.contains(potentialEntryToRemove)) {
      locationToExitingPageRoute[previousOldPageRouteEntry] = potentialEntryToRemove;
      // 只有在尚未弹出的情况下我们才需要做出决定。
      // We only need a decision if it has not already been popped.
      if (potentialEntryToRemove.willBePresent) {
        potentialEntryToRemove.markNeedsExitingDecision();
      }
    }
    previousOldPageRouteEntry = potentialEntryToRemove;
  }

  // 我们已经扫描了整个列表。
  // We've scanned the whole list.
  assert(oldEntriesBottom == oldEntriesTop + 1);
  assert(newPagesBottom == newPagesTop + 1);
  newPagesTop = widget.pages.length - 1;
  oldEntriesTop = _history.length - 1;
  // 验证我们是否到达底部或 oldEntriesBottom 必须可由 newPagesBottom 更新。
  // Verifies we either reach the bottom or the oldEntriesBottom must be updatable
  // by newPagesBottom.
  assert(() {
    if (oldEntriesBottom <= oldEntriesTop) {
      return newPagesBottom <= newPagesTop &&
        _history[oldEntriesBottom].pageBased &&
        _history[oldEntriesBottom].canUpdateFrom(widget.pages[newPagesBottom]);
    } else {
      return newPagesBottom > newPagesTop;
    }
  }());

  // 更新列表顶部。
  // Updates the top of the list.
  while ((oldEntriesBottom <= oldEntriesTop) && (newPagesBottom <= newPagesTop)) {
    final _RouteEntry oldEntry = _history[oldEntriesBottom];
    assert(oldEntry.currentState != _RouteLifecycle.disposed);
    if (!oldEntry.pageBased) {
      assert(previousOldPageRouteEntry != null);
      final List<_RouteEntry> pagelessRoutes = pageRouteToPagelessRoutes
        .putIfAbsent(
        previousOldPageRouteEntry,
          () => <_RouteEntry>[],
      );
      pagelessRoutes.add(oldEntry);
      continue;
    }
    previousOldPageRouteEntry = oldEntry;
    final Page<dynamic> newPage = widget.pages[newPagesBottom];
    assert(oldEntry.canUpdateFrom(newPage));
    oldEntry.route._updateSettings(newPage);
    newHistory.add(oldEntry);
    oldEntriesBottom += 1;
    newPagesBottom += 1;
  }

  // 最后,如果需要,使用转换委托做出明确的决定。
  // Finally, uses transition delegate to make explicit decision if needed.
  needsExplicitDecision = needsExplicitDecision || locationToExitingPageRoute.isNotEmpty;
  Iterable<_RouteEntry> results = newHistory;
  if (needsExplicitDecision) {
    // 执行transitionDelegate的_transition方法,因为在实战示例中,没有在Navigator传入,所以默认是DefaultTransitionDelegate
    results = widget.transitionDelegate._transition(
      // 新路由
      newPageRouteHistory: newHistory,
      // 即将退出的路由
      locationToExitingPageRoute: locationToExitingPageRoute,
      // 无页路由
      pageRouteToPagelessRoutes: pageRouteToPagelessRoutes,
    ).cast<_RouteEntry>();
  }
  _history = <_RouteEntry>[];
  // 添加主要的无页面路由(如果有)。
  // Adds the leading pageless routes if there is any.
  if (pageRouteToPagelessRoutes.containsKey(null)) {
    _history.addAll(pageRouteToPagelessRoutes[null]!);
  }
  // 添加正常的有Page的路由
  for (final _RouteEntry result in results) {
    _history.add(result);
    // 最后添加依附于当前路由result的无页路由
    if (pageRouteToPagelessRoutes.containsKey(result)) {
      _history.addAll(pageRouteToPagelessRoutes[result]!);
    }
  }
  assert(() {_debugUpdatingPage = false; return true;}());
  assert(() { _debugLocked = true; return true; }());
  // 执行_history更新
  _flushHistoryUpdates();
  assert(() { _debugLocked = false; return true; }());
}

看下TransitionDelegate_transition 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Iterable<RouteTransitionRecord> _transition({
  required List<RouteTransitionRecord> newPageRouteHistory,
  required Map<RouteTransitionRecord?, RouteTransitionRecord> locationToExitingPageRoute,
  required Map<RouteTransitionRecord?, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
}) {
  final Iterable<RouteTransitionRecord> results = resolve(
    newPageRouteHistory: newPageRouteHistory,
    locationToExitingPageRoute: locationToExitingPageRoute,
    pageRouteToPagelessRoutes: pageRouteToPagelessRoutes,
  );
  // 做出决定后验证其完整性。
  // Verifies the integrity after the decisions have been made.
  //
  // 规则如下:
  // Here are the rules:
  // - newPageRouteHistory 中的所有进入路由都必须推送或添加。
  // - All the entering routes in newPageRouteHistory must either be pushed or
  //   added.
  // - locationToExitingPageRoute 中的所有现有路由必须弹出、完成或删除。
  // - All the exiting routes in locationToExitingPageRoute must either be
  //   popped, completed or removed.
  // - 属于现有路由的所有无页路由必须弹出、完成或删除。
  // - All the pageless routes that belong to exiting routes must either be
  //   popped, completed or removed.
  // - 结果中的所有进入路由必须与 newPageRouteHistory 中的进入路由保持相同的顺序,并且结果必须包含所有退出路由。
  // - All the entering routes in the result must preserve the same order as
  //   the entering routes in newPageRouteHistory, and the result must contain
  //   all exiting routes.
  //     例如:
  //     ex:
  //
  //     newPageRouteHistory = [A, B, C]
  //
  //     locationToExitingPageRoute = {A -> D, C -> E}
  //
  //     results = [A, B ,C ,D ,E] is valid
  //     results = [D, A, B ,C ,E] is also valid because exiting route can be
  //     inserted in any place
  //
  //     results = [B, A, C ,D ,E] is invalid because B must be after A.
  //     results = [A, B, C ,E] is invalid because results must include D.

  ...
  return results;
}

TransitionDelegate_transition 方法中,执行了resolve方法,该方法由子类DefaultTransitionDelegate实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@override
Iterable<RouteTransitionRecord> resolve({
  required List<RouteTransitionRecord> newPageRouteHistory,
  required Map<RouteTransitionRecord?, RouteTransitionRecord> locationToExitingPageRoute,
  required Map<RouteTransitionRecord?, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
}) {
  final List<RouteTransitionRecord> results = <RouteTransitionRecord>[];
  // 此方法将处理该位置处的现有路由及其相应的无页路由。它还会递归地检查其上方是否有任何其他退出路由并相应地处理它们。
  // This method will handle the exiting route and its corresponding pageless
  // route at this location. It will also recursively check if there is any
  // other exiting routes above it and handle them accordingly.
  void handleExitingRoute(RouteTransitionRecord? location, bool isLast) {
    final RouteTransitionRecord? exitingPageRoute = locationToExitingPageRoute[location];
    if (exitingPageRoute == null) {
      return;
    }
    if (exitingPageRoute.isWaitingForExitingDecision) {
      final bool hasPagelessRoute = pageRouteToPagelessRoutes.containsKey(exitingPageRoute);
      final bool isLastExitingPageRoute = isLast && !locationToExitingPageRoute.containsKey(exitingPageRoute);
      if (isLastExitingPageRoute && !hasPagelessRoute) {
        // 标记路由状态为pop
        exitingPageRoute.markForPop(exitingPageRoute.route.currentResult);
      } else {
        // 标记路由状态为complete
        exitingPageRoute.markForComplete(exitingPageRoute.route.currentResult);
      }
      if (hasPagelessRoute) {
        final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute]!;
        for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) {
          // 属于现有基于页面的路由的无页面路由可能不需要退出决策。如果页面列表在 Navigator.pop 之后立即更新,则可能会发生这种情况。
          // It is possible that a pageless route that belongs to an exiting
          // page-based route does not require exiting decision. This can
          // happen if the page list is updated right after a Navigator.pop.
          if (pagelessRoute.isWaitingForExitingDecision) {
            if (isLastExitingPageRoute && pagelessRoute == pagelessRoutes.last) {
              pagelessRoute.markForPop(pagelessRoute.route.currentResult);
            } else {
              pagelessRoute.markForComplete(pagelessRoute.route.currentResult);
            }
          }
        }
      }
    }
    results.add(exitingPageRoute);

    // It is possible there is another exiting route above this exitingPageRoute.
    handleExitingRoute(exitingPageRoute, isLast);
  }

  // Handles exiting route in the beginning of list.
  handleExitingRoute(null, newPageRouteHistory.isEmpty);

  for (final RouteTransitionRecord pageRoute in newPageRouteHistory) {
    final bool isLastIteration = newPageRouteHistory.last == pageRoute;
    if (pageRoute.isWaitingForEnteringDecision) {
      if (!locationToExitingPageRoute.containsKey(pageRoute) && isLastIteration) {
        // 标记路由状态为push
        pageRoute.markForPush();
      } else {
        // 标记路由状态为add
        pageRoute.markForAdd();
      }
    }
    results.add(pageRoute);
    handleExitingRoute(pageRoute, isLastIteration);
  }
  return results;
}

5.5、pushNamed执行逻辑

在本实战示例中,执行NavigatorHelperpushNamed方法后,就会触发在CustomRouterDelegate构造方法中注册的registerOnJumpListener回调方法。

在该回调中执行了 _updatePages 方法,那么就会更新pages,同时内部也调用了notifyListeners方法。

前面讲过,routerDelegate添加了监听回调方法 _handleRouterDelegateNotification,此时就会执行该方法。

首先执行setState方法更新UI,最后,又执行了routeInformationParserrestoreRouteInformation方法。

5.6、pop执行逻辑

当点击标题栏中返回按钮时,就会触发CustomRouterDelegate_onPopPage 方法,那么又会触发 _updatePages 方法,之后的逻辑与前面一致。

六、参考文献