Flutter Bloc
英文全稱(business logic),用于處理業(yè)務(wù)邏輯,其內(nèi)部實(shí)現(xiàn)主要是對(duì)Stream的輸入和輸出進(jìn)行了封裝,它的實(shí)現(xiàn)原理是利用RxDart(基于Stream封裝)提供的PublishSubject
和BehivorSubject
實(shí)現(xiàn)了Event
和State之間的轉(zhuǎn)換
,以及利用Flutter提供的局部狀態(tài)管理控件InheritedWidget
來傳遞Bloc
對(duì)象,涉及的類比較多,但基本都圍繞著對(duì)上面這個(gè)對(duì)象的封裝。
Usage
WorkFlow
- Provider負(fù)責(zé)提供Bloc對(duì)象,注冊(cè)到當(dāng)前的context中
- Bloc接口來自service和ui的事件,并將其轉(zhuǎn)換為對(duì)應(yīng)的state
- service和UI根據(jù)接受的state進(jìn)行更新各自的狀態(tài)
- 利用hydrateBloc對(duì)bloc的信息進(jìn)行緩存
主要類之間的關(guān)系
實(shí)現(xiàn)步驟
-
首先自定義Bloc
自定義一個(gè)繼承Bloc的類,并實(shí)現(xiàn)
mapEventToState
的方法邏輯更胖,完成事件輸入到數(shù)據(jù)輸出之間的轉(zhuǎn)換, 注冊(cè)Bloc
BlocProvider(
create: (context) => MyCustomBloc(),
child: YourCustomWidget(),
);
這里需要用到
BlocProvider
來包裝Bloc
的構(gòu)造方法和它的獲取方法
BlocProvider
繼承于ValueDelegateWidget
,并實(shí)現(xiàn)了SingleChildCloneableWidget
,包含一個(gè)ValueStateDelegate
對(duì)象ValueStateDelegate
主要是用于包裝Bloc
對(duì)象,用于控制是否更新或者銷毀blocValueDelegateWidget
繼承于DelegateWidget
,在其State
事件initState
,didUpdateWidget
,dispose
中對(duì)ValueStateDelegate
進(jìn)行去重,更新和銷毀操作,這樣就能間接作用于bloc上(這樣減少了Bloc的方法,起到了一定的解耦合作用),StateDelegate分為2中,BuilderStateDelegate
支持關(guān)閉bloc
,而ValueStateDelegate
不會(huì)自動(dòng)關(guān)閉bloc
,在使用中如果不是全局單例Bloc,則容易出現(xiàn)內(nèi)存泄漏
-BlocProvider
在build時(shí)他會(huì)在內(nèi)部創(chuàng)建一個(gè)InheritedProvider
,它接受了一個(gè)Bloc
作為值,由于InheritedProvider
是繼承于InheritedWidget
,所以Bloc
就可以通過InhertedElement
間接的存儲(chǔ)到當(dāng)前的的Element中,它的child
內(nèi)部就能直接通過Context獲取到.
SingleChildCloneableWidget
這個(gè)類主要是定義了一個(gè)clone的協(xié)議徒蟆,主要是為后面的MutliBlocProvider
服務(wù)的,將單個(gè)child拷貝多分,設(shè)置到每個(gè)BlocProvider
中,是框架內(nèi)部的一個(gè)語法糖拯啦。在
BlocProvider
注冊(cè)的過程中,可以看到它必須要又一個(gè)child,它自己本身是不需要在界面顯示,只是作為一個(gè)數(shù)據(jù)的提供者,所有在它的之下的widget都能通過BlocProvder.of<BlocName>(context)
獲取到這個(gè)對(duì)應(yīng)的Bloc郎逃。
class BlocProvider<T extends Bloc<dynamic, dynamic>>
extends ValueDelegateWidget<T> implements SingleChildCloneableWidget {
final Widget child;
//BlocProvider銷毀時(shí)bloc會(huì)被自動(dòng)關(guān)閉
BlocProvider({ ...
@required ValueBuilder<T> create,//在init時(shí)執(zhí)行此方法創(chuàng)建bloc
Widget child,
})
//BlocProvider銷毀時(shí)bloc不會(huì)自動(dòng)關(guān)閉
BlocProvider.value({ ...
@required T value, //bloc
Widget child, //具體的頁面
});
//這里只是用`Provider`的類方法包裝了一層,將通過InheritedElment獲取方法`context.inheritFromWidgetOfExactType(type) as InheritedProvider<T>`
static T of<T extends Bloc<dynamic, dynamic>>(BuildContext context) { ...
//通過`InheritedProvider`存儲(chǔ)bloc,提供provider獲取的方法
@override
Widget build(BuildContext context) {
return InheritedProvider<T>(
value: delegate.value,
child: child,
);
}
//方便MutliBlocProvider使用
@override
BlocProvider<T> cloneWithChild(Widget child) {
return BlocProvider<T>._(
key: key,
delegate: delegate,
child: child,
);
}
}
- Bloc使用方式一般有三種询件,
- 通過BlocBuilder來自動(dòng)捕獲asscent的bloc,或者直接在初始化的時(shí)候傳入一個(gè)bloc,通過監(jiān)聽內(nèi)部的bloc狀態(tài),并調(diào)用setState來更新當(dāng)前的widgteTree,在
BlocBuilder
和它繼承的BlocBuilderBase
實(shí)現(xiàn), 這里的條件主要是用來過濾Bloc的state,用于觸發(fā)不同的child widget
構(gòu)建
class BlocBuilder .. extends BlocBuilderBase { ...
final BlocWidgetBuilder<S> builder;
/// {@macro blocbuilder}
const BlocBuilder({
Key key,
@required this.builder,
B bloc,
BlocBuilderCondition<S> condition,)
@override
Widget build(BuildContext context, S state) => builder(context, state);
}
class _BlocBuilderBaseState<B extends Bloc<dynamic, S>, S>
extends State<BlocBuilderBase<B, S>> { ...
//記錄當(dāng)前bloc的訂閱,用于在銷毀時(shí)釋放訂閱
StreamSubscription<S> _subscription;
//狀態(tài)值記錄,用于回調(diào)給 api調(diào)用者過濾不同的 condition,決定當(dāng)前的state是否需要觸發(fā)新的構(gòu)建
S _previousState;
S _state;
B _bloc;
@override
void initState() {
super.initState();
//可以看到bloc還可以直接重BlocBuilder傳入,但這樣就不支持BlocProvider便利獲取了
_bloc = widget.bloc ?? BlocProvider.of<B>(context);
...
_subscribe();
}
//更新bloc的訂閱
@override
void didUpdateWidget(BlocBuilderBase<B, S> oldWidget) { ...
//外部widget構(gòu)建的回調(diào)方法
@override
Widget build(BuildContext context) => widget.build(context, _state);
//釋放bloc的訂閱
@override
void dispose() {
_unsubscribe();
super.dispose();
}
//監(jiān)聽bloc,并跳過intialState,因?yàn)樵趇nitial方法中已經(jīng)拿到了
void _subscribe() {
if (_bloc != null) {
_subscription = _bloc.skip(1).listen((S state) {
if (widget.condition?.call(_previousState, state) ?? true) {
setState(() { //很熟悉的setState
_state = state;
_previousState = state;
...
}
- 通過BlocListener來監(jiān)聽bloc的state變化,過濾指定條件的state執(zhí)行響應(yīng)的邏輯
class BlocListener<B extends Bloc<dynamic, S>, S> extends BlocListenerBase<B, S>
with SingleChildCloneableWidget {
//初始化BlocListener,指定需要過濾的條件和設(shè)置listener執(zhí)行的邏輯
const BlocListener({
Key key,
@required BlocWidgetListener<S> listener,
B bloc,
BlocListenerCondition<S> condition,
this.child,
})
//拷貝child到當(dāng)前的BlocListener中
@override
BlocListener<B, S> cloneWithChild(Widget child) { ...
@override
Widget build(BuildContext context) => child;
}
//Listener的邏輯實(shí)現(xiàn)
class _BlocListenerBaseState<B extends Bloc<dynamic, S>, S>
extends State<BlocListenerBase<B, S>> {
StreamSubscription<S> _subscription;
S _previousState;
B _bloc;
@override
void initState() {
super.initState();
//可以自帶一個(gè)bloc缚忧,但不支持其它子widget通過BlocProvider便利獲取
_bloc = widget.bloc ?? BlocProvider.of<B>(context);
_previousState = _bloc?.state;
_subscribe();
}
//更新當(dāng)前的bloc,重新訂閱
@override
void didUpdateWidget(BlocListenerBase<B, S> oldWidget) { ...
//構(gòu)建child widget,這里只是by pass,沒有做任何操作
@override
Widget build(BuildContext context) => widget.build(context);
//訂閱bloc state
void _subscribe() {
if (_bloc != null) {
_subscription = _bloc.skip(1).listen((S state) {
if (widget.condition?.call(_previousState, state) ?? true) { //condition的過濾條件在這里
widget.listener(context, state);
_previousState = state;
}
});
}
}
void _unsubscribe() { ...
- 為了便面地獄式的嵌套
BlocProvider/BlocListener
,引入了MultiBlocListener
和MultiBlocProvider
,他們屬于StatelessWidget
,主要是利用一個(gè)數(shù)組存儲(chǔ)多個(gè)BlocProvider
或BlocListener
,結(jié)合SingleChildCloneableWidget
協(xié)議的實(shí)現(xiàn)
Widget build(BuildContext context) {
return MultiProvider(
providers: listeners,
child: child,
);
}
// SingleChildCloneableWidget 協(xié)議的實(shí)現(xiàn)
@override
Widget build(BuildContext context) {
var tree = child;
for (final provider in providers.reversed) {
tree = provider.cloneWithChild(tree);
}
return tree;
}
@override
MultiProvider cloneWithChild(Widget child) {
return MultiProvider(
key: key,
providers: providers,
child: child,
);
}
Bloc數(shù)據(jù)緩存
主要是通過構(gòu)建中間基類,對(duì)Bloc的initialState和translation事件進(jìn)行傳遞并緩存數(shù)據(jù)到storage.可以參考HydratedBloc
實(shí)現(xiàn),以Bloc官方提供的HydratedBloc
為例,它主要包括HydratedBloc
,Your CustomBloc
和Bloc
之間插入的基本,用于管理Bloc數(shù)據(jù)的緩存.HydratedBlocDelegate
它繼承了BlocDelegate
,用于替換Bloc框架內(nèi)部默認(rèn)的BlocDelegate,攔截Bloc
的Translation
中的兩個(gè)State并存儲(chǔ)其每次變化的狀態(tài)到緩存中去.
關(guān)鍵部分的代碼實(shí)現(xiàn):
abstract class HydratedBloc<Event, State> extends Bloc<Event, State> {
//內(nèi)置一個(gè)默認(rèn)的Storage,
//BlocSupervisor.delegate這個(gè)是改寫之后的 BlocDelegate
final HydratedStorage _storage =
(BlocSupervisor.delegate as HydratedBlocDelegate).storage;
//初始化時(shí)從緩存獲取數(shù)據(jù),并在子類自定義的Bloc中實(shí)現(xiàn)這個(gè)三個(gè)方法,
@mustCallSuper
@override
State get initialState { ...
State fromJson(Map<String, dynamic> json);
Map<String, dynamic> toJson(State state);
//HydratedBloc內(nèi)部使用,用于提供BlocState的key
String get id => ''; //可以定義擴(kuò)展緩存的key名字,用于區(qū)分不同的業(yè)務(wù)邏輯數(shù)據(jù),一般公共的bloc邏輯類可以采用此類方法處理
Future<void> clear() => _storage.delete('${runtimeType.toString()}$id');
}
//Bloc state攔截,和保存
class HydratedBlocDelegate extends BlocDelegate { ...
final HydratedStorage storage;
static Future<HydratedBlocDelegate> build({
Directory storageDirectory,
}) async {
return HydratedBlocDelegate(
await HydratedBlocStorage.getInstance(storageDirectory: storageDirectory),
);
}
/// {@macro hydratedblocdelegate}
HydratedBlocDelegate(this.storage);
//關(guān)鍵步驟
@override
void onTransition(Bloc bloc, Transition transition) {
storage.write('${bloc.runtimeType.toString()}${bloc.id}',
json.encode(stateJson)
//這個(gè)類邏輯單一,只負(fù)責(zé)文件存儲(chǔ),提供一個(gè)存儲(chǔ)的Storage給上面的`HydratedBlocDelegate`
class HydratedBlocStorage implements HydratedStorage {...
static Future<HydratedBlocStorage> getInstance({
Directory storageDirectory,
})
Future<void> delete(String key) async {
Future<void> delete(String key) async {
Future<void> clear() async {
通過上面State部分的代碼可以看出,此類bloc的設(shè)計(jì)主要是對(duì)Bloc的initialState和translation攔擊來自動(dòng)保存數(shù)據(jù),默認(rèn)提供了一個(gè)key,如果有多個(gè)不同的state則需使用多個(gè)state.這種寫法其實(shí)對(duì)性能有一定影響僵控。因?yàn)樗腔跔顟B(tài)值實(shí)施存儲(chǔ)的,所以這就需要我們做一些額外的優(yōu)化痕钢。
如果項(xiàng)目足夠龐大,在初始化時(shí)則會(huì)有大量的bloc緩存加載,這樣勢必會(huì)加重Flutter I/O讀取的速度,拖慢啟動(dòng)速度,而且不同的bloc之間有依賴關(guān)系,我們還需要保證他們的初始化順序完全同步,這就要求我們從業(yè)務(wù)上盡可能的對(duì)bloc的依賴層級(jí)進(jìn)行解耦,分不同的優(yōu)先等級(jí)分批初始化;
另外盡量不要采用多個(gè)State狀態(tài)緩存,不管是從代碼管理,狀態(tài)傳值都會(huì)顯得非常長不便;
此外為了緩解項(xiàng)目類同時(shí)過多的translation并發(fā)讀寫,可以對(duì)其進(jìn)行擴(kuò)展綁定bloc的dispose事件,標(biāo)記state之后再慢慢緩存(以內(nèi)存空間的部分緩存來解決磁盤空間的頻繁緩存)图柏。
Bloc獲取網(wǎng)絡(luò)數(shù)據(jù)
需要提前注冊(cè)Repository
,注冊(cè)方式同Bloc
類似,然后將Repository
賦值給bloc,這樣在bloc內(nèi)部的mapEventToState
的方法中就可以根據(jù)不同的event時(shí)間去訪問網(wǎng)絡(luò)請(qǐng)求了。
Bloc中的坑
- Bloc中所有的類型都是采用泛型推導(dǎo)的,這就要求我們?cè)谑褂玫臅r(shí)候不要忘記了指定我們它的類型.
BlocProvider<CustomBloc>.of(context);
BlocBuilder<CustomBloc,CustomBlocState>(builder: (context, state) { ...
BlocListener<CustomBloc,CustomBlocState>(listener: (preState, currentState){ ...
- BlocPovider作為Bloc載體,Bloc數(shù)據(jù)的存儲(chǔ)會(huì)最終通過InheritedWidget存儲(chǔ)在它當(dāng)前所注冊(cè)的作用閾中,如果我們?cè)诓煌淖饔糜蛳伦?cè)Bloc,那么它將會(huì)被低級(jí)的作用域覆蓋,因?yàn)榇藭r(shí)WidgetTree中插入了2個(gè)不同層級(jí)的
InheritedWidget<CustomBloc>
,
如果想讓他們共享統(tǒng)一個(gè)bloc,就必須要保證InheritedWidget
所關(guān)聯(lián)的Bloc是同一個(gè)value.根據(jù)前面對(duì)Bloc創(chuàng)建提到的2個(gè)類可以看出,Bloc創(chuàng)建分2中方法:
-
一種是通過SingleValueDelegateState直接傳入Bloc
,這種情況會(huì)將Bloc由上自下設(shè)為全部共享,但是有個(gè)弊端,會(huì)導(dǎo)致最上層的Widget移除后,bloc仍然會(huì)繼續(xù)訂閱,除非我們手動(dòng)dispose,容易存在潛在的內(nèi)存泄漏風(fēng)險(xiǎn),一般只介意在整個(gè)FlutterApp生命周期全部都需要使用的單利才可使用此方法
*另一種是通過
BuilderStateDelegate來創(chuàng)建Bloc,它通過一個(gè)構(gòu)造函數(shù)
create(BuildContext context) =>BlocProvider<CustomBloc>(context)`,需要注意的是后面的Bloc需要從上游已注冊(cè)的Bloc中獲取任连。
3.提到坑這里最近發(fā)掘了一個(gè)由FutureBuilder
引起的bug,
在RootWidget下initialState中初始化了很多全局單例的bloc,然后在該widget的builder中使用了FutureBuilder異步構(gòu)建BlocProvider對(duì)象,BlocProvider對(duì)象蚤吹。但是在每次熱重載的時(shí)候發(fā)現(xiàn)了所有的bloc全部被管理,最后排查了Bloc的作用域,自定義實(shí)現(xiàn)BlocProvider斷點(diǎn)分析,bloc創(chuàng)建和初始化均無異常,排查FutureBuilder,它根據(jù)初始化的future生成done和其他的狀態(tài)的widget,由于是啟動(dòng)入口随抠,一般只會(huì)初始化一次,所以一開始沒太注意,但最終發(fā)現(xiàn)運(yùn)來是因?yàn)?個(gè)bugfix間接導(dǎo)致, 第一個(gè)是由于ios平臺(tái)1.17渲染異常,加上了后臺(tái)靜止切換設(shè)置setState來避免UI渲染部分缺失的問題裁着,所以在某些情況下這個(gè)FutureBuilder并不是真正的第一次創(chuàng)建,理論上來說只要這個(gè)future沒變應(yīng)該也不會(huì)出現(xiàn)另外一個(gè)rootWidget重新創(chuàng)建,然而恰哈就是因?yàn)橹安蹲絝utureBuilder傳遞的future(它是項(xiàng)目core service初始化的所有異步task的合并)在catchError的鬼使神差下,每次rootWidget構(gòu)建都會(huì)觸發(fā)futureBuilde的重新生成另外一個(gè)wiget重建,這樣所有的BlocProvider都是移除后重建,就出現(xiàn)了類似的問題.
總結(jié)之后得出2點(diǎn)結(jié)論:
FutureBuilder作用域下面
總結(jié)
Bloc框架目前已有5.6k左右,這種簡單的固定格式的寫法非常適合快速構(gòu)建項(xiàng)目架構(gòu),由于它是基于stream來實(shí)現(xiàn)的,所以使用中要特別注意訂閱的銷毀,避免出現(xiàn)內(nèi)存的泄漏,和冗余的訂閱拱她。它的數(shù)據(jù)傳遞利用了InheritedWidget的局部狀態(tài)刷新來完成層,相比使用常規(guī)的stateState
來刷新獲取數(shù)據(jù)會(huì)更搞效率,這一點(diǎn)上也基本上參照了Flutter官方的設(shè)計(jì),比如ThemeData
,Localization
,MediaQuery
等全局?jǐn)?shù)據(jù)的獲取.它局部Widget構(gòu)建BlocBuilder
依然采用的setState方式構(gòu)建,因此我們需要盡量避免在BlocBuilder
過多的業(yè)務(wù)邏輯處理,和不必要widget渲染,盡量把靜態(tài)的widget移出去,業(yè)務(wù)邏輯我們可以采用BlocListener
對(duì)BlocBuilder
包裹,來優(yōu)化代碼結(jié)構(gòu),和減少BlocBuilder的次數(shù).