Flutter 學習之旅(四十一) Flutter 狀態 Stream 學習(一)

Stream 是Flutter 中非常常用的一個概念,但是他并不是Flutter 所獨有的,而是在Dart中默認封裝的,他和Future 一樣,都可以異步執行,但是相對于Future Stream 更加高級.

Stream代表著事件流,通過Stream可以快速的實現事件流驅動業務邏輯,界面通過訂閱事件,針對事件轉換最后通過響應事件完成頁面布局,而在整個Stream流過程中,有四個角色分別扮演了不同的角色

1.StreamController

字面意思,管理調度整個事件流的流程,并保存整個事件流中所需要的對象,便于管理和使用

2.StreamSink

事件的開始入口,所有的同步和異步事件都是從這里開始的 ,提供了add 和 addStream 等方法

3.Stream

事件本身,可以被轉換和監聽,訂閱后返回StreamSubscription 對象

4.StreamSubscription

訂閱Stream 后得到的對象,可以管理訂閱過的各種操作,例: cancel() pause();

整個事件流的流經過程就是,先創建Stream ,創建回調方法并訂閱, 再通過StreamSink 添加事件源,在訂閱的過程中可以使用StreamSubscription管理這個訂閱狀態,最后回調 Stream 訂閱的回調方法,仔細想一下整個過程其實和 StreamController 并沒有太大的關系, 但是整個過程如果離開了StreamController , 沒有從中間調度的過程,整個代碼的邏輯和代碼量都是非常讓人頭疼的,

在這里再吐槽一下dart 源碼的結構,雖然通過 with 拼接是一個很好的方式,但是這樣對剛剛學習的來說是非常不友好的,我整個學習過程中在學習控件的時候并沒有太多的感觸,但是在接觸Element 和RenderObject 后,心里已經產生了這個想法,今天上午在看Stream的過程更是讓我再次認同了這個想法,不過話雖然這么說,還是需要看的,自己也算是看了一些源碼了,總結了一下dart源碼怎么看,正好利用我對Stream的學習,分享一下我是怎么看源碼的. 廢話不多說,上套路!!!

這里先貼一下我的環境,
D:\User\flutter_app1>flutter --version
Flutter 1.20.3 ? channel stable ? https://github.com/flutter/flutter.git
Framework ? revision 216dee60c0 (6 weeks ago) ? 2020-09-01 12:24:47 -0700
Engine ? revision d1bc06f032
Tools ? Dart 2.9.2

1.理清構架

想要學習源碼,首先你要知道各個類在這個功能中所扮演的角色,不必清楚每個方法是如何實現的,但是你需要他在整個流程中存在的目的, 這些很簡單,通過度娘就能獲取到你想要知道的淺顯的介紹,這里回想一下Stream 的幾個角色,想想他們的用途,但是他們是如何關聯起來的呢,帶著這個疑問,我們就來從代碼說起.

2.重點突破

StreamController 作為管理和調度中心,他的源碼其實應該就是整個Stream實現的核心,但是你去看一下其實他的方法非常的亂,沒有一個主線將它連接起來, 這里我們看一下上面我們對整個Stream 的流程總結,我們就按照總結的流程來看一下Stream 的工作過程, 先看Stream

1>Stream 的創建和訂閱

我們首次獲取 Stream 是通過StreamController 來獲取的,

  // Return a new stream every time. The streams are equal, but not identical.
///在獲取Stream 的時候,每次都獲取一個新的Stream , 流是相等的,但是并不完全相同,
  Stream<T> get stream => _ControllerStream<T>(this);

根據創建的實體我們看一下他的訂閱 也就是listen 方法,

abstract class _StreamImpl<T> extends Stream<T> {

