一起原始碼 之 GetX(二) :GetxController和GetX控制元件

ifmu發表於2021-03-03

一起原始碼 之 GetX(一) :從官方demo開始中我們使用了官方demo,我們貼出的原始碼是簡單改動過的。

而改動的部分主要是將CountModel由GetxController的子類,更換為了普通物件。

下面我們一塊學習一下GetxController原始碼。

1. GetxController的實現

先不說其他,先看一下GetxController的繼承結構,如下圖:

由上圖可知,GetxController通過繼承DisposableInterface和mixin ListNotifier的方式,提供了生命週期和批量更新的能力。

  • DisposableInterface

    DisposableInterface通過繼承GetLifeCycle,提供瞭如下生命週期方法:

    • onStart 雖然是Function型別,但是以final變數的方式存在,用來防止子類重寫。本方法將在widget被載入到記憶體中時由框架呼叫。此方法的預設實現會檢查是否已經初始化,如果沒有,則呼叫onInit()方法,所以多次呼叫onStart,只會執行一次onInit()
    • onInit() 本方法會在widget被載入到記憶體中後呼叫。一般用來初始化一些後續需要使用的物件。
    • onReady() 此方法會比onInit()方法晚呼叫一幀,此方法一般用於執行snackbar、新的路由等需要頁面init完成後執行的操作。
    • onDelete 和onStart很類似,也是用於框架呼叫的final變數型別的Function。預設會關聯呼叫onClose(),並且執行多次,也只會執行一次onClose()。
    • onClose() 此方法用於回收資源,類似Widget中的dispose方法。

    對於**onReady()方法的描述,我們知道他會在onInit()**的下一幀呼叫,我們看一下原始碼的實現方式:

    @override
    @mustCallSuper
    void onInit() {
        super.onInit();
        SchedulerBinding.instance?.addPostFrameCallback((_) => onReady());
    }
    複製程式碼

    我們看到要達到這個效果,這裡使用的是SchedulerBinding.instance?.addPostFrameCallback的方式,類似的還有addPersistentFrameCallback方法,有興趣的童鞋可以瞭解一下Flutter的載入流程。

  • ListNotifier 既然是一起看原始碼,那先上酸菜:

    class ListNotifier implements Listenable {
        // 忽略了部分不重要的程式碼
          
        List<GetStateUpdate> _updaters = <GetStateUpdate>[];
          
        HashMap<Object, List<GetStateUpdate>> _updatersGroupIds = HashMap<Object, List<GetStateUpdate>>();
    
        @protected
        void refresh() {
            if (_microtask == _version) {
                _microtask++;
                // 非同步更新
                scheduleMicrotask(() {
                    _version++;
                    _microtask = _version;
                    _notifyUpdate();
                });
            }
        }
          
        // 簡單的更新邏輯
        void _notifyUpdate() {
            for (var element in _updaters) {
                element();
            }
        }
          
        // 按照組id進行更新
        void _notifyIdUpdate(Object id) {
            if (_updatersGroupIds.containsKey(id)) {
                final listGroup = _updatersGroupIds[id];
                    for (var item in listGroup) {
                    item();
                }
            }
        }
    
        @protected
        void refreshGroup(Object id) {
            // 非同步更新
            if (_microtask == _version) {
                _microtask++;
                scheduleMicrotask(() {
                    _version++;
                    _microtask = _version;
                    _notifyIdUpdate(id);
                });
            }
        }
          
        // 在更新佇列_updaters中新增setState方法
        @protected
        void notifyChildrens() {
            TaskManager.instance.notify(_updaters);
        }
          
        // 刪除了部分增加、刪除監聽的方法
    }
    複製程式碼

    由上面的原始碼可知,ListNotifier提供了兩種註冊方式:以單個的方式註冊更新、以組的方式註冊更新

    我們知道Flutter是單執行緒執行的,在Flutter啟動過程中會維護兩個佇列:EventQueue和MicroTaskQueue

    每次ticker觸發時,都會執行兩個佇列,其中MicroTaskQueue的優先順序要高於EventQueue。只有當task執行完成以後,才會執行EventQueue中的內容。

    這裡使用scheduleMicrotask()方法執行具體的更新操作,可以不阻塞呼叫方,將具體更新放到下次ticker執行,並且在下次繪製之前得到執行。

2. GetX的自動更新

GetX的官方demo中是這樣介紹GetX的:

// controller
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;

// 檢視
GetX<Controller>(
  builder: (controller) {
    print("count 1 rebuild");
    return Text('${controller.count1.value}');
  },
),
GetX<Controller>(
  builder: (controller) {
    print("count 2 rebuild");
    return Text('${controller.count2.value}');
  },
),
GetX<Controller>(
  builder: (controller) {
    print("count 3 rebuild");
    return Text('${controller.sum}');
  },
),
複製程式碼

