Flutter-initialRoute原理

  • 概述

    我们知道,在配置FlutterEngine的时候,有两种方式可以设置初始路由,这个初始路由就是flutter第一个会打开的页面,通常有两种方式设置,一种是通过flutterEngine.navigationChannel.setInitialRoute("/"),另一种是通过构造FlutterActivity的Intent设置:FlutterActivity.withNewEngine().initialRoute("myHome")。

    下面我们看看这个初始路由是如何配置到flutter的。

  • 二就是一

    其实第二种方式最终也是通过NavigationChannel来设置的,FlutterActivity.withEngine方法实际上是返回了一个内部类对象NewEngineIntentBuilder:

    @NonNull
    public static NewEngineIntentBuilder withNewEngine() {
      return new NewEngineIntentBuilder(FlutterActivity.class);
    }
    

    然后调用它的initialRoute方法持有初始路由地址:

    @NonNull
    public NewEngineIntentBuilder initialRoute(@NonNull String initialRoute) {
      this.initialRoute = initialRoute;
      return this;
    }
    

    最后调用build方法构造Intent:

    @NonNull
    public Intent build(@NonNull Context context) {
      return new Intent(context, activityClass)
          .putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
          .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
          .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
    }
    

    可以看到,初始路由被保存在Intent中,EXTRA_INITIAL_ROUTE是“route”。

    然后在FlutterActivity的onStart方法中调用了FlutterActivityAndFragmentDelegate的onStart方法:

    @Override
    protected void onStart() {
      super.onStart();
      lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
      if (stillAttachedForEvent("onStart")) {
        delegate.onStart();
      }
    }
    

    FlutterActivityAndFragmentDelegate是业务代理类,在onCreate的时候初始化的。看一下它的onStart方法:

    void onStart() {
      Log.v(TAG, "onStart()");
      ensureAlive();
      doInitialFlutterViewRun();
    }
    

    这里调用了一个doInitialFlutterViewRun方法:

    private void doInitialFlutterViewRun() {
      // Don't attempt to start a FlutterEngine if we're using a cached FlutterEngine.
      if (host.getCachedEngineId() != null) {
        return;
      }
    
      if (flutterEngine.getDartExecutor().isExecutingDart()) {
        // No warning is logged because this situation will happen on every config
        // change if the developer does not choose to retain the Fragment instance.
        // So this is expected behavior in many cases.
        return;
      }
      String initialRoute = host.getInitialRoute();
      if (initialRoute == null) {
        initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent());
        if (initialRoute == null) {
          initialRoute = DEFAULT_INITIAL_ROUTE;
        }
      }
      Log.v(
          TAG,
          "Executing Dart entrypoint: "
              + host.getDartEntrypointFunctionName()
              + ", and sending initial route: "
              + initialRoute);
    
      // The engine needs to receive the Flutter app's initial route before executing any
      // Dart code to ensure that the initial route arrives in time to be applied.
      flutterEngine.getNavigationChannel().setInitialRoute(initialRoute);
    
      String appBundlePathOverride = host.getAppBundlePath();
      if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
        appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
      }
    
      // Configure the Dart entrypoint and execute it.
      DartExecutor.DartEntrypoint entrypoint =
          new DartExecutor.DartEntrypoint(
              appBundlePathOverride, host.getDartEntrypointFunctionName());
      flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
    }
    

    在这里我们看到了熟悉的flutterEngine.getNavigationChannel().setInitialRoute(initialRoute),所以两种方式其实都是通过NavigationChannel来设置的。我们还知道默认的路由是DEFAULT_INITIAL_ROUTE,也就是“/”。

  • NavigationChannel

    NavigationChannel是在FlutterEngine构造时创建的,构造方法如下:

    public NavigationChannel(@NonNull DartExecutor dartExecutor) {
      this.channel = new MethodChannel(dartExecutor, "flutter/navigation", JSONMethodCodec.INSTANCE);
    }
    

    可见它内部持有了一个MethodChannel,MethodChannel内部又持有了DartExecutor。

    setInitialRoute方法是:

    public void setInitialRoute(@NonNull String initialRoute) {
      Log.v(TAG, "Sending message to set initial route to '" + initialRoute + "'");
      channel.invokeMethod("setInitialRoute", initialRoute);
    }
    

    invokeMethod方法最终执行:

    @UiThread
    public void invokeMethod(String method, @Nullable Object arguments, @Nullable Result callback) {
      messenger.send(
          name,
          codec.encodeMethodCall(new MethodCall(method, arguments)),
          callback == null ? null : new IncomingResultHandler(callback));
    }
    

    这里的callback是回调,这里为null先不管,method参数就是“setInitialRoute”,arguments就是初始路由地址。回头来看MethodChannel类,它内部有三个变量:

    private final BinaryMessenger messenger;
    private final String name;
    private final MethodCodec codec;
    

    根据NavigationChannel的构造,这里分别是DartExecutor、“flutter/navigation”和JSONMethodCodec.INSTANCE,JSONMethodCodec.INSTANCE是JSONMethodCodec实例,它的encodeMethodCall方法如下:

    @Override
    public ByteBuffer encodeMethodCall(MethodCall methodCall) {
      try {
        final JSONObject map = new JSONObject();
        map.put("method", methodCall.method);
        map.put("args", JSONUtil.wrap(methodCall.arguments));
        return JSONMessageCodec.INSTANCE.encodeMessage(map);
      } catch (JSONException e) {
        throw new IllegalArgumentException("Invalid JSON", e);
      }
    }
    

    后面的代码就不贴了,就是转成{"method":"setInitialRoute","args":"路由地址"}这样的json字符串,然后转换成ByteBuffer。

    接下来来看DartExecutor。

  • DartExecutor

    DartExecutor的send方法如下:

    @Deprecated
    @Override
    @UiThread
    public void send(
        @NonNull String channel,
        @Nullable ByteBuffer message,
        @Nullable BinaryMessenger.BinaryReply callback) {
      binaryMessenger.send(channel, message, callback);
    }
    

    可以看到,它内部有一个binaryMessenger,binaryMessenger在构造时生成:

    this.binaryMessenger = new DefaultBinaryMessenger(dartMessenger);
    

    所以send方法也就是DefaultBinaryMessenger的send方法:

    @Override
    @UiThread
    public void send(
        @NonNull String channel,
        @Nullable ByteBuffer message,
        @Nullable BinaryMessenger.BinaryReply callback) {
      messenger.send(channel, message, callback);
    }
    

    这个messenger是构造时传入的dartMessenger,dartMessenger是在构造DartExecutor的时候生成的:

    this.dartMessenger = new DartMessenger(flutterJNI);
    dartMessenger.setMessageHandler("flutter/isolate", isolateChannelMessageHandler);
    

    DartMessenger中的send方法如下:

    @Override
    public void send(
        @NonNull String channel,
        @Nullable ByteBuffer message,
        @Nullable BinaryMessenger.BinaryReply callback) {
      Log.v(TAG, "Sending message with callback over channel '" + channel + "'");
      int replyId = 0;
      if (callback != null) {
        replyId = nextReplyId++;
        pendingReplies.put(replyId, callback);
      }
      if (message == null) {
        flutterJNI.dispatchEmptyPlatformMessage(channel, replyId);
      } else {
        flutterJNI.dispatchPlatformMessage(channel, message, message.position(), replyId);
      }
    }
    

    最终调用JNI:

    bool Engine::HandleNavigationPlatformMessage(
        fml::RefPtr<blink::PlatformMessage> message) {
      const auto& data = message->data();
    
      rapidjson::Document document;
      document.Parse(reinterpret_cast<const char*>(data.data()), data.size());
      if (document.HasParseError() || !document.IsObject())
        return false;
      auto root = document.GetObject();
      auto method = root.FindMember("method");
      if (method->value != "setInitialRoute")
        return false;
      auto route = root.FindMember("args");
      initial_route_ = std::move(route->value.GetString());
      return true;
    }
    

