本文收錄於
張風捷特烈
的公衆號編程之王
: 文章內存地址f-s-p-01
,
如何獲取更多知識乾糧
,詳見<<編程之王食用規範1.0>>
編程
Flutter的狀態管理三足鼎立,明媒正室當
Provider
莫屬,可謂劉備級別的大佬,名正言順。做爲一個喜歡偷懶的人,能省則省。都知道Provider有一把梭,打遍天下無敵手。不過刷這兩招,可要悠着點,不然代價就是性能。性能優化
Provider.of<XXX>(context).數據
Provider.of<XXX>(context).方法
複製代碼
頁面以下,第一個界面是四個色塊,點擊藍色字時跳到紫色界面
這裏進行了五次操做:狀態同步實現,貌似表面上
完美無瑕,並且一把梭就OK了,也很方便,BUT!!!----往下翻。bash
1:打開界面
2:點擊按鈕,+1
3:點擊藍塊文字,跳轉界面
4:點擊紫塊,觸發方法
5:返回
複製代碼
class CountState with ChangeNotifier {
int _count = 0;
get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
void main() {
final count = CountState();
runApp(MultiProvider(
providers: [ChangeNotifierProvider.value(value: count)],
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primaryColor: Colors.blue,
),
home: new HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Wrap(
spacing: 10,
runSpacing: 10,
children: <Widget>[
RedBox(),YellowBox(),BlueBox(),GreenBox(),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Provider.of<CountState>(context).increment();
},
child: Icon(Icons.add),
)
);
}
}
class RedBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("---------RedBox---------build---------");
return Container(
color: Colors.red,
width: 150,
height: 150,
alignment: Alignment.center,
child: Text("Red:${Provider.of<CountState>(context).count}",
style: TextStyle(fontSize: 20),),
);
}
}
class YellowBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("---------YellowBox---------build---------");
return Container(
color: Colors.yellow,
width: 150,
height: 150,
alignment: Alignment.center,
child: Text("Yellow:${Provider.of<CountState>(context).count}",
style: TextStyle(fontSize: 20)),
);
}
}
class BlueBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("---------BlueBox---------build---------");
return Container(
color: Colors.blue,
width: 150,
height: 150,
alignment: Alignment.center,
child: InkWell(
onTap:() {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => NextPage()));
},
child: Text("Blue:${Provider.of<CountState>(context).count}",
style: TextStyle(fontSize: 20)),
),
);
}
}
class GreenBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("---------GreenBox---------build---------");
return Container(
color: Colors.green,
width: 150,
height: 150,
alignment: Alignment.center,
child: Text("GreenBox:${Provider.of<CountState>(context).count}",
style: TextStyle(fontSize: 20)),
);
}
}
class NextPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("---------NextPage---------build---------");
return Scaffold(
body: Center(
child: InkWell(
onTap: (){
Provider.of<CountState>(context).increment();
},
child: Container(
color: Colors.purple,
width: 150,
height: 150,
alignment: Alignment.center,
child: Text("NextPage:${Provider.of<CountState>(context).count}",
style: TextStyle(fontSize: 20))),
)),
);
}
}
複製代碼
打印的日誌讓我一身冷汗。不知道有多少人爲了方便濫用這一把梭。
可見這裏在跳轉時、五個組件所有觸發build,在第二個頁面(紫塊)執行方法時竟五個組件所有觸發build
,其中四個都是不可見的組件,build何用?微信
---->[1.打開時]----
I/flutter (24913): ---------RedBox---------build---------
I/flutter (24913): ---------YellowBox---------build---------
I/flutter (24913): ---------BlueBox---------build---------
I/flutter (24913): ---------GreenBox---------build---------
---->[2.點擊+號,觸發方法]----
I/flutter (24913): ---------RedBox---------build---------
I/flutter (24913): ---------YellowBox---------build---------
I/flutter (24913): ---------BlueBox---------build---------
I/flutter (24913): ---------GreenBox---------build---------
---->[3.點擊藍塊文字,跳轉界面]----
I/flutter (24913): ---------NextPage---------build---------
I/flutter (24913): ---------RedBox---------build---------
I/flutter (24913): ---------YellowBox---------build---------
I/flutter (24913): ---------BlueBox---------build---------
I/flutter (24913): ---------GreenBox---------build---------
---->[4.點擊紫塊,觸發方法]----
I/flutter (24913): ---------NextPage---------build---------
I/flutter (24913): ---------GreenBox---------build---------
I/flutter (24913): ---------BlueBox---------build---------
I/flutter (24913): ---------YellowBox---------build---------
I/flutter (24913): ---------RedBox---------build---------
---->[5.返回]----
I/flutter (24913): ---------RedBox---------build---------
I/flutter (24913): ---------YellowBox---------build---------
I/flutter (24913): ---------BlueBox---------build---------
I/flutter (24913): ---------GreenBox---------build---------
複製代碼
這時候使用Consumer包裹須要更新的節點。
將四個色塊處理
框架
class HomePage extends StatelessWidget {
//英雄所見...
floatingActionButton://使用Consumer包裹
Consumer<CountState>(builder: (ctx,state,child)=>FloatingActionButton(
onPressed: () {
state.increment();//使用其內的state執行方法
},
child: Icon(Icons.add),
))
);
}
}
class RedBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("---------RedBox---------build---------");
return Container(
color: Colors.red,
width: 150,
height: 150,
alignment: Alignment.center,
child: Consumer<CountState>(builder: (ctx,state,child)=>
Text("Red:${state.count}",
style: TextStyle(fontSize: 20),)),
);
}
}
//其餘三個處理相似,略...
複製代碼
如今再來看看打印結果,什麼是世界瞬間清淨了許多。
Consumer能夠指定小塊的局部消費,避免總體的的所有刷新less
---->[1.打開時]----
I/flutter (24913): ---------RedBox---------build---------
I/flutter (24913): ---------YellowBox---------build---------
I/flutter (24913): ---------BlueBox---------build---------
I/flutter (24913): ---------GreenBox---------build---------
---->[2.點擊+號,觸發方法]----
無打印信息
---->[3.點擊藍塊文字,跳轉界面]----
I/flutter (26468): ---------NextPage---------build---------
---->[4.點擊紫塊,觸發方法]----
I/flutter (26468): ---------NextPage---------build---------
---->[5.返回]----
無打印信息
複製代碼
你也許會說,乖乖,這麼秀,都不用build了?但不吃飯是長不胖的...且靜看下文。ide
源碼第一句說的很清楚:
從先祖獲取Provider<T>而後傳遞給builder出的組件
原本代代相承的傳家寶直接經過Consumer隔代傳送。不作那些花裏胡哨的傳遞。性能
目的有2:
其一:當沒有BuildContext時可使用Consumer
@override // ERROR:ProviderNotFoundError 由於該context中並無Provider
Widget build(BuildContext context) {
return ChangeNotifierProvider(
builder: (_) => Foo(),
child: Text(Provider.of<Foo>(context).value),
);
}
@override // OK
Widget build(BuildContext context) {
return ChangeNotifierProvider(
builder: (_) => Foo(),
child: Consumer<Foo>(
builder: (_, foo, __) => Text(foo.value),
},
);
}
其二:它經過更細粒度的重構來幫助性能優化。
複製代碼
經過上面可知實際上是建立有構建組件的,只不過是局部構建
這樣可讓構建的粒度變細,天然避免了沒必要要的過程,能夠在Builder裏打印來測試一下也就是說,構建的只是知識一個Text而非整個RedBox。測試
class RedBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("---------RedBox---------build---------");
return Container(
color: Colors.red,
width: 150,
height: 150,
alignment: Alignment.center,
child: Consumer<CountState>(builder: (ctx,state,child){
print("---------RedBox----Consumer-----build---------");
return Text("Red:${state.count}",
style: TextStyle(fontSize: 20),);
}),
);
}
}
複製代碼
那他是如何實現的呢?一共也不到20行。它繼承自StatelessWidget
能夠看出有三個字段:key、child和、builder,其中builder是一個三參的方法 既然是StatelessWidget,build方法天然跑不了。可見該方法是由 builder方法全權負責的。T泛型就是狀態模型,這裏也是經過Provider.of<T>(context),
來拿到的。優化
class Consumer<T> extends StatelessWidget
implements SingleChildCloneableWidget {
Consumer({
Key key,
@required this.builder,
this.child,
}) : assert(builder != null),
super(key: key);
final Widget child;
final Widget Function(BuildContext context, T value, Widget child) builder;
@override
Widget build(BuildContext context) {
return builder(
context,
Provider.of<T>(context),
child,
);
}
@override
Consumer<T> cloneWithChild(Widget child) {
return Consumer(
key: key,
builder: builder,
child: child,
);
}
}
複製代碼
那問題來了?傳入的context是誰的BuildContext? 衆所周知,每一個Widget都有屬於本身的元素Element,在該Element進行mount的時候回將自身化做美麗的天使(Context)傳入組件或State的build方法中來供你使用。這裏分別在
頂層MyApp的build
、頁面HomePage的build``紅色的build
、紅色Consumer內部
打上斷點,來一窺這四個小天使的容顏。
因此BuildContext並非咱們想象中的,什麼代代相傳的東西。而是每一個Widget特有的存在,就像他們的基因同樣,在每一個Widget裏都是不一樣的。因此咱們的問題很簡單,Consumer做爲一個Widget,它提供的context即是Consumer的context。下面看一下這幾個小天使在界面的Element樹上的位置。
再強調一下,Element是實現BuildContext抽象接口協議的具象類,Widget或State中Build傳入的BuildContext都是各自的組件對應的Element。每一個Element都會記錄它們的父親,就像這樣,按照一個BuildContext(即Element),你能夠找到它的祖宗18代,應該是祖宗108代。雖然Widget僞樹
很是簡短,但Element樹並不想你想象的那樣。
也許你會好奇,最頂級的元總是誰?那咱們就偷瞄一下,誰是天使之王。
框架在開始是會建立一個 RenderObjectToWidgetElement,她即是一切美麗的根源。
緊接着即是Provider提供的MultiProvider ,咱們的MyApp還要後兩輩。
Consumer何德何能,居然直接越過父親? 遇事不決,量子力學,把哥的debug量子炮拿來
在點擊時觸發方法是打個斷點,來走一波。
斷點處: 3個
state.increment()
紅色Consumer內部
buildScope方法
當第一次點擊按鈕時:
buildScope 中髒表元素 1 ,爲按鈕元素:RawMaterialButton(dirty)
接下來斷點走到state.increment();開始觸發通知更新
會走到buildScope,髒表數爲3
ListenableProvider<CountState>(dirty, state: _DelegateWidgetState#ae650)
RawMaterialButton(dirty, state: _RawMaterialButtonState#80335)
_MaterialInterior(duration: 200ms, shape: CircleBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none)), elevation: 12.0, c
斷點方行,來到[紅色Consumer內部]斷點,此時控制檯已經顯示:
I/flutter (13300): ---------GreenBox----Consumer-----build---------
I/flutter (13300): ---------YellowBox----Consumer-----build---------
再次方行此時控制檯已經顯示:
I/flutter (13300): ---------RedBox----Consumer-----build---------
I/flutter (13300): ---------BlueBox----Consumer-----build---------
此時界面已更新。但按鈕還沒緩過神
會走到buildScope,髒表數爲1,
_MaterialInterior(duration: 200ms,
再放行,按鈕更新,一次界面的點擊刷新完成。
複製代碼
如今看來惟一的關注點是ListenableProvider這個東東,何許人也?
在這幅圖中已經浮現大佬的身姿了,它老爹是MutiProvider。
在buildScope中,咱們的故事便發生在ListenableProvider的rebuild方法裏
進入後咱們到達
Element#rebuild()=> ComponentElement#performRebuild()
看到InheritedProvider,我也就會心一笑了。就快打完收工了。
rebuild一波後,髒表加了5個,每錯,都是Consumer的節點。只要四個塊,爲何有5個?
百思不得其解,最後一句TMD,按鈕上也加了Consumer,被本身蠢死。
衆所周知,Flutter只會繪製重建髒表裏的元素。因此會直接構建Consumer而非總體。
沒有對比就沒有傷害,最後看一下不用Consumer時重構頁面的髒表狀況。在rebuild一波後髒表加入的是整個Widget的元素。
就這樣,因此層次較深時,推薦使用Consumer來將更新的粒度變小。
本文到此接近尾聲了,若是想快速嚐鮮Flutter,《Flutter七日》會是你的必備佳品;若是想細細探究它,那就跟隨個人腳步,完成一次Flutter之旅。
另外本人有一個Flutter微信交流羣,歡迎小夥伴加入,共同探討Flutter的問題,本人微信號:zdl1994328
,期待與你的交流與切磋。另外歡迎關注公衆號編程之王