在 一起原始碼 之 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看,可能會有三個問題:
- GetX是如何做到自動更新的?
- Controller和GetX是如何互動的?
- 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的入參泛型T是DisposableInterface的子類,而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();
}
複製程式碼
上述程式碼的作用如下:
- GetInstance類提供了跨頁面的例項共享與管理。我們暫且不討論。isRegistered()方法用於判斷指定型別下的指定tag物件是否已經在框架中註冊。
- controller物件如果是全域性物件(global=true),則再檢查物件是否已準備就緒。在GetInstance中,如果一個物件已經被init或者物件沒有被註冊,isPrepared()都會返回false,所以這裡先使用isRegistered()確保物件已經被註冊,然後使用isPrepared()判斷物件是否已經被載入。
- 如果被載入,則標記controller的建立者為當前state,也就是isCreator=true。
- 則使用widget.init初始物件,註冊到Get中,並設定isCreator=true
- 如果controller物件是區域性物件(global=false),並且直接將widget.init設定為controller,並呼叫他的onStart()方法,開啟生命週期。
- 我們在分析GetxController的時候知道,onStart()會呼叫onInit()方法,所以Controller.onInit()對應State.onInit()。而onReady()方法會在下一幀呼叫,也就是會在onInit()之後呼叫。
- Get.smartManagement是對GetX中共享物件的管理策略,我們暫時不討論。
- 設定當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中將發揮作用。