  ////可以傳入onError 和 onDone  也就是錯誤和完成回調
  StreamSubscription<T> listen(void onData(T data)?, {Function? onError, void onDone()?, bool? cancelOnError}) {
    cancelOnError ??= false;
    ////創建訂閱 _createSubscription 這個方法被子類重寫了
    StreamSubscription<T> subscription =_createSubscription(onData, onError, onDone, cancelOnError);
    _onListen(subscription);
    return subscription;
  }
}

在這里發現這個方法并不是_ControllerStream實現的,而是由他的父類_StreamImpl實現的,代碼我已經放在上面了,

class _ControllerStream<T> extends _StreamImpl<T> {
  _StreamControllerLifecycle<T> _controller;
  _ControllerStream(this._controller);
  StreamSubscription<T> _createSubscription(void onData(T data)?,
          Function? onError, void onDone()?, bool cancelOnError) =>
      _controller._subscribe(onData, onError, onDone, cancelOnError);
}

這里我們發現_createSubscription 這個方法又調用了 StreamController 的_subscribe 方法

  StreamSubscription<T> _subscribe(void onData(T data)?, Function? onError,
      void onDone()?, bool cancelOnError) {
    ///不能重復訂閱
    if (!_isInitialState) {
      throw StateError("Stream has already been listened to.");
    }
///創建訂閱
    _ControllerSubscription<T> subscription = _ControllerSubscription<T>( this, onData, onError, onDone, cancelOnError);
    _PendingEvents<T>? pendingEvents = _pendingEvents;
///修改狀態為已經訂閱  
    _state |= _STATE_SUBSCRIBED;
if (_isAddingStream) {
      _StreamControllerAddStreamState<T> addState = _varData as dynamic;
      addState.varData = subscription;
      addState.resume();
    } else {
      _varData = subscription;
    }
     ...
    return subscription;
  }

_varData 就是訂閱后的 subscription 在這里共享了他,以便于在后續接收到數據時找到回調 ,他只能被訂閱一次,

我們發現在創建訂閱過程中使用的是 _ControllerSubscription ,他繼承了 _BufferingStreamSubscription 這個類來創建的訂閱,

  _BufferingStreamSubscription(void onData(T data)?, Function? onError,
      void onDone()?, bool cancelOnError)
      : this.zoned(Zone.current, onData, onError, onDone, cancelOnError);

  _BufferingStreamSubscription.zoned(this._zone, void onData(T data)?,
      Function? onError, void onDone()?, bool cancelOnError)
      : _state = (cancelOnError ? _STATE_CANCEL_ON_ERROR : 0),
        _onData = _registerDataHandler<T>(_zone, onData),
        _onError = _registerErrorHandler(_zone, onError),
        _onDone = _registerDoneHandler(_zone, onDone);

來到創建 StreamSubscription 的這個方法 ,收到了這個onData () 這個方法,并保存了起來,這里我們發現
StreamSubscription 的最終實現子類是 _BufferingStreamSubscription ,記錄一下,這個很重要

controller.stream.listen() --> _ControllerStream.listen() --> _ControllerStream._createSubscription() --> controller._subscribe() --> 返回_ControllerSubscription(); 并執行 zone.runUnaryGuarded()

創建Stream 并訂閱的整個流程大致就是這些方法,

這里我們就把創建 Stream 和 訂閱的這個過程看完了,接下來我們再看 StreamSink 發送事件源的過程

2> StreamSink 發送事件源

說道這里我們就要說一下StreamController 他的創建方法, 這里有一個bool sync ,他控制著是同步執行還是異步執行,
如果 sync 為true 則同步執行,否則異步執行,他默認的是異步執行,也就是說 默認創建StreamController 發送這接收信息之間是異步的,這里先說一下同步和異步的區別,

  factory StreamController(
      {void onListen()?,
      void onPause()?,
      void onResume()?,
      FutureOr<void> onCancel()?,
      bool sync = false}) {
    return sync
        ? _SyncStreamController<T>(onListen, onPause, onResume, onCancel)
        : _AsyncStreamController<T>(onListen, onPause, onResume, onCancel);
  }

