美文网首页
Flutter框架《get》的路由管理解析

Flutter框架《get》的路由管理解析

作者: 就叫汉堡吧 | 来源:发表于2022-03-09 17:16 被阅读0次
    • 概述

      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&#39;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 &lt;OverlayEntry&gt;[],
                  ),
                ),
              ),
            ),
          ),
        );
      }
      

      可以看到,最内层的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》的路由管理解析

          本文链接:https://www.haomeiwen.com/subject/kxmlrrtx.html