如果我們把**count1.value++**遞增,就會列印出來:

  • count 1 rebuild
  • count 3 rebuild

如果我們改變count2.value++,就會列印出來。

  • count 2 rebuild
  • count 3 rebuild

因為count2.value改變了,sum的結果現在是2。

從Demo看,可能會有三個問題:

  1. GetX是如何做到自動更新的?
  2. Controller和GetX是如何互動的?
  3. sum沒有使用Rx,但是count1、count2更新時,他同樣觸發了更新,是如何做到的?

在看原始碼的過程中,帶著這三個問題,我們一步步尋找答案。

2.1. GetX的自動更新

我們前一篇已經分析過Obx是如何自動更新的,我們已經瞭解到Obx是如何巧妙的使用全域性proxy替換,以及如何巧妙的使用Rx物件的get方法。

我們推測GetX也是基於StatefulWidget的能力進行的UI重繪,也是使用了相同的套路進行事件流監聽。

GetX的原始碼如下:

class GetX<T extends DisposableInterface> extends StatefulWidget {
  final GetXControllerBuilder<T> builder;
  final bool global;
  final bool autoRemove;
  final bool assignId;
  final void Function(State state) initState, dispose, didChangeDependencies;
  final void Function(GetX oldWidget, State state) didUpdateWidget;
  final T init;
  final String tag;

  const GetX({
    this.tag,
    this.builder,
    this.global = true,
    this.autoRemove = true,
    this.initState,
    this.assignId = false,
    this.dispose,
    this.didChangeDependencies,
    this.didUpdateWidget,
    this.init,
  });

  @override
  GetXState<T> createState() => GetXState<T>();
}
複製程式碼

從上述原始碼可見:

  • GetX確實是使用的StatefulWidget進行的UI更新
  • GetX的入參泛型TDisposableInterface的子類,而DisposableInterface我們前面已經分析過,他主要提供宣告週期的功能。所以T型別時具有宣告週期的型別,可以是GetxController型別。
  • GetX提供了一些類似State中的回撥方法,用於將State回撥時機給到使用方。
  • GetX還提供了諸如global、tag等入參,我們通過對State的分析,再看具體作用。

從上述原始碼可以看到GetX是StatefulWidget,對應GetXState類,我們再詳細看一下其中的原始碼:


class GetXState<T extends DisposableInterface> extends State<GetX<T>> {
  GetXState() {
    _observer = RxNotifier();
  }
  RxInterface _observer;
  
  // 省略部分程式碼

  Widget get notifyChildren {
    final observer = RxInterface.proxy;
    RxInterface.proxy = _observer;
    final result = widget.builder(controller);
    if (!_observer.canUpdate) {
      throw "錯誤提示";
    }
    RxInterface.proxy = observer;
    return result;
  }

  @override
  Widget build(BuildContext context) => notifyChildren;
}
複製程式碼

從上述原始碼可知,和我們推測的一致,GetX也使用了proxy的交換技巧,用以遠端設定觀察者。

2.2. GetX和GetxController的互動

除了上述原始碼,State中還有一部分原始碼,用於和Controller進行互動,如下:

// GetXState原始碼
T controller;
bool isCreator = false;
StreamSubscription subs;
    
@override
void initState() {
    // 1
    var isRegistered = GetInstance().isRegistered<T>(tag: widget.tag);
    
    // 2
    if (widget.global) {
        if (isRegistered) {
            if (GetInstance().isPrepared<T>(tag: widget.tag)) {
                isCreator = true;
            } else {
                isCreator = false;
            }
            controller = GetInstance().find<T>(tag: widget.tag);
        } else {
            controller = widget.init;
            isCreator = true;
            GetInstance().put<T>(controller, tag: widget.tag);
        }
    } else {
        // 3
        controller = widget.init;
        isCreator = true;
        controller?.onStart();
    }
    // 4
    widget.initState?.call(this);
    if (widget.global && Get.smartManagement == SmartManagement.onlyBuilder) {
        controller?.onStart();
    }
    // 5
    subs = _observer.listen((data) => setState(() {}));
    super.initState();
}
複製程式碼

