簡(jiǎn)介
業(yè)務(wù)開發(fā)中經(jīng)常會(huì)碰到這樣的情況,多個(gè)Widget需要同步同一份全局?jǐn)?shù)據(jù)给涕,比如點(diǎn)贊數(shù)、評(píng)論數(shù)额获、夜間模式等等够庙。在安卓中,一般的實(shí)現(xiàn)方式是觀察者模式抄邀,需要開發(fā)者自行實(shí)現(xiàn)并維護(hù)觀察者的列表耘眨。在flutter中,原生提供了用于Widget間共享數(shù)據(jù)的InheritedWidget境肾,當(dāng)InheritedWidget發(fā)生變化時(shí)剔难,它的子樹中所有依賴了它的數(shù)據(jù)的Widget都會(huì)進(jìn)行rebuild,這使得開發(fā)者省去了維護(hù)數(shù)據(jù)同步邏輯的麻煩准夷。
使用范例
先來看下官方注釋中的范例:
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
@required this.color,
@required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.inheritFromWidgetOfExactType(FrogColor);
}
@override
bool updateShouldNotify(FrogColor old) => color != old.color;
}
實(shí)現(xiàn)比較簡(jiǎn)單钥飞,直接繼承InheritedWidget即可,類中的color即為需要共享的數(shù)據(jù)衫嵌。
此外需要實(shí)現(xiàn)updateShouldNotify
方法读宙,當(dāng)InheritedWidget被更新時(shí),該方法會(huì)被調(diào)用楔绞,并傳入更新之前的Widget對(duì)象结闸,該方法內(nèi)需要實(shí)現(xiàn)新舊數(shù)據(jù)的比較唇兑,若數(shù)據(jù)不同需要通知依賴的Widget更新,則返回true桦锄。
使用時(shí)扎附,可以通過FrogColor.of(context).color
獲取到它的值。
下面舉一個(gè)具體的例子结耀,簡(jiǎn)單的使用場(chǎng)景如下圖所示:
將最上層的Widget用InheritedWidget包裝起來留夜,并設(shè)置顏色為綠色,子Widget中需要使用該顏色時(shí)調(diào)用FrogColor.of(context).color
來獲取图甜,如圖中的Icon和Image碍粥,此時(shí)調(diào)用該方法獲取到的顏色即為綠色。當(dāng)InheritedWidget的數(shù)據(jù)有變化黑毅,即通過setState
將綠色改為紅色后嚼摩,依賴了該數(shù)據(jù)的Icon和Image會(huì)自動(dòng)rebuild,構(gòu)造新的Widget時(shí)矿瘦,此時(shí)從FrogColor.of(context).color
獲取到的顏色也會(huì)變?yōu)榧t色枕面,由此便實(shí)現(xiàn)了數(shù)據(jù)的同步。
源碼分析
InheritedWidget
先來看下InheritedWidget的源碼:
abstract class InheritedWidget extends ProxyWidget {
const InheritedWidget({ Key key, Widget child })
: super(key: key, child: child);
@override
InheritedElement createElement() => new 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
class InheritedElement extends ProxyElement {
InheritedElement(InheritedWidget widget) : super(widget);
@override
InheritedWidget get widget => super.widget;
// 這個(gè)Set記錄了所有依賴的Element
final Set<Element> _dependents = new HashSet<Element>();
// 該方法會(huì)在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 Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = new HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = new HashMap<Type, InheritedElement>();
_inheritedWidgets[widget.runtimeType] = this;
}
// 該方法在父類ProxyElement中調(diào)用衬衬,看名字就知道是通知依賴方該進(jìn)行更新了,這里首先會(huì)調(diào)用重寫的updateShouldNotify方法是否需要進(jìn)行更新改橘,然后遍歷_dependents列表并調(diào)用didChangeDependencies方法滋尉,該方法內(nèi)會(huì)調(diào)用mardNeedsBuild,于是在下一幀繪制流程中飞主,對(duì)應(yīng)的Widget就會(huì)進(jìn)行rebuild狮惜,界面也就進(jìn)行了更新
@override
void notifyClients(InheritedWidget oldWidget) {
if (!widget.updateShouldNotify(oldWidget))
return;
for (Element dependent in _dependents) {
dependent.didChangeDependencies();
}
}
}
其中_updateInheritance
方法在基類Element中的實(shí)現(xiàn)如下:
void _updateInheritance() {
_inheritedWidgets = _parent?._inheritedWidgets;
}
總結(jié)來說就是Element在mount的過程中,如果不是InheritedElement碌识,就簡(jiǎn)單的將緩存指向父節(jié)點(diǎn)的緩存碾篡,如果是InheritedElement,就創(chuàng)建一個(gè)緩存的副本筏餐,然后將自身添加到該副本中开泽,這樣做會(huì)有兩個(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ù)的過程,對(duì)于InheritedWidget有一個(gè)通用的約定就是添加static的of方法辅髓,該方法中通過inheritFromWidgetOfExactType
找到parent中對(duì)應(yīng)類型的的InheritedWidget的實(shí)例并返回泣崩,與此同時(shí),也將自己注冊(cè)到了依賴列表中洛口,該方法的實(shí)現(xiàn)位于Element類律想,實(shí)現(xiàn)如下:
@override
InheritedWidget inheritFromWidgetOfExactType(Type targetType) {
// 這里通過上述mount過程中建立的HashMap緩存找到對(duì)應(yīng)類型的InheritedElement
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
// 這個(gè)列表記錄了當(dāng)前Element依賴的所有InheritedElement,用于在當(dāng)前Element deactivate時(shí)绍弟,將自己從InheritedElement的_dependents列表中移除技即,避免不必要的更新操作
_dependencies ??= new HashSet<InheritedElement>();
_dependencies.add(ancestor);
// 這里將自己添加到了_dependents列表中,相當(dāng)于注冊(cè)了監(jiān)聽
ancestor._dependents.add(this);
return ancestor.widget;
}
_hadUnsatisfiedDependencies = true;
return null;
}
到此InheritedWidget就分析完了樟遣,相比觀察者模式而叼,使用它是不是方便了很多呢?