-
概述
我们知道,在配置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>>(); }
梳理一下这个方法的逻辑:
- 自定义路由需要以‘/’开头;
- 取‘/’之后的字符串;
- 把这个字符串按照‘/’分割成一个集合;
- 把所有的子路由都添加到result这个集合中,比如“/route/my/one”会分成三个路由分别是“route”、“route/my”和“route/my/one”;
- 最后去除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获取。