abstract class _SyncStreamControllerDispatch<T>
    implements _StreamController<T>, SynchronousStreamController<T> {
  void _sendData(T data) {
    _subscription._add(data);
  }
}

abstract class _AsyncStreamControllerDispatch<T>
    implements _StreamController<T> {
  void _sendData(T data) {
    _subscription._addPending(_DelayedData<T>(data));
  }
}

開始說事件源分發
與 Stream 同樣的道理,先在 StreamController 中找到sink ,

  /**
   * Returns a view of this object that only exposes the [StreamSink] interface.
   */
  StreamSink<T> get sink => _StreamSinkWrapper<T>(this);

這里的sink 使用的是_StreamSinkWrapper 來實現,我們來看一下他的送法事件源 即 add 方法

class _StreamSinkWrapper<T> implements StreamSink<T> {
  final StreamController _target;
  _StreamSinkWrapper(this._target);
  void add(T data) {
    _target.add(data);
  }
}

來到這里我們發現整個 _StreamSinkWrapper 就是一個中轉類,在調用他的add 方法的時候,其實調用的是target.add()方法,即 StreamController 的add 方法

 void _add(T value) {
  ///在 listen 方法中已經調用controller 的 sub
    if (hasListener) {
      _sendData(value);
    } else if (_isInitialState) {
      _ensurePendingEvents().add(_DelayedData<T>(value));
    }
  }

在StreamController 的add方法中我們看到,如果已經_subscribe 方法將state 修改為 _STATE_SUBSCRIBED;即已訂閱的狀態,這里走事件的分發 _sendData(value); 這個方法

這里我們先說一下同步執行的方法

abstract class _SyncStreamControllerDispatch<T>
    implements _StreamController<T>, SynchronousStreamController<T> {
  void _sendData(T data) {
    _subscription._add(data);
  }

  void _sendError(Object error, StackTrace stackTrace) {
    _subscription._addError(error, stackTrace);
  }

  void _sendDone() {
    _subscription._close();
  }
}

在同步方法里面,使用的_subscription 即上面所說的_varData 得_add 方法 也就是_BufferingStreamSubscription 的add 方法

///添加數據
  void _add(T data) {
    assert(!_isClosed);
    if (_isCanceled) return;
    if (_canFire) {
      _sendData(data);
    } else {
      _addPending(new _DelayedData<T>(data));
    }
  }
  
///使用zone 來 調用回調
  void _sendData(T data) {
    assert(!_isCanceled);
    assert(!_isPaused);
    assert(!_inCallback);
    bool wasInputPaused = _isInputPaused;
    _state |= _STATE_IN_CALLBACK;
    _zone.runUnaryGuarded(_onData, data);
    _state &= ~_STATE_IN_CALLBACK;
    _checkState(wasInputPaused);
  }

關于異步方法

abstract class _AsyncStreamControllerDispatch<T>
    implements _StreamController<T> {
  void _sendData(T data) {
    _subscription._addPending(_DelayedData<T>(data));
  }

  void _sendError(Object error, StackTrace stackTrace) {
    _subscription._addPending(_DelayedError(error, stackTrace));
  }

  void _sendDone() {
    _subscription._addPending(const _DelayedDone());
  }
}

這里使用的是 _subscription._addPending 的這個方法

  void _addPending(_DelayedEvent event) {
    _StreamImplEvents<T>? pending = _pending as dynamic;
    pending ??= _StreamImplEvents<T>();
    _pending = pending;
    pending.add(event);
    if (!_hasPending) {/// 沒有開始循環
      _state |= _STATE_HAS_PENDING;
      if (!_isPaused) {///沒有被暫停
        pending.schedule(this);
      }
    }
  }

