認識一下Flutter中Navigator資料傳遞原理

AndroidHint發表於2020-02-22

引言

在Flutter中,路由間的頁面跳轉使用的是Navigator.pushNavigator.pop方法。

在頁面跳轉時如何將資料傳遞過去,目前有兩種方法:

1、目標頁面的建構函式顯式接收引數。例如跳轉過去的是SearchPage,接收一個字串引數,則如下所示。

Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context) => new SearchPage("傳遞的引數")));
複製程式碼

2、我們還可以使用RouteRouteSetting引數進行資料的傳遞。例如

Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context) => new SearchPage()
    , settings: RouteSettings(arguments: "傳遞的引數")));
複製程式碼

在頁面SearchPage可以這樣獲取傳遞過來的引數:

String arg = ModalRoute.of(context).settings.arguments;
複製程式碼

這裡我們想說一下第二種方式資料傳遞的原理,讓我們從原始碼中一窺究竟吧!

原始碼解析

大推論

究竟怎麼開始分析原始碼呢?我們用打斷點的形式,就能一步一步地看到每一個方法的執行過程,既然最終的目標頁是SearchPage,那麼我們就將斷點打在SearchPagebuild方法,由於SearchPage是一個StatefulWidget,則斷點打在了SearchPageStatebuild方法裡,如下圖所示。

認識一下Flutter中Navigator資料傳遞原理
context是一個StatefulElement,依次點開它的_parent變數,如下兩圖所示。

認識一下Flutter中Navigator資料傳遞原理
認識一下Flutter中Navigator資料傳遞原理
最終看到了Navigator的身影。也就是說,使用Navigator.push方法跳轉到SearchPage,則相當於將SearchPage掛載在了Navigator這個Widget下,SearchPage相當於Navigator的一個子元件。

接下來,我們將斷點打在SearchPage中使用ModalRoute.of方法這一行,如下圖所示。

認識一下Flutter中Navigator資料傳遞原理
進入到ModalRouteof方法。

static ModalRoute<T> of<T extends Object>(BuildContext context) {
    final _ModalScopeStatus widget = context.inheritFromWidgetOfExactType(_ModalScopeStatus);
    return widget?.route;
}
複製程式碼

這裡的context就是SearchPageElement,呼叫了inheritFromWidgetOfExactType方法,並將_ModalScopeStatus作為引數傳遞過去。

@override
  InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    if (ancestor != null) {
      assert(ancestor is InheritedElement);
      return inheritFromElement(ancestor, aspect: aspect);
    }
    _hadUnsatisfiedDependencies = true;
    return null;
 }
複製程式碼

上面的方法邏輯也挺簡單的,_inheritedWidgets是一個Map,如果不為空則獲取_inheritedWidgetskey_ModalScopeStatusInheritedElement物件。那麼_inheritedWidgets是什麼時候被賦值的呢?我們全域性搜尋一下,發現在 _updateInheritance中有對_inheritedWidgets進行操作。

void _updateInheritance() {
    assert(_active);
    final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
    if (incomingWidgets != null)
      _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
    else
      _inheritedWidgets = HashMap<Type, InheritedElement>();
    _inheritedWidgets[widget.runtimeType] = this;
 }
複製程式碼

首先它會將父類(_parent)的_inheritedWidgets賦值給自己的_inheritedWidgets,然後對於_inheritedWidgets的當前執行型別,將自己(this)進行賦值。

現在讓我們回過頭來看一下上面的ModalRoute.of方法。

static ModalRoute<T> of<T extends Object>(BuildContext context) {
    final _ModalScopeStatus widget = context.inheritFromWidgetOfExactType(_ModalScopeStatus);
    return widget?.route;
}
複製程式碼

第一行返回了_ModalScopeStatus物件。根據上面的結論,我們知道_inheritedWidgets[_ModalScopeStatus]就是在_ModalScopeStatus_updateInheritance進行賦值的。

也就是說context.inheritFromWidgetOfExactType其實是從下往上尋找_ModalScopeStatus物件,然後of方法會返回_ModalScopeStatusroute變數。

NavigatorSearchPageWidget樹可以看到,_ModalScopeStatus的確存在於上述的Widget樹中,如下圖所示。

認識一下Flutter中Navigator資料傳遞原理

於是我們有理由得出如下大推論:

Navigator.push方法自上而下地產生了從Navigator到_ModalScopeStatus到SearchPage的Widget樹,並且Navigator將路由資訊儲存在了_ModalScopeStatus這個Widget中,SearchPage自下而上尋找_ModalScopeStatus並獲取其中的路由資訊。

驗證大推論

第一個小推論

為了驗證上述推論的正確性,我們從Navigatorbuild方法出發,一步步地看是否真的有將Route物件傳遞到_ModalScopeStatus物件。

Widget build(BuildContext context) {
    return 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: Overlay(
            key: _overlayKey,
            initialEntries: _initialOverlayEntries,
          ),
        ),
      ),
    );
 }
複製程式碼

