最近公司做技術(shù)分享寫的文章的demo
創(chuàng)新互聯(lián)建站主要從事網(wǎng)頁設(shè)計(jì)、PC網(wǎng)站建設(shè)(電腦版網(wǎng)站建設(shè))、wap網(wǎng)站建設(shè)(手機(jī)版網(wǎng)站建設(shè))、自適應(yīng)網(wǎng)站建設(shè)、程序開發(fā)、網(wǎng)站優(yōu)化、微網(wǎng)站、小程序定制開發(fā)等,憑借多年來在互聯(lián)網(wǎng)的打拼,我們在互聯(lián)網(wǎng)網(wǎng)站建設(shè)行業(yè)積累了豐富的成都網(wǎng)站建設(shè)、網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)、網(wǎng)絡(luò)營銷經(jīng)驗(yàn),集策劃、開發(fā)、設(shè)計(jì)、營銷、管理等多方位專業(yè)化運(yùn)作于一體。
Flutter中的InheritedWidget狀態(tài)管理
1.InheritedWidget是什么?
InheritedWidget是Flutter中非常重要的一個(gè)功能型組件,它提供了一種數(shù)據(jù)在widget樹中從上到下傳遞、共享的方式,比如我們在應(yīng)用的根widget中通過InheritedWidget共享了一個(gè)數(shù)據(jù),那么我們便可以在任意子widget中來獲取該共享的數(shù)據(jù)!這個(gè)特性在一些需要在widget樹中共享數(shù)據(jù)的場景中非常方便!如Flutter SDK中正是通過InheritedWidget來共享應(yīng)用主題(Theme)和Locale (當(dāng)前語言環(huán)境)信息的。
InheritedWidget和React中的context功能類似,和逐級傳遞數(shù)據(jù)相比,它們能實(shí)現(xiàn)組件跨級傳遞數(shù)據(jù)。InheritedWidget的在widget樹中數(shù)據(jù)傳遞方向是從上到下的,這和通知Notification的傳遞方向正好相反。
2.源碼分析
InheritedWidget
先來看下InheritedWidget的源碼:
abstract class?InheritedWidget?extends?ProxyWidget { ??const?InheritedWidget({ Key key,?Widget child }):?super(key: key,?child: child);??@override??InheritedElement?createElement() =InheritedElement(this);??@protected??bool?updateShouldNotify(covariant?InheritedWidget oldWidget);}
它繼承自ProxyWidget:
abstract class?ProxyWidget?extends?Widget { ??const?ProxyWidget({ Key key,?@required?this.child?}) :?super(key: key);??final?Widget?child;}
可以看出Widget內(nèi)除了實(shí)現(xiàn)了createElement方法外沒有其他操作了,它的實(shí)現(xiàn)關(guān)鍵一定就是InheritedElement了。
InheritedElement 來看下InheritedElement源碼
class?InheritedElement?extends?ProxyElement { ??InheritedElement(InheritedWidget widget) :?super(widget);??@override??InheritedWidget?get?widget?=?super.widget;??// 這個(gè)Set記錄了所有依賴的Elementfinal?MapElement,?Object?_dependents?=?HashMapElement,?Object();
//該方法會在Element mount和activate方法中調(diào)用,_inheritedWidgets為基類Element中的成員,用于提高Widget查找父節(jié)點(diǎn)中的InheritedWidget的效率,它使用HashMap緩存了該節(jié)點(diǎn)的父節(jié)點(diǎn)中所有相關(guān)的InheritedElement,因此查找的時(shí)間復(fù)雜度為o(1) ??@override??void?_updateInheritance() {final?MapType,?InheritedElement incomingWidgets =?_parent?._inheritedWidgets;if?(incomingWidgets !=?null)??????_inheritedWidgets?=?HashMapType,?InheritedElement.from(incomingWidgets);????else??????_inheritedWidgets?=?HashMapType,?InheritedElement();????_inheritedWidgets[widget.runtimeType] =?this;??}
//該方法在父類ProxyElement中調(diào)用,看名字就知道是通知依賴方該進(jìn)行更新了,這里首先會調(diào)用重寫的updateShouldNotify方法是否需要進(jìn)行更新,然后遍歷_dependents列表并調(diào)用didChangeDependencies方法,該方法內(nèi)會調(diào)用mardNeedsBuild,于是在下一幀繪制流程中,對應(yīng)的Widget就會進(jìn)行rebuild,界面也就進(jìn)行了更新 ??@override??void?notifyClients(InheritedWidget oldWidget) {????assert(_debugCheckOwnerBuildTargetExists('notifyClients'));for?(Element dependent?in?_dependents.keys) {??????notifyDependent(oldWidget,?dependent);????}??}
其中_updateInheritance方法在基類Element中的實(shí)現(xiàn)如下:
void _updateInheritance() {
_inheritedWidgets = _parent?._inheritedWidgets;
}
總結(jié)來說就是Element在mount的過程中,如果不是InheritedElement,就簡單的將緩存指向父節(jié)點(diǎn)的緩存,如果是InheritedElement,就創(chuàng)建一個(gè)緩存的副本,然后將自身添加到該副本中,這樣做會有兩個(gè)值得注意的點(diǎn):
InheritedElement的父節(jié)點(diǎn)們是無法查找到自己的,即InheritedWidget的數(shù)據(jù)只能由父節(jié)點(diǎn)向子節(jié)點(diǎn)傳遞,反之不能。
如果某節(jié)點(diǎn)的父節(jié)點(diǎn)有不止一個(gè)同一類型的InheritedWidget,調(diào)用inheritFromWidgetOfExactType獲取到的是離自身最近的該類型的InheritedWidget。
看到這里似乎還有一個(gè)問題沒有解決,依賴它的Widget是在何時(shí)被添加到_dependents這個(gè)列表中的呢?
回憶一下從InheritedWidget中取數(shù)據(jù)的過程,對于InheritedWidget有一個(gè)通用的約定就是添加static的of方法,該方法中通過inheritFromWidgetOfExactType找到parent中對應(yīng)類型的的InheritedWidget的實(shí)例并返回,與此同時(shí),也將自己注冊到了依賴列表中,該方法的實(shí)現(xiàn)位于Element類,實(shí)現(xiàn)如下:
@overrideT?dependOnInheritedWidgetOfExactType
// 這里通過上述mount過程中建立的HashMap緩存找到對應(yīng)類型的InheritedElement final?InheritedElement ancestor =?_inheritedWidgets?==?null???null?:?_inheritedWidgets[T];if?(ancestor !=?null) {????assert(ancestor?is?InheritedElement);return?dependOnInheritedElement(ancestor,?aspect: aspect);??}??_hadUnsatisfiedDependencies?=?true;??return null;}
@overrideInheritedWidget?dependOnInheritedElement(InheritedElement ancestor,?{ Object aspect }) {??assert(ancestor !=?null);
// 這個(gè)列表記錄了當(dāng)前Element依賴的所有InheritedElement,用于在當(dāng)前Element deactivate時(shí),將自己從InheritedElement的_dependents列表中移除,避免不必要的更新操作 ??_dependencies???=?HashSetInheritedElement();??_dependencies.add(ancestor);??ancestor.updateDependencies(this,?aspect);return?ancestor.widget;}
3.如何使用InheritedWidget
1)、創(chuàng)建一個(gè)類繼承自Inheritedwidget
class?InheritedContext?extends?InheritedWidget{??final?InheritedTestModel?inheritedTestModel;??InheritedContext({????Key key,????@required?this.inheritedTestModel,????@required?Widget child}):?super(key: key,?child: child);static?InheritedContext? of (BuildContext context) {????return?context.dependOnInheritedWidgetOfExactTypeInheritedContext();??}??@override??bool?updateShouldNotify(InheritedContext oldWidget) {????return?inheritedTestModel?!= oldWidget.inheritedTestModel;??}}
2)、InheritedTestModel類為數(shù)據(jù)容器(這里定義了一個(gè)Listint數(shù)據(jù)源)
class?InheritedTestModel{?final?List?_list;??InheritedTestModel(this._list);??List?getList(){????return?_list;??}}
class?ArrayListData{??static?List? _list ;static?List? getListData (){???? _list? =?new?List();???? _list .add(1);???? _list .add(2);???? _list .add(3);???? _list .add(4);return? _list ;??}}
3)、定義一個(gè)Widget?使用?InheritedContext類的數(shù)據(jù)?InheritedTestModel?
class?ListDemo?extends?StatefulWidget{??@override??State?createState() {????return new?ListDemoState();??}}class?ListDemoState?extends?StateListDemo{List?_list;??InheritedTestModel?_inheritedTestModel;??Timer?_timer;??Duration?oneSec?=?const?Duration(seconds:?1);??@override??void?initState() {????_list?= ArrayListData. getListData ();????_inheritedTestModel?=?new?InheritedTestModel(_list);????_timer?=?Timer.periodic(oneSec,?(timer) {??????_doTimer();????});??}??void?_doTimer() {????for(int i =?0;?i ?_list.length;?i++){??????_list[i] =?_list[i]+?1;????}??setState(() {????_inheritedTestModel?=?new?InheritedTestModel(_list);??});??}Widget?_buildBody() {????return?Container(child:?ListDemo2(),????);??}??@override??Widget?build(BuildContext context) {????return?InheritedContext(inheritedTestModel:?_inheritedTestModel,??????child:?Scaffold(appBar:?AppBar(title:?Text("ListDemo"),????????actions: Widget[????????????IconButton(icon:?Icon(Icons. add ),????????????)????????],),????????body: _buildBody(),??????),????);??}??@override??void?dispose() {????super.dispose();if?(_timer?!=?null) {??????_timer.cancel();????}??}}
4)、在ListDemo中通過Timer更新InheritedTestModel?中的數(shù)據(jù),然后再下一個(gè)Widget中獲取更新的數(shù)據(jù)作為展示
class?ListDemo2?extends?StatefulWidget{??@override??State?createState() {????return new?ListDemoState2();??}}class?ListDemoState2?extends?StateListDemo2{InheritedTestModel?_inheritedTestModel;??Widget?_buildListItem(BuildContext context,int index) {????return ?Container(height:?50,????????width:?100,????????alignment: Alignment. center ,????????child:?Text(_inheritedTestModel.getList()[index].toString()),????);??}Widget?_buildBody() {????_inheritedTestModel?= InheritedContext. of (context).inheritedTestModel;return?Container(child:?ListView.builder(itemBuilder:(context,?index)=_buildListItem(context,index),itemCount:?_inheritedTestModel.getList().length,),????);??}??@override??Widget?build(BuildContext context) {????return ?_buildBody();??}}
這樣就可以在父widget中更新數(shù)據(jù),子View不需任何操作直接從數(shù)據(jù)容器InheritedTestModel?中獲取到更新后的新數(shù)據(jù)
這是一個(gè)數(shù)據(jù)共享的簡單的例子,基本的流程,大致就是A去更新B的數(shù)據(jù),A和B有一個(gè)共同的父類,實(shí)現(xiàn)數(shù)據(jù)的共享
4.上面說了原理和基本的使用,但是在實(shí)際項(xiàng)目當(dāng)中,我當(dāng)然不建議這樣來使用,Google?已經(jīng)為我們封裝好了功能更加強(qiáng)大的插件Provider,其內(nèi)部原理就是基于InheritedWidget來實(shí)現(xiàn)的,我們理解了基本原理,可以更好的在項(xiàng)目中運(yùn)用Provider
1.環(huán)境準(zhǔn)備, 參考鏈接
2.添加國內(nèi)環(huán)境配置: 參考鏈接
3.新建
name: String類型,代表Channel的名字,也是其唯一標(biāo)識符。
messager:BinaryMessenger類型,代表消息信使,是消息的發(fā)送與接收的工具。
codec: MessageCodec類型或MethodCodec類型,代表消息的編解碼器。
fluuter中的MessageCodec用于二進(jìn)制格式數(shù)據(jù)與基礎(chǔ)數(shù)據(jù)之間的編解碼。BasicMessageChannel所使用的編解碼器就是MessageCodec。
iOS中,名稱為FlutterMessageCodec,是一個(gè)協(xié)議,定義了兩個(gè)方法:encode接收一個(gè)類型為id的消息,將其編碼為NSData類型,而decode接收NSData類型消息,將其解碼為id類型數(shù)據(jù)。
MessageCodec有多種不同的實(shí)現(xiàn):
與MessageCodec不同的是,MethodCodec用于MethodCall對象的編解碼,一個(gè)MethodCall對象代表一次從Flutter端發(fā)起的方法調(diào)用。MethodCall有2個(gè)成員變量:String類型的method代表需要調(diào)用的方法名稱,通用類型(Android中為Object,iOS中為id)的arguments代表需要調(diào)用的方法入?yún)?/p>
由于處理的是方法調(diào)用,故相比于MessageCodec,MethodCodec多了對調(diào)用結(jié)果的處理。當(dāng)方法調(diào)用成功時(shí),使用encodeSuccessEnvelope將result編碼為二進(jìn)制數(shù)據(jù),而當(dāng)方法調(diào)用失敗時(shí),則使用encodeErrorEnvelope將error的code、message、detail編碼為二進(jìn)制數(shù)據(jù)
MethodCodec有兩種實(shí)現(xiàn):
1、繼承SingleChildStatelessWidget,就是一個(gè)widget,通過create 傳入一個(gè)Bloc對象
1、Bloc繼承自BlocBase,BlocBase中創(chuàng)建了StreamController對象,為多訂閱對象
其中onCounterEvent((event, emit)為初始化創(chuàng)建_eventController監(jiān)聽
2、Bloc中創(chuàng)建_eventController,為事件通知
3、BlocBase創(chuàng)建_stateController,為狀態(tài)刷新通知
4、add方法是執(zhí)行廣播通知
5、處理完數(shù)據(jù)之后執(zhí)行emit()方法,其中emit方法是stateController廣播
1、 BlocBuilder繼承自BlocBuilderBase,_BlocBuilderBaseState中build方法返回的是BlocListener
2、BlocListener繼承BlocListenerBase,_BlocListenerBaseState中_subscribe()添加監(jiān)聽stateController廣播通知
Flutter是谷歌公司推出的跨終端的開發(fā)框架,支持Android、iOS和WEB終端。1.0版在2018年12月5日發(fā)布,目前的最新版本是1.5,它采用的開發(fā)語言是Dart,Dart也是谷歌開發(fā)的計(jì)算機(jī)編程語言,語法類似C,是編譯型語言:
hello world例子,打印字符串“Hello World!”:
1、沒有橋接層
React Native、Weex等技術(shù)都是跨終端的框架,然而性能跟原生App存在很大差距。這是由于它們的工作原理決定的:
React Native、Weex等技術(shù)多了一個(gè)橋接層,所以界面渲染會慢一些,由于UI渲染非常頻繁,想要不卡頓,基本上比較難,性能和用戶體驗(yàn)跟原生代碼有差距。而這恰恰是Flutter的優(yōu)勢所在:
Dart可以被編譯成不同平臺的本地代碼,讓Flutter不通過橋接層直接跟平臺通信,自然性能會快一些。
2、編譯執(zhí)行
JavaScript是解釋執(zhí)行的,Dart是編譯執(zhí)行的,性能誰好一目了然。
3、Flutter Engine虛擬機(jī)
Flutter是依靠Flutter Engine虛擬機(jī)在iOS和Android上運(yùn)行的,F(xiàn)lutter Engine使用C/C++編寫,開發(fā)人員通過Flutter框架直接和API在內(nèi)部進(jìn)行交互,所以具有輸入低延遲和UI渲染高幀速率的特點(diǎn)。除了這特點(diǎn)之外,F(xiàn)lutter還提供了自己的小部件,F(xiàn)lutter小部件是使用從React獲取靈感的現(xiàn)代框架構(gòu)建的。 中心思想是您使用小部件構(gòu)建UI。
窗口小部件根據(jù)其當(dāng)前配置和狀態(tài)描述了它們的視圖。 當(dāng)窗口小部件的狀態(tài)發(fā)生更改時(shí),窗口小部件會重建其描述,框架將根據(jù)前面的描述進(jìn)行區(qū)分,以確定底層呈現(xiàn)樹從一個(gè)狀態(tài)轉(zhuǎn)換到下一個(gè)狀態(tài)所需的最小更改。可以直接在OS平臺提供的畫布上進(jìn)行描繪,也就是一些核心類庫直接放到虛擬機(jī)里面,調(diào)用起來更快。
從它的系統(tǒng)結(jié)構(gòu)可以看出,類似安卓的ART(Android Run Time)虛擬機(jī),同樣采用AOT(Ahead of TIme)技術(shù),會在APP安裝時(shí)就編譯成機(jī)器語言,不再解釋執(zhí)行,從而優(yōu)化了APP運(yùn)行的性能。
4、自帶渲染引擎
Flutter使用谷歌自己的Skia渲染引擎,而Android系統(tǒng)自帶Skia引擎,iOS平臺上Flutter也會把Skia引擎打包到APP中,從而實(shí)現(xiàn)了高效渲染。而React Native通過橋接層訪問原生UI,操作頻繁就容易出性能問題。
綜合所述,F(xiàn)lutter 是性能最接近原生代碼 的一種開發(fā)框架,未來也會是構(gòu)建谷歌Fuchsia應(yīng)用的主要方式,前途不可限量,唯一的問題就是需要學(xué)習(xí)一門新的語言:Dart,而有Java或者C#語言基礎(chǔ)的程序員會比較容易學(xué)習(xí)。
ChangeNotifierProvider、ChangeNotifier、Consumer的關(guān)系
1、ChangeNotifierProvider的父類ListenableProvider,ListenableProvider中實(shí)現(xiàn)了_startListening方法,_startListening主要是將Element的刷新方法添加到ChangeNotifier中的_listeners中
2、ListenableProvider將startListening傳入其父類InheritedProvider,InheritedProvider主要是創(chuàng)建delegate = _CreateInheritedProvider。這個(gè)delegate就是_CreateInheritedProvider中delegate.startListening中的delegate
1、ChangNotifier負(fù)責(zé)更新UI
ChangeNotifier中notifyListeners,通過遍歷_listeners,實(shí)現(xiàn)強(qiáng)制刷新UI
1、Consumer:包裹待刷新UI,在buildWithChild中將Provider.ofT(context)傳入builder方法
2、解析 Provider.ofT(context)
Provider.ofT(context)是獲取ChangeNotifier對象的方法
Provider.of中通過傳入的context,獲取父視圖為inheritedElement的對象
獲取到inheritedElement,通過inheritedElement.getValue的方式獲取ChangeNotifier對象
7、繼續(xù)查看inheritedElement?.value。通過斷點(diǎn)可以進(jìn)入_CreateInheritedProvider 類中g(shù)et Value方法。調(diào)用startListening將當(dāng)前Consumer包裹的element添加到Prorivder的_listeners數(shù)組中
通過調(diào)用notifyListeners()來刷新所有Consumer完整的Provider執(zhí)行流程大概就是這樣,流程圖如下