這里初始化了 _pending 并將event 添加到里這個_pending 里面,這里如果循環處理事件沒有被開啟,則開啟循環事件處理消息,即 pending.schedule(this);

  void schedule(_EventDispatch<T> dispatch) {
    if (isScheduled) return;///已經開啟了,則返回
    assert(!isEmpty);
    if (_eventScheduled) {///事件已經被消費
      assert(_state == _STATE_CANCELED);
      _state = _STATE_SCHEDULED;
      return;
    }
////這里開始異步,遍歷 dispatch , 并開啟了循環
    scheduleMicrotask(() {
      int oldState = _state;
      _state = _STATE_UNSCHEDULED;
      if (oldState == _STATE_CANCELED) return;
      handleNext(dispatch);
    });
    _state = _STATE_SCHEDULED;
  }
  
////此時是異步執行,使用的是鏈表方式保存的數據  ,會存在異步耗時任務,第一個沒有執行完第二個又進來了,則這個任務會被默認調價到隊尾,
  void handleNext(_EventDispatch<T> dispatch) {
    assert(!isScheduled);
    assert(!isEmpty);
    _DelayedEvent event = firstPendingEvent!;
    _DelayedEvent? nextEvent = event.next;
    firstPendingEvent = nextEvent;
    if (nextEvent == null) {
      lastPendingEvent = null;
    }
    ///最后再執行下一個事件
    event.perform(dispatch);
  }
  ///此時是異步執行,使用_BufferingStreamSubscription   的_sendData 方法 執行回調 ,回到同步方法onData 
 void perform(_EventDispatch<T> dispatch) {
    dispatch._sendData(value);
  }

如何調用異步的簡單的說一下,由于本人了解的也不是很多,簡單的說一下loop的這個概念

///在我的理解所有的  _AsyncRun 共享同一個loop ,在你添加任務進來的同時,如果其他的 StreamSink 添加進來的任務沒有被消費完,則將你的添加到隊尾, 否則開始新的 異步循環
void _scheduleAsyncCallback(_AsyncCallback callback) {
  _AsyncCallbackEntry newEntry = new _AsyncCallbackEntry(callback);
  _AsyncCallbackEntry? lastCallback = _lastCallback;
  if (lastCallback == null) {
    _nextCallback = _lastCallback = newEntry;
    if (!_isInCallbackLoop) {
      _AsyncRun._scheduleImmediate(_startMicrotaskLoop);
    }
  } else {
    lastCallback.next = newEntry;
    _lastCallback = newEntry;
  }
}

///開始執行循環,如果你的循環執行完后,再次檢查,如果隊伍還有,則重新開始異步循環
void _startMicrotaskLoop() {
  _isInCallbackLoop = true;
  try {
    // Moved to separate function because try-finally prevents
    // good optimization.
    _microtaskLoop();
  } finally {
    _lastPriorityCallback = null;
    _isInCallbackLoop = false;
    if (_nextCallback != null) {
      _AsyncRun._scheduleImmediate(_startMicrotaskLoop);
    }
  }
}
 ////隊列模式,一個一個取出,并調用callback 回調
void _microtaskLoop() {
  for (var entry = _nextCallback; entry != null; entry = _nextCallback) {
    _lastPriorityCallback = null;
    var next = entry.next;
    _nextCallback = next;
    if (next == null) _lastCallback = null;
    (entry.callback)();
  }
}

這個發送事件源的過程也結束了,總結一下
StreamSink.add() --> StreamController.sendData ()
--> 同步: 直接StreamSubscription.sendData()
-->異步: 添加到_pending 隊列中StreamSubscription.addPending , -->如果loop 循環沒有執行 ,則開始循環 即 scheduleMicrotask 異步處理數據, -->_StreamImplEvents.handleNext() -->最后調用 StreamSubscription.sendData() 給予回調

我學習flutter的整個過程都記錄在里面了
http://www.lxweimin.com/c/36554cb4c804

最后附上demo 地址

https://github.com/tsm19911014/tsm_flutter

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374