-
概述
get的官方文档上介绍说,它具有更快和更实际的路由管理,至于性能上是不是如他所说我暂时没做比较,本文从初始化的路由逻辑和部分其他跳转逻辑的代码上来看一下和Flutter原生跳转有何不同,确切地说是做了何种封装。
-
Flutter原生初始路由获取
在Flutter原生中,初始化路由主要通过在MaterialApp中指定的initialRoute和routes属性来设置初始页面,当然也可以设置home属性。
在_WidgetsAppState的build方法中有这么一段:
Widget? routing; if (_usesRouter) { assert(_effectiveRouteInformationProvider != null); routing = Router<Object>( routeInformationProvider: _effectiveRouteInformationProvider, routeInformationParser: widget.routeInformationParser, routerDelegate: widget.routerDelegate!, backButtonDispatcher: widget.backButtonDispatcher, ); } else if (_usesNavigator) { assert(_navigator != null); routing = Navigator( 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, ); }
可见,首先会判断_usesRouter来决定routing,那他是什么呢?
bool get _usesRouter => widget.routerDelegate != null; bool get _usesNavigator => widget.home != null || widget.routes?.isNotEmpty == true || widget.onGenerateRoute != null || widget.onUnknownRoute != null;
可以看到,首先会先采用routerDelegate来创建route,其次会根据home、routes、onGenerateRoute或onUnknownRoute来生成route。
以Navigator为例,它的initialRoute属性通过 _initialRouteName来赋值, _initialRouteName产生逻辑如下:
String get _initialRouteName => WidgetsBinding.instance!.window.defaultRouteName != Navigator.defaultRouteName ? WidgetsBinding.instance!.window.defaultRouteName : widget.initialRoute ?? WidgetsBinding.instance!.window.defaultRouteName;
WidgetsBinding.instance!.window.defaultRouteName是Android原生API传过来的initialRoute,这个会首先被尝试取用,如果没设置则取MaterialApp设置的initialRoute属性的值。
initialRoute流程中也会走到_onGenerateRoute方法:
Route<dynamic>? _onGenerateRoute(RouteSettings settings) { final String? name = settings.name; final WidgetBuilder? pageContentBuilder = name == Navigator.defaultRouteName && widget.home != null ? (BuildContext context) => widget.home! : widget.routes![name]; if (pageContentBuilder != null) { assert( widget.pageRouteBuilder != null, 'The default onGenerateRoute handler for WidgetsApp must have a ' 'pageRouteBuilder set if the home or routes properties are set.', ); final Route<dynamic> route = widget.pageRouteBuilder!<dynamic>( settings, pageContentBuilder, ); assert(route != null, 'The pageRouteBuilder for WidgetsApp must return a valid non-null Route.'); return route; } if (widget.onGenerateRoute != null) return widget.onGenerateRoute!(settings); return null; }
可见,在这里会决定是直接用home还是去routes中查找,总结一下就是,如果initialRoute没有设置并且设置了home的情况下会使用home,否则就尝试去routes中取。
-
get 框架初始路由的获取
和原生不同的是,get框架用的是getPages属性,原生的routes属性是一个map,而getPages属性是一个list。
GetMaterialApp的build方法中有一句:
if (getPages != null) { Get.addPages(getPages!); }
Get.addPages方法如下:
void addPages(List<GetPage> getPages) { routeTree.addRoutes(getPages); }
好了下面我们来看使用。
NavigatorState的build方法如下:
@override Widget build(BuildContext context) { assert(!_debugLocked); assert(_history.isNotEmpty); // Hides the HeroControllerScope for the widget subtree so that the other // nested navigator underneath will not pick up the hero controller above // this level. return HeroControllerScope.none( child: Listener( onPointerDown: _handlePointerDown, onPointerUp: _handlePointerUpOrCancel, onPointerCancel: _handlePointerUpOrCancel, child: AbsorbPointer( absorbing: false, // it's mutated directly by _cancelActivePointers above child: FocusScope( node: focusScopeNode, autofocus: true, child: UnmanagedRestorationScope( bucket: bucket, child: Overlay( key: _overlayKey, initialEntries: overlay == null ? _allRouteOverlayEntries.toList(growable: false) : const <OverlayEntry>[], ), ), ), ), ), ); }
可以看到,最内层的child是Overlay,它的State的build方法如下:
@override Widget build(BuildContext context) { // This list is filled backwards and then reversed below before // it is added to the tree. final List<Widget> children = <Widget>[]; bool onstage = true; int onstageCount = 0; for (int i = _entries.length - 1; i >= 0; i -= 1) { final OverlayEntry entry = _entries[i]; if (onstage) { onstageCount += 1; children.add(_OverlayEntryWidget( key: entry._key, entry: entry, )); if (entry.opaque) onstage = false; } else if (entry.maintainState) { children.add(_OverlayEntryWidget( key: entry._key, entry: entry, tickerEnabled: false, )); } } return _Theatre( skipCount: children.length - onstageCount, children: children.reversed.toList(growable: false), clipBehavior: widget.clipBehavior, ); }
这里是含有多个child,每一个都是_OverlayEntryWidget,它的State的build 方法如下:
@override Widget build(BuildContext context) { return TickerMode( enabled: widget.tickerEnabled, child: widget.entry.builder(context), ); }
可以看到,这里最终调用了entry的builder函数来创建Widget,那这个entry从哪来的呢?它来自前面NavigatorState的build方法中传入的initialEntries属性的值,_allRouteOverlayEntries根据 _history生成:
Iterable<OverlayEntry> get _allRouteOverlayEntries sync* { for (final _RouteEntry entry in _history) yield* entry.route.overlayEntries; }
而_history又是在NavigatorState的restoreState方法(这个方法至少会在initState之后调用一次)中添加的initialRoute:
if (initialRoute != null) { _history.addAll( widget.onGenerateInitialRoutes( this, widget.initialRoute ?? Navigator.defaultRouteName, ).map((Route<dynamic> route) => _RouteEntry( route, initialState: _RouteLifecycle.add, restorationInformation: route.settings.name != null ? _RestorationInformation.named( name: route.settings.name!, arguments: null, restorationScopeId: _nextPagelessRestorationScopeId, ) : null, ), ), ); }
可见,entry就是这里的_RouteEntry,entry.route就是这里的route,它是由widget.onGenerateInitialRoutes创建的,回到Navigator的构造处,widget.onGenerateInitialRoutes就是:
(NavigatorState navigator, String initialRouteName) { return widget.onGenerateInitialRoutes!(initialRouteName); }
这里其实又会调用Navigator的widget的onGenerateInitialRoutes函数,回到GetMaterialApp的build方法中,返回的MaterialApp构造对象的onGenerateInitialRoutes属性为:
onGenerateInitialRoutes: (getPages == null || home != null) ? onGenerateInitialRoutes : initialRoutesGenerate,
因为此处getPages不为null且没有配置home属性,所以用的是initialRoutesGenerate函数:
List<Route<dynamic>> initialRoutesGenerate(String name) { return [ PageRedirect( settings: RouteSettings(name: name), unknownRoute: unknownRoute, ).page() ]; }
所以上面的overlayEntries来自于这里产生的Route,经查发现,overlayEntries定义于OverlayRoute中,它们的继承关系是GetPageRoute->PageRoute->ModalRoute->TransitionRoute->OverlayRoute->Route。overlayEntries返回的就是_overlayEntries的值, _overlayEntries会在install方法中添加内容,install方法在Route插入到Navigator时会被调用:
@factory Iterable<OverlayEntry> createOverlayEntries(); @override List<OverlayEntry> get overlayEntries => _overlayEntries; final List<OverlayEntry> _overlayEntries = <OverlayEntry>[]; @override void install() { assert(_overlayEntries.isEmpty); _overlayEntries.addAll(createOverlayEntries()); super.install(); }
可见,overlayEntries的内容来自于createOverlayEntries方法,这个方法交给子类重写,根据继承关系我们在ModalRoute中找到了该方法的实现:
@override Iterable<OverlayEntry> createOverlayEntries() sync* { yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier); yield _modalScope = OverlayEntry(builder: _buildModalScope, maintainState: maintainState); }
这里会使用生成器来创建OverlayEntry,为什么一次生成两个呢?_modalBarrier其实是页面之间的隔断,相当于分割线的角色。
所以,_OverlayEntryWidgetState的build中entry的builder函数就是这里的 _buildModalBarrier和 _buildModalScope, _buildModalScope如下:
Widget _buildModalScope(BuildContext context) { // To be sorted before the _modalBarrier. return _modalScopeCache ??= Semantics( sortKey: const OrdinalSortKey(0.0), child: _ModalScope<T>( key: _scopeKey, route: this, // _ModalScope calls buildTransitions() and buildChild(), defined above ), ); }
在_ModalScope的State的build中的内层child是:
child: _page ??= RepaintBoundary( key: widget.route._subtreeKey, // immutable child: Builder( builder: (BuildContext context) { return widget.route.buildPage( context, widget.route.animation!, widget.route.secondaryAnimation!, ); }, ), ),
widget.route是前面传入的‘this’,也就是ModalRoute本身,在GetPageRoute继承自ModalRoute的子类链中我们并没有发现有实现这个方法,我们最终在GetPageRouteTransitionMixin中找到了实现:
mixin GetPageRouteTransitionMixin<T> on PageRoute<T>{ @protected Widget buildContent(BuildContext context); @override Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { final child = buildContent(context); final Widget result = Semantics( scopesRoute: true, explicitChildNodes: true, child: child, ); return result; } }
buildPage返回了Semantics,它的child是通过buildContent创建的,这个方法在GetPageRoute中实现:
@override Widget buildContent(BuildContext context) { return _getChild(); }
_getChild方法如下:
Widget _getChild() { if (_child != null) return _child!; final middlewareRunner = MiddlewareRunner(middlewares); final localbindings = [ if (bindings != null) ...bindings!, if (binding != null) ...[binding!] ]; final bindingsToBind = middlewareRunner.runOnBindingsStart(localbindings); if (bindingsToBind != null) { for (final binding in bindingsToBind) { binding.dependencies(); } } final pageToBuild = middlewareRunner.runOnPageBuildStart(page)!; _child = middlewareRunner.runOnPageBuilt(pageToBuild()); return _child!; }
这个地方_child的构造和很多尚未确定的变量有关,所以这里有些麻烦,一步一步来找。
_child通过middlewareRunner.runOnPageBuilt产生,runOnPageBuilt方法的参数是一个函数,通过middlewareRunner.runOnPageBuildStart产生,runOnPageBuildStart方法和runOnPageBuilt方法内部逻辑都是会尝试调用 _getMiddlewares方法循环每一个中间件,最后采用最后一个中间件(这个东西有什么用现在还不知道)的对应方法处理过的参数:
GetPageBuilder? runOnPageBuildStart(GetPageBuilder? page) { _getMiddlewares().forEach((element) { page = element.onPageBuildStart(page); }); return page; } Widget runOnPageBuilt(Widget page) { _getMiddlewares().forEach((element) { page = element.onPageBuilt(page); }); return page; }
那么_getMiddlewares方法获取的是什么呢?是 _middlewares,他在构造时传入,也就是前面 _getChild方法传入的middlewares,它又是通过GetPageRoute构造时传入的,也就是在PageRedirect的page方法中,middlewares的值是 _r.middlewares, _r是:
final _r = (isUnknown ? unknownRoute : route)!;
这里的isUnknown是false,所以_r就是 route,route是什么时候构造的呢?在page方法的最开始有一句:
while (needRecheck()) {}
needRecheck方法如下:
bool needRecheck() { if (settings == null && route != null) { settings = route; } final match = Get.routeTree.matchRoute(settings!.name!); Get.parameters = match.parameters; // No Match found if (match.route == null) { isUnknown = true; return false; } final runner = MiddlewareRunner(match.route!.middlewares); route = runner.runOnPageCalled(match.route); addPageParameter(route!); // No middlewares found return match. if (match.route!.middlewares == null || match.route!.middlewares!.isEmpty) { return false; } final newSettings = runner.runRedirect(settings!.name); if (newSettings == null) { return false; } settings = newSettings; return true; }
还记得我们之前把getPages属性的所有GetPage都存在了Get.routeTree吗,这里就是去那里找到initialRoute指定的初始路由页面,所以middlewares是在getPages路由集合里构造每一个GetPage时自定义设置的,所以到这里我们也就知道了middlewares的作用,就是在构造page之前提供一个额外的处理入口,通过它你可以做一些自定义的处理工作。
所以_getChild 方法中的page就是GetPage中page属性指向的值,也就是我们的页面,比如:
GetPage unknowPage = GetPage( name: "myPage", page: () => MyPage(), );
如上,MyPage是我们自定义的页面组件,在这个流程中最终会最为最内层的组件显示在最上面。
-
get 框架的部分其他路由方法
以Get.to方法为例:
Future<T?>? to<T>( dynamic page, { bool? opaque, Transition? transition, Curve? curve, Duration? duration, int? id, String? routeName, bool fullscreenDialog = false, dynamic arguments, Bindings? binding, bool preventDuplicates = true, bool? popGesture, double Function(BuildContext context)? gestureWidth, }) { // var routeName = "/${page.runtimeType}"; routeName ??= "/${page.runtimeType}"; routeName = _cleanRouteName(routeName); if (preventDuplicates && routeName == currentRoute) { return null; } return global(id).currentState?.push<T>( GetPageRoute<T>( opaque: opaque ?? true, page: _resolvePage(page, 'to'), routeName: routeName, gestureWidth: gestureWidth, settings: RouteSettings( name: routeName, arguments: arguments, ), popGesture: popGesture ?? defaultPopGesture, transition: transition ?? defaultTransition, curve: curve ?? defaultTransitionCurve, fullscreenDialog: fullscreenDialog, binding: binding, transitionDuration: duration ?? defaultTransitionDuration, ), ); }
page作为唯一必传参数,表示要跳转的页面组件,global(id)表示每个id都有一个公有的GlobalKey,可以理解成每个id都有一个单例。因为global(id)返回的是一个GlobalKey<NavigatorState>,所以currentState是NavigatorState,可见,最后也是调用原生的push方法进行路由调度,只不过路由换成了GetPageRoute。
其他路由方法原理是一样的,都是对于原生的进一步封装。
-
获取传参
Get的toNamed方法可以通过arguments属性传递给下一个页面参数,那么怎么获取呢?答案是通过Get.parameters获取,拿到的是一个Map,然后根据自己的key去取对应的值。
Get.parameters中可以拿到两种方式的值,一种是通过toNamed方法传递的page属性指定的url来传参,另一种是通过toNamed方法的另一个形参arguments指定一个Map参数集合。
在上面的needRecheck方法中有一句Get.parameters = match.parameters,我们看一下matchRoute方法:
RouteDecoder matchRoute(String name, {Object? arguments}) { final uri = Uri.parse(name); // /home/profile/123 => home,profile,123 => /,/home,/home/profile,/home/profile/123 final split = uri.path.split('/').where((element) => element.isNotEmpty); var curPath = '/'; final cumulativePaths = <String>[ '/', ]; for (var item in split) { if (curPath.endsWith('/')) { curPath += '$item'; } else { curPath += '/$item'; } cumulativePaths.add(curPath); } //_findRoute会从之前GetMaterialApp构造中通过getPages属性添加的路由集合中找出所有符合的路由 final treeBranch = cumulativePaths .map((e) => MapEntry(e, _findRoute(e))) .where((element) => element.value != null) .map((e) => MapEntry(e.key, e.value!)) .toList(); final params = Map<String, String>.from(uri.queryParameters); if (treeBranch.isNotEmpty) { //解析出路由url中携带的参数 final lastRoute = treeBranch.last; final parsedParams = _parseParams(name, lastRoute.value.path); if (parsedParams.isNotEmpty) { params.addAll(parsedParams); } //找出每一个路由的arguments的值,把它们赋给MapEntry,此步过后,MapEntry的parameters中会同时持有url中携带的参数和路由中arguments指定的参数 final mappedTreeBranch = treeBranch .map( (e) => e.value.copy( parameters: { if (e.value.parameters != null) ...e.value.parameters!, ...params, }, name: e.key, ), ) .toList(); return RouteDecoder( mappedTreeBranch, params, arguments, ); } //route not found return RouteDecoder( treeBranch.map((e) => e.value).toList(), params, arguments, ); }
里面有一句final params = Map<String, String>.from(uri.queryParameters),uri.queryParameters就是路由地址中“?”后面的部分,_parseParams方法会把这部分按照url带参标准转成一个参数Map,最终这个params会传给返回的RouteDecoder的parameters属性,RouteDecoder也就是上面的match。
现在我们知道Get.parameters中已经有路由url本身携带的参数了,再来看toNamed方法传递的参数是怎么保存到Get.parameters的。
needRecheck方法中往下看,调用了一个addPageParameter方法:
void addPageParameter(GetPage route) { if (route.parameters == null) return; final parameters = Get.parameters; parameters.addEntries(route.parameters!.entries); Get.parameters = parameters; }
在这个方法中可以看到在这里会把route中的arguments放入Get.parameters中,而route在matchRoute方法中早已把toNamed传递的argumetns保存了。
-
总结
现在我们知道,Get框架的路由调用内部也是使用了原生的Navigator,只不过多了一些为了简化使用的封装处理,比如,传递参数的获取是通过Get.parameters统一保存url中的参数和方法传递的参数。
Flutter框架《get》的路由管理解析
?著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
推荐阅读更多精彩内容
- 概述我们知道,在配置FlutterEngine的时候,有两种方式可以设置初始路由,这个初始路由就是flutter第...
- 没有注释的代码不是好代码 没有demo的博客不是好博客 本博客代码请移步github 什么是路由管理 Flutte...
- 概述 路由管理 路由基本使用 命名路由使用(重点) 页面跳转的拓展 一、路由管理 1.1、认识Flutter路由路...
- 邂逅FLutter 万物皆是Widget 一般缩进2个空格 文字居中 Widget Center() Materi...
- 我们通?;嵊闷粒⊿creen)来称呼一个页面(Page),一个完整的App应该是有多个Page组成的。 在之前的案...