從上面的build方法與上面打斷點時的Widget樹得知,Navigator的子Widget樹為Listener->AbsorbPointer->FocusScope->Overlay,而Overlaybuild方法如下。

Widget build(BuildContext context) {
    final List<Widget> onstageChildren = <Widget>[];
    final List<Widget> offstageChildren = <Widget>[];
    bool onstage = true;
    for (int i = _entries.length - 1; i >= 0; i -= 1) {
      final OverlayEntry entry = _entries[i];
      if (onstage) {
        onstageChildren.add(_OverlayEntry(entry));
        if (entry.opaque)
          onstage = false;
      } else if (entry.maintainState) {
        offstageChildren.add(TickerMode(enabled: false, child: _OverlayEntry(entry)));
      }
    }
    return _Theatre(
      onstage: Stack(
        fit: StackFit.expand,
        children: onstageChildren.reversed.toList(growable: false),
      ),
      offstage: offstageChildren,
    );
 }
複製程式碼

Overlay的子Widget樹為_Theatre->Stack,而StackchildrenonstageChildren列表,則Stack的子Widget_OverlayEntry,而_OverlayEntrybuild方法如下。

Widget build(BuildContext context) {
    return widget.entry.builder(context);
}
複製程式碼

認識一下Flutter中Navigator資料傳遞原理
結合打斷點後Navigator的子Widget樹得知,_OverlayEntry的子Widget_ModalScope,也就是說我們得出第一個小推論:

widget.entry.builder(context)返回的Widget就是_ModalScope。

驗證第一個小推論

我們驗證一下對不對,widget.entry其實是從OverlayState_enties列表中獲取的,而_enties列表是在Navigatorbuild方法中通過Overlay傳遞_initialOverlayEntries引數初始化的。

child: Overlay(
    key: _overlayKey,
    initialEntries: _initialOverlayEntries,
),
複製程式碼

_initialOverlayEntries是在NavigatorinitState初始化的。

void initState() {
    ...省略
    for (Route<dynamic> route in _history)
      _initialOverlayEntries.addAll(route.overlayEntries);
}
複製程式碼

這裡傳遞進來的是Route物件的overlayEntries,而Route物件的overlayEntries,而這裡的Route物件其實是MaterialPageRoute,所以的overlayEntriesMaterialPageRoute的父類OverlayRoute被重寫了,所以它真實的賦值是在OverlayRouteinstall方法裡面,而install方法在Navigator.push方法中就被呼叫了,所以我們看一下install方法。

void install(OverlayEntry insertionPoint) {
    assert(_overlayEntries.isEmpty);
    _overlayEntries.addAll(createOverlayEntries());
    navigator.overlay?.insertAll(_overlayEntries, above: insertionPoint);
    super.install(insertionPoint);
}
複製程式碼

_overlayEntries新增了createOverlayEntries方法的執行結果。

Iterable<OverlayEntry> createOverlayEntries() sync* {
    yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
    yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
}
複製程式碼

上面方法構建了一個OverlayEntry物件,其中的builder引數傳了_buildModalScope,而_buildModalScope返回的是_ModalScope物件。

Widget _buildModalScope(BuildContext context) {
    return _modalScopeCache ??= _ModalScope<T>(
      key: _scopeKey,
      route: this,
      // _ModalScope calls buildTransitions() and buildChild(), defined above
    );
}
複製程式碼

到這裡,我們終於驗證了第一個小推論的正確性,即_OverlayEntry的子Widget就是_ModalScope,而且這個_ModalScope還包含了路由資訊,就是其中的route變數。

由小推論驗證大推論的正確性

我們再來看_ModalScopebuild方法。

Widget build(BuildContext context) {
    return _ModalScopeStatus(
      route: widget.route,
      ...省略
    );
 }
複製程式碼

它返回了_ModalScopeStatus,並將它的route變數賦值給了_ModalScopeStatusroute變數,我們上面說過_ModalScoperoute變數就是MaterialPageRoute物件。

也就是說只要拿到了_ModalScopeStatus物件,就能通過它的route方法獲取MaterialPageRoute物件,並通過其中的setting變數獲取RouteSetting物件,而RouteSetting物件的arguments變數就是路由傳遞進來的引數了。

看到這裡,我們就不難理解為什麼在SearchPage中使用下面這樣的方法能獲取路由傳遞進來的資料的原因了。

String arg = ModalRoute.of(context).settings.arguments;
複製程式碼

因為通過ModalRoute.of(context)SearchPage自下而上地尋找_ModalScopeStatus物件,_ModalScopeStatus物件找到後通過route變數找到Route物件,也就是MaterialPageRoute物件,然後通過訪問MaterialPageRoute物件的setting.arguments就能拿到Navigator傳遞進來的引數了,這就是Navigator資料傳遞的原理。

從上述分析中,我們知道了Flutter中一個重要的功能型元件,它就是InheritedWidget,通過它我們可以實現跨元件的資料共享。有關更多InheritedWidget的使用方法,可以參考 資料共享(InheritedWidget)

相關文章