然后我们看从dart的使用入手,是如何使用initialRoute的。

  • MaterialApp

    从MaterialApp开始:

    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          // home: MyHomePage(title: 'Flutter Demo Home Page'),
          initialRoute: "myHome",
          routes: {
            "myHome": (context) => MyHomePage(
                  title: "MyHome",
                ),
          },
        );
      }
    }
    

    构造MaterialApp时传入initialRoute。MaterialApp是一个StatefulWidget,所以它的State-_MaterialAppState的build如下:

    @override
    Widget build(BuildContext context) {
      Widget result = _buildWidgetApp(context);
    
      assert(() {
        if (widget.debugShowMaterialGrid) {
          result = GridPaper(
            color: const Color(0xE0F9BBE0),
            interval: 8.0,
            divisions: 2,
            subdivisions: 1,
            child: result,
          );
        }
        return true;
      }());
    
      return ScrollConfiguration(
        behavior: widget.scrollBehavior ?? const MaterialScrollBehavior(),
        child: HeroControllerScope(
          controller: _heroController,
          child: result,
        ),
      );
    }
    

    _buildWidgetApp方法如下:

    Widget _buildWidgetApp(BuildContext context) {
      // The color property is always pulled from the light theme, even if dark
      // mode is activated. This was done to simplify the technical details
      // of switching themes and it was deemed acceptable because this color
      // property is only used on old Android OSes to color the app bar in
      // Android's switcher UI.
      //
      // blue is the primary color of the default theme.
      final Color materialColor = widget.color ?? widget.theme?.primaryColor ?? Colors.blue;
      if (_usesRouter) {
        return WidgetsApp.router(
          key: GlobalObjectKey(this),
          routeInformationProvider: widget.routeInformationProvider,
          routeInformationParser: widget.routeInformationParser!,
          routerDelegate: widget.routerDelegate!,
          backButtonDispatcher: widget.backButtonDispatcher,
          builder: _materialBuilder,
          title: widget.title,
          onGenerateTitle: widget.onGenerateTitle,
          textStyle: _errorTextStyle,
          color: materialColor,
          locale: widget.locale,
          localizationsDelegates: _localizationsDelegates,
          localeResolutionCallback: widget.localeResolutionCallback,
          localeListResolutionCallback: widget.localeListResolutionCallback,
          supportedLocales: widget.supportedLocales,
          showPerformanceOverlay: widget.showPerformanceOverlay,
          checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
          checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
          showSemanticsDebugger: widget.showSemanticsDebugger,
          debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
          inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
          shortcuts: widget.shortcuts,
          actions: widget.actions,
          restorationScopeId: widget.restorationScopeId,
        );
      }
    
      return WidgetsApp(
        key: GlobalObjectKey(this),
        navigatorKey: widget.navigatorKey,
        navigatorObservers: widget.navigatorObservers!,
        pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
          return MaterialPageRoute<T>(settings: settings, builder: builder);
        },
        home: widget.home,
        routes: widget.routes!,
        initialRoute: widget.initialRoute,
        onGenerateRoute: widget.onGenerateRoute,
        onGenerateInitialRoutes: widget.onGenerateInitialRoutes,
        onUnknownRoute: widget.onUnknownRoute,
        builder: _materialBuilder,
        title: widget.title,
        onGenerateTitle: widget.onGenerateTitle,
        textStyle: _errorTextStyle,
        color: materialColor,
        locale: widget.locale,
        localizationsDelegates: _localizationsDelegates,
        localeResolutionCallback: widget.localeResolutionCallback,
        localeListResolutionCallback: widget.localeListResolutionCallback,
        supportedLocales: widget.supportedLocales,
        showPerformanceOverlay: widget.showPerformanceOverlay,
        checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
        checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
        showSemanticsDebugger: widget.showSemanticsDebugger,
        debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
        inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
        shortcuts: widget.shortcuts,
        actions: widget.actions,
        restorationScopeId: widget.restorationScopeId,
      );
    }
    

    _usesRouter为:

    bool get _usesRouter => widget.routerDelegate != null;
    

    这里为false,所以build返回的是WidgetsApp()构造。

    同理,_WidgetsAppState里面:

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

    注意这里,首先会尝试使用defaultRouteName,这个值就是原生通过FlutterEngine的NavigationChannel的setInitialRoute设置的,这个值优先级最高;如果没有设置过这个值则使用MaterialApp中initialRoute属性指定的值。最终会赋给_initialRouteName,build方法里返回:

    @override
    Widget build(BuildContext context) {
      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,
        );
      }
    
      Widget result;
      if (widget.builder != null) {
        result = Builder(
          builder: (BuildContext context) {
            return widget.builder!(context, routing);
          },
        );
      }
      ... 
      ...
    }
    

    这里只截取到此处的函数返回处,widget.builder是前面传来的_materialBuilder:

    Widget _materialBuilder(BuildContext context, Widget? child) {
      // Resolve which theme to use based on brightness and high contrast.
      final ThemeMode mode = widget.themeMode ?? ThemeMode.system;
      final Brightness platformBrightness = MediaQuery.platformBrightnessOf(context);
      final bool useDarkTheme = mode == ThemeMode.dark
        || (mode == ThemeMode.system && platformBrightness == ui.Brightness.dark);
      final bool highContrast = MediaQuery.highContrastOf(context);
      ThemeData? theme;
    
      if (useDarkTheme && highContrast && widget.highContrastDarkTheme != null) {
        theme = widget.highContrastDarkTheme;
      } else if (useDarkTheme && widget.darkTheme != null) {
        theme = widget.darkTheme;
      } else if (highContrast && widget.highContrastTheme != null) {
        theme = widget.highContrastTheme;
      }
      theme ??= widget.theme ?? ThemeData.light();
    
      return ScaffoldMessenger(
        key: widget.scaffoldMessengerKey,
        child: AnimatedTheme(
          data: theme,
          child: widget.builder != null
            ? Builder(
                builder: (BuildContext context) {
                  return widget.builder!(context, child);
                },
              )
            : child!,
        ),
      );
    }
    

    而这里的widget.builder是null,所以直接返回child,即上面的Navigator。

  • Navigator

    Navigator是一个路由管理的组件,它提供了打开和退出路由页方法。Navigator通过一个栈来管理活动路由集合。当前屏幕显示的页面就是栈顶的路由。

    NavigatorState的中重写了restoreState方法:

    This method is always invoked at least once right after [State.initState]
    to register the [RestorableProperty]s with the mixin even when state
    restoration is turned off or no restoration data is available for this
    [State] object.
    

    可见,这个方法总是在initState方法之后至少执行一次:

    @override
    void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
      registerForRestoration(_rawNextPagelessRestorationScopeId, 'id');
      registerForRestoration(_serializableHistory, 'history');
    
      // Delete everything in the old history and clear the overlay.
      while (_history.isNotEmpty) {
        _history.removeLast().dispose();
      }
      assert(_history.isEmpty);
      _overlayKey = GlobalKey<OverlayState>();
    
      // Populate the new history from restoration data.
      _history.addAll(_serializableHistory.restoreEntriesForPage(null, this));
      for (final Page<dynamic> page in widget.pages) {
        final _RouteEntry entry = _RouteEntry(
          page.createRoute(context),
          initialState: _RouteLifecycle.add,
        );
        assert(
          entry.route.settings == page,
          '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.',
        );
        _history.add(entry);
        _history.addAll(_serializableHistory.restoreEntriesForPage(entry, this));
      }
    
      // If there was nothing to restore, we need to process the initial route.
      if (!_serializableHistory.hasData) {
        String? initialRoute = widget.initialRoute;
        if (widget.pages.isEmpty) {
          initialRoute = initialRoute ?? Navigator.defaultRouteName;
        }
        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,
              ),
            ),
          );
        }
      }
    }
    

    这里有个_history字段,它盛放的就是所有的页面路由,首先会把之前的页面重新恢复,这里不是我们考虑的因素,我们只需要知道初始路由要放在最上层,即第一个需要展示的页面,在代码的最后,如果是第一次显示则尝试去显示初始路由页面。

    widget.onGenerateInitialRoutes是前面传来的:

    onGenerateInitialRoutes: widget.onGenerateInitialRoutes == null
      ? Navigator.defaultGenerateInitialRoutes
      : (NavigatorState navigator, String initialRouteName) {
        return widget.onGenerateInitialRoutes!(initialRouteName);
      }
    

    这里是Navigator.defaultGenerateInitialRoutes:

    static List<Route<dynamic>> defaultGenerateInitialRoutes(NavigatorState navigator, String initialRouteName) {
      final List<Route<dynamic>?> result = <Route<dynamic>?>[];
      if (initialRouteName.startsWith('/') && initialRouteName.length > 1) {
        initialRouteName = initialRouteName.substring(1); // strip leading '/'
        assert(Navigator.defaultRouteName == '/');
        List<String>? debugRouteNames;
        assert(() {
          debugRouteNames = <String>[ Navigator.defaultRouteName ];
          return true;
        }());
        result.add(navigator._routeNamed<dynamic>(Navigator.defaultRouteName, arguments: null, allowNull: true));
        final List<String> routeParts = initialRouteName.split('/');
        if (initialRouteName.isNotEmpty) {
          String routeName = '';
          for (final String part in routeParts) {
            routeName += '/$part';
            assert(() {
              debugRouteNames!.add(routeName);
              return true;
            }());
            result.add(navigator._routeNamed<dynamic>(routeName, arguments: null, allowNull: true));
          }
        }
        if (result.last == null) {
          assert(() {
            FlutterError.reportError(
              FlutterErrorDetails(
                exception:
                  'Could not navigate to initial route.\n'
                  'The requested route name was: "/$initialRouteName"\n'
                  'There was no corresponding route in the app, and therefore the initial route specified will be '
                  'ignored and "${Navigator.defaultRouteName}" will be used instead.',
              ),
            );
            return true;
          }());
          result.clear();
        }
      } else if (initialRouteName != Navigator.defaultRouteName) {
        // If initialRouteName wasn't '/', then we try to get it with allowNull:true, so that if that fails,
        // we fall back to '/' (without allowNull:true, see below).
        result.add(navigator._routeNamed<dynamic>(initialRouteName, arguments: null, allowNull: true));
      }
      result.removeWhere((Route<dynamic>? route) => route == null);
      if (result.isEmpty)
        result.add(navigator._routeNamed<dynamic>(Navigator.defaultRouteName, arguments: null));
      return result.cast<Route<dynamic>>();
    }
    

    梳理一下这个方法的逻辑:

    1. 自定义路由需要以‘/’开头;
    2. 取‘/’之后的字符串;
    3. 把这个字符串按照‘/’分割成一个集合;
    4. 把所有的子路由都添加到result这个集合中,比如“/route/my/one”会分成三个路由分别是“route”、“route/my”和“route/my/one”;
    5. 最后去除null的路由。

    null路由是在哪产生的呢?每一个路由都是由navigator._routeNamed产生的:

    Route<T>? _routeNamed<T>(String name, { required Object? arguments, bool allowNull = false }) {
      assert(!_debugLocked);
      assert(name != null);
      if (allowNull && widget.onGenerateRoute == null)
        return null;
      assert(() {
        if (widget.onGenerateRoute == null) {
          throw FlutterError(
            'Navigator.onGenerateRoute was null, but the route named "$name" was referenced.\n'
            'To use the Navigator API with named routes (pushNamed, pushReplacementNamed, or '
            'pushNamedAndRemoveUntil), the Navigator must be provided with an '
            'onGenerateRoute handler.\n'
            'The Navigator was:\n'
            '  $this',
          );
        }
        return true;
      }());
      final RouteSettings settings = RouteSettings(
        name: name,
        arguments: arguments,
      );
      Route<T>? route = widget.onGenerateRoute!(settings) as Route<T>?;
      if (route == null && !allowNull) {
        assert(() {
          if (widget.onUnknownRoute == null) {
            throw FlutterError.fromParts(<DiagnosticsNode>[
              ErrorSummary('Navigator.onGenerateRoute returned null when requested to build route "$name".'),
              ErrorDescription(
                'The onGenerateRoute callback must never return null, unless an onUnknownRoute '
                'callback is provided as well.',
              ),
              DiagnosticsProperty<NavigatorState>('The Navigator was', this, style: DiagnosticsTreeStyle.errorProperty),
            ]);
          }
          return true;
        }());
        route = widget.onUnknownRoute!(settings) as Route<T>?;
        assert(() {
          if (route == null) {
            throw FlutterError.fromParts(<DiagnosticsNode>[
              ErrorSummary('Navigator.onUnknownRoute returned null when requested to build route "$name".'),
              ErrorDescription('The onUnknownRoute callback must never return null.'),
              DiagnosticsProperty<NavigatorState>('The Navigator was', this, style: DiagnosticsTreeStyle.errorProperty),
            ]);
          }
          return true;
        }());
      }
      assert(route != null || allowNull);
      return route;
    }
    

    widget.onGenerateRoute是_WidgetsAppState中的 _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;
    }
    

    pageContentBuilder逻辑是如果路由是默认的“/”且配置了home属性则返回home属性的值,否则取routes集合中的对应路由,最后如果都没有找到合适的则会调用widget.onGenerateRoute来处理,我们这里的情况是后者,所以看一下widget.pageRouteBuilder:

    pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
      return MaterialPageRoute<T>(settings: settings, builder: builder);
    }
    

    可见返回的是MaterialPageRoute。回到_routeNamed,Route为null并且不允许null则会尝试找widget.onUnknownRoute指定的路由作为容错显示页面。

    那么页面怎么展示的呢?

    看一下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,它的initialEntries属性指定了 _allRouteOverlayEntries:

    Iterable<OverlayEntry> get _allRouteOverlayEntries sync* {
      for (final _RouteEntry entry in _history)
        yield* entry.route.overlayEntries;
    }
    

    _allRouteOverlayEntries通过懒加载的方式从 _history中获取路由,然后最终在OverlayState中交给了 _Theatre:

    class _Theatre extends MultiChildRenderObjectWidget {
    

    _Theatre继承自MultiChildRenderObjectWidget,下面就走渲染逻辑了:

    组件最终的Layout、渲染都是通过RenderObject来完成的,从创建到渲染的大体流程是:根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。

  • dart代码中获取路由参数

    引入:

    import 'dart:ui';
    

    然后通过window.defaultRouteName获取。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,874评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,151评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,270评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,137评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,116评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,935评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,261评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,895评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,342评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,854评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,978评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,609评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,181评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,182评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,402评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,376评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,677评论 2 344

推荐阅读更多精彩内容