上述程式碼的作用如下:

  1. GetInstance類提供了跨頁面的例項共享與管理。我們暫且不討論。isRegistered()方法用於判斷指定型別下的指定tag物件是否已經在框架中註冊。
  2. controller物件如果是全域性物件(global=true),則再檢查物件是否已準備就緒。在GetInstance中,如果一個物件已經被init或者物件沒有被註冊,isPrepared()都會返回false,所以這裡先使用isRegistered()確保物件已經被註冊,然後使用isPrepared()判斷物件是否已經被載入。
    • 如果被載入,則標記controller的建立者為當前state,也就是isCreator=true。
    • 則使用widget.init初始物件,註冊到Get中,並設定isCreator=true
  3. 如果controller物件是區域性物件(global=false),並且直接將widget.init設定為controller,並呼叫他的onStart()方法,開啟生命週期。
    • 我們在分析GetxController的時候知道,onStart()會呼叫onInit()方法,所以Controller.onInit()對應State.onInit()。而onReady()方法會在下一幀呼叫,也就是會在onInit()之後呼叫。
  4. Get.smartManagement是對GetX中共享物件的管理策略,我們暫時不討論。
  5. 設定當observer的資料流發生變化時,呼叫setState。和Obx中相似,但是Obx判斷了當前State的狀態,感覺Obx中的處理會更安全。

其實GetX控制元件中還有部分程式碼沒有貼出來,基本上都是轉發didChangeDependencies()之類的,以及dispose的時候,根據isCreator,是否移除controller物件。

看完GetState的原始碼以後,我們回過頭再仔細看一下GetX的定義:

class GetX<T extends DisposableInterface> extends StatefulWidget {
  final GetXControllerBuilder<T> builder;
  final bool global;
  final bool autoRemove;
  final bool assignId;
  final void Function(State state) initState, dispose, didChangeDependencies;
  final void Function(GetX oldWidget, State state) didUpdateWidget;
  final T init;
  final String tag;

  const GetX({
    this.tag,
    this.builder,
    this.global = true,
    this.autoRemove = true,
    this.initState,
    this.assignId = false,
    this.dispose,
    this.didChangeDependencies,
    this.didUpdateWidget,
    this.init,
  });
}
複製程式碼
  • tag 我們從State的原始碼知道,這個tag定義的是從Get中獲取共享變數時的key值。所以如果前後兩個頁面共享相同例項,當tag有自定義的時候,則前後必須一致。
  • builder 構建真正的顯示Widget.
  • global 定義controller變數是否為全域性的。
    • 如果不是全域性,則會使用init作為controller,跟隨當前Widget的生命週期而回收。
    • 如果是全域性,則會查詢Get中是否有共享例項。
      • 如果沒有,使用init引數作為預設controller,並新增到Get中管理。關於是否回收,需要有autoRemove引數決定。
      • 如果有,則複用共享例項。
  • autoRemove 如果判斷出當前例項是由當前widget所create,則會判斷此變數,從而在State.dispose()的時候回收共享例項。
  • initState、dispose、didChangeDependencies、didUpdateWidget 方法會在State相應方法被呼叫時,進行回撥。
  • assignId 如果此變數為true,則Get中的例項controller,不論是不是當前create的,都會根據autoRemove嘗試回收。
  • init 預設controller物件。
    • 如果global=false,則直接使用此入參為controller。並在dispose的時候根據autoRemove進行刪除
    • 如果global=true,則會嘗試從Get中載入指定tag以及型別的共享例項,如果沒有,則使用init作為預設引數。同樣,在dispose的時候根據 是否為當前create或者是否是assignId,以及 是否autoRemove 進行刪除

通過原始碼閱讀我們知道GetX是響應式的自動更新,可以設定controller共享物件,並支援controller的自動回收。但是因為這裡的controller物件不是真正的GetxController,所以不支援controller內部的update。

GetX和Obx他們都是響應式自動更新,但是兩者最大的區別在於GetX支援很多State生命週期的回撥,以及controller的例項共享和生命週期觸發。

可以認為Obx是輕量級的GetX實現。

2.3. sum是如何更新的

我們再回過頭來看之前提到的官方demo中sum的問題:sum沒有使用Rx,但是count1、count2更新時,他同樣觸發了更新,是如何做到的?

我們通過分析GetX的程式碼知道,GetX使用了和Obx相同的proxy互換的技巧提供響應式更新。

其實在build第三個GetX的時候使用了sum,sum就會同步使用count1和count2,當呼叫他們的get方法時,此時的proxy是當前GetX中的observer,所以count1和count2都會再設定GetX中的observer到流監聽中。此時count1被GetX1和GetX3監聽,count2被GetX2和GetX3監聽,所以當他們任意一個改變時都會觸發GetX3的更新。

3. 總結

GetxController通過繼承DisposableInterface和mixin ListNotifier的方式,提供了生命週期和批量更新的能力。

而GetX要求入參為DisposableInterface型別,實現了共享例項生命週期的維護。與Obx相同,使用了proxy交換的方式,實現了響應式更新。

我們這裡的GetxController中的更新能力在GetX中沒有使用(入參是DisposableInterface型別),但是在後面的GetBuilder中將發揮作用。

一起原始碼 之 GetX(一) :從官方demo開始

相關文章