原始碼篇:Flutter Provider的另一面(萬字圖文+外掛)

小呆呆666發表於2021-05-31

前言

閱讀此文的彥祖,亦菲們,附送一枚Provider模板程式碼生成外掛!

我為啥要寫這個外掛呢?

此事說來話短,我這不準備寫解析Provider原始碼的文章,肯定要寫這框架的使用樣例啊,然後再嗶嗶原始碼呀!在寫demo樣例的時候,新建那倆三個檔案、資料夾和必寫的模板程式碼,這讓我感到很方啊,這不耽誤我時間嘛!然後就擼了這個外掛,相對而言,多花了幾百倍的時間。。。

希望這個外掛,能減輕使用Provider小夥們的一點工作量;外掛裡面的模板程式碼是經過我深思熟慮過的,如果各位靚仔有更好的模板程式碼,請在評論裡貼出來,我覺得合理的話,會加入到外掛裡。

關於Provider的原始碼,如果對設計模式或面向介面程式設計不熟悉的話,看起來是相當懵逼的,基本就是:懵逼樹上懵逼果,懵逼樹下你和我;Provider原始碼使用了大量的抽象類,呼叫父類建構函式,繼承實現斷言,很多關鍵的函式呼叫,點進去都是抽象類,必須返回好幾層去看看這個抽象類的實現類是什麼,看的十分頭大!這裡面有很多設計模式的痕跡:觀察者模式、策略模式、外觀模式、命令模式、訪問者模式、模板模式、迭代器模式、、、

我會竭盡所能的將總體流程說清楚,相關晦澀流程會結合圖文,並給出相應小demo演示

ε=(´ο`*)))唉,這篇文章寫完,我感覺整個人都被掏空了。。。

img

不管你用或不用Provider,我相信在你讀完本文的重新整理機制欄目,大概率會對該框架中閃耀的智慧,感到由衷的讚歎!

使用

老規矩,說原理之前,先來看下使用

Provider的使用,和我前倆篇寫的Handler和ThreadLocal使用有一些區別

Provider是一個狀態管理框架,寫它的使用可能會佔較多篇幅,所以文章整體篇幅也會較長,請見諒。。。

我實在不想分篇幅水贊啊,而且也是為了方便大家可以在一篇文章裡面查閱相關知識(請結合掘金旁邊的大綱食用),也方便我隨時修改優化文章內容。。。

外掛

  • 外掛github:provider_template

    • 使用中碰見什麼bug,希望大家能及時給我提issue
  • 外掛可以進入Android Studio的Setting裡面,選擇Plugins,然後搜尋flutter provider,第一個,看圖上紅框標定的就是了,點選install安裝即可

image-20210521161541895

  • 來下看使用效果圖

provider_plugin

  • 如果你不喜歡這種命名方式,這裡提供修改入口;也支援了持久化
    • 大家按需修改吧

image-20210521162324454

初始寫法

  • 在寫Provider的demo例項的時候,是按照下面這種寫法的,畢竟下面這種寫法,是非常正統且常見的一種寫法
class ProEasyCounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (BuildContext context) => ProEasyCounterProvider(),
      child: _buildPage(context),
    );
  }

  Widget _buildPage(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Provider-Easy範例')),
      body: Center(
        child: Consumer<ProEasyCounterProvider>(
          builder: (context, provider, child) {
            return Text('點選了 ${provider.count} 次',
                style: TextStyle(fontSize: 30.0));
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => Provider.of<ProEasyCounterProvider>(context, listen: false).increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}

class ProEasyCounterProvider extends ChangeNotifier {
  int count = 0;

  void increment() {
    count++;
    notifyListeners();
  }
}
複製程式碼

這地方有個讓我很難受的地方,就是Provider.of這個實在是太長了,但是我如果不使用Provider.of,就需要把Scaffold整體包裹在Consumer裡面,這樣可以直接拿到provider變數使用,,,但是這樣的話,Consumer包裹的模組就有點太大了。。。

而且Provider.of這地方還只是使用了模組內Provider,還不是獲取全域性的Provider,使用頻率肯定很高,都這麼寫而且這麼長,想想就頭皮發麻,我方了呀。。。

img

優化寫法

上面那個Provider.of寫法,讓我巨難受:走在回去的路上想,有什麼方法可以優化呢?洗澡的時候想,有什麼方法可以優化呢?

我轉念一想,我這地方只是寫個使用demo,我特麼有必要這麼糾結嗎?!

但是,我就是糾結的一批啊,一定有什麼方法可以優化!(魔改框架? ...石樂志吧我)

突然靈光一閃!我!看到了光!蓋亞!

既然ChangeNotifierProvider裡面create引數,是接受了我例項化的ChangeNotifier物件,然後它內部存了起來,然後在Consume裡面的builder方法裡面分發給我,那我自己是不是也可把ChangeNotifier物件存起來!

  • 突然間醍醐灌頂,思路就突破了,然後就可以愉快的在這上面玩耍了
class ProEasyCounterPage extends StatelessWidget {
  final provider = ProEasyCounterProvider();

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (BuildContext context) => provider,
      child: _buildPage(),
    );
  }

  Widget _buildPage() {
    return Scaffold(
      appBar: AppBar(title: Text('Provider-Easy範例')),
      body: Center(
        child: Consumer<ProEasyCounterProvider>(
          builder: (context, provider, child) {
            return Text('點選了 ${provider.count} 次',
                style: TextStyle(fontSize: 30.0));
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => provider.increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}



class ProEasyCounterProvider extends ChangeNotifier {
  int count = 0;

  void increment() {
    count++;
    notifyListeners();
  }
}
複製程式碼

Provider.of(context, listen: false).increment() 直接變成 provider.increment()

一個模組裡面,會有很多地方用到provider,這樣一改,瞬間輕鬆很多,而且還不需要傳context了。。。

在這上面我們還能騷!還能簡化!

  • 因為這裡我們直接使用我們自己儲存起來provider,所以可以進一步簡化
    • Consumer進行了簡化,builder方法裡面引數,大部分情況不需要了
    • 我甚至都想把泛型去掉;看了下原始碼,應該很難去掉,泛型在框架內部起到了至關重要的作用
//原版
Consumer<ProEasyCounterProvider>(builder: (context, provider, child) {
    return Text(
        '點選了 ${provider.count} 次',
        style: TextStyle(fontSize: 30.0),
    );   
}),

//簡化
Consumer<ProEasyCounterProvider>(builder: (_, __, ___) {
    return Text(
        '點選了 ${provider.count} 次',
        style: TextStyle(fontSize: 30.0),
    );
}),    
複製程式碼

瀏覽了Provider內部的原始碼後,發現:按照上面這樣寫是完全沒問題!會一定程度上提升效率!

凎!可以把外掛和demo程式碼全改了!搞起!

外掛生成程式碼

外掛生成程式碼分為倆個模式:Default和High

預設模式有倆個檔案(Default):view、provider

高階模式有三個檔案(High):view、provider、state

大家都是用Flutter的老手,對這種結構應該非常瞭解,state層是把資料層獨立出來維護

在非常複雜的提交介面,state層我甚至還會分出:跳轉(jump)、提交(submit)、展示(show)這三種結構;沒辦法,一個模組搞了上百個變數,不這樣分,太難維護了

default:預設模式下的模板程式碼

  • view
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'provider.dart';

class CounterPage extends StatelessWidget {
  final provider = CounterProvider();

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (BuildContext context) => provider,
      child: Container(),
    );
  }
}
複製程式碼
  • provider
import 'package:flutter/material.dart';

class CounterProvider extends ChangeNotifier {

}
複製程式碼

High:高階模式下的模板程式碼

  • view
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'provider.dart';

class CounterPage extends StatelessWidget {
  final provider = CounterProvider();

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (BuildContext context) => provider,
      child: Container(),
    );
  }
}
複製程式碼
  • provider
import 'package:flutter/material.dart';

import 'state.dart';

class CounterProvider extends ChangeNotifier {
  final state = CounterState();
}
複製程式碼
  • state
class CounterState {

  CounterState() {
    // init some variables
  }
}
複製程式碼

前置知識

下面就是Provider的原始碼分析內容了,如果大家趕時間,可以點個贊(方便日後查閱,滑稽.jpg),回頭等有時間,再靜下心來慢慢看;我怕你快餐式閱讀,讀到重新整理機制那塊,會直接罵街,這寫的啥玩意???

Provider的重新整理機制,相關流程相當之繞,我已經竭盡全力,精簡了無數我們不需要關注的程式碼,然後一步步帶著你的思路去走一遍正確的流程,相關類還給了很多說明,但是架不住原始碼流程山路十八彎,繞的一比啊!你如果不用心去看,去體會,會相當煩躁。。。

我已經幫大家熬過最蛋筒的部分,相關繞的流程畫了詳細的圖示,我已經努力了;如果你想知道Provider內部運轉機制,現在就需要你努力了!

ChangeNotifier的單獨使用

ValueListenableBuilder和ValueNotifier可以配套使用,ValueListenableBuilder內部也是一個StatefulWidget,程式碼很簡單,感興趣的可以自己檢視

這個暫且不表,這邊就搞最原始的ChangeNotifier的使用

大家肯定在Provider都寫過繼承ChangeNotifier的程式碼,而且寫的非常多,但是大家知道怎麼單獨使用ChangeNotifier,以達到控制介面變化的效果嗎?

我搜了很多怎麼單獨使用ChangeNotifier的文章,但是基本都是寫配合ChangeNotifierProvider在Provider中使用的,我佛了呀,搜到寥寥無幾的文章,也沒說清楚,怎麼單獨使用;我想這玩意是不是有個單獨XxxWidgetBuild配合使用?但是!我怎麼都找不到,氣抖冷!

我突然想到,TextField控制元件中的TextEditingController用到了ChangeNotifier,總不可能TextField還用Provider吧!我在原始碼裡面一通翻,各種super,abstract,私有變數,看的頭皮發麻,最後終於找到了關鍵程式碼,搞清楚TextField是怎麼使用ChangeNotifier的了,為什麼每次改變TextEditingController的text值,然後在TextField資料框裡的資料也及時改變了,其實最後還是用到setState

TextField中的流程程式碼不貼了,如果貼出來,會相當佔篇幅:我下面會寫一個顆粒度最小ChangeNotifier的單獨使用demo

  • TextEditingController實際是繼承了ValueNotifier,來看下ValueNotifier
class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
  ValueNotifier(this._value);
  @override
  T get value => _value;
  T _value;
  set value(T newValue) {
    if (_value == newValue)
      return;
    _value = newValue;
    notifyListeners();
  }

  @override
  String toString() => '${describeIdentity(this)}($value)';
}
複製程式碼

ValueNotifier實際是對ChangeNotifier的封裝

這裡影響不大,我們還是使用ChangeNotifier,來寫一個類似TextField中的控制器效果,每當控制器中的數值改變,其控制元件內容就自動更新

  • 先使用ChangeNotifier搞一個控制器
class TestNotifierController extends ChangeNotifier {
  String _value = '0';

  String get value => _value;

  set value(String newValue) {
    if (_value == newValue) return;
    _value = newValue;
    notifyListeners();
  }
}
複製程式碼
  • 搭配這個控制器的Widget
    • OK,這樣就搞定了,改變控制器的資料,Widget也會自動重新整理
    • 我把功能顆粒度壓縮的非常小,希望大家閱讀會比較輕鬆
class TestNotifierWidget extends StatefulWidget {
  const TestNotifierWidget({
    Key? key,
    this.controller,
  }) : super(key: key);

  final TestNotifierController? controller;

  @override
  _TestNotifierState createState() => _TestNotifierState();
}

class _TestNotifierState extends State<TestNotifierWidget> {
  @override
  void initState() {
    ///新增回撥 value改變時,自動觸發回撥內容
    widget.controller?.addListener(_change);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Text(
      widget.controller?.value ?? '初始值為空',
      style: TextStyle(fontSize: 30.0),
    );
  }

  ///被觸發的回撥
  void _change() {
    setState(() {});
  }
}
複製程式碼
  • 來看下怎麼使用這個控制元件
    • 使用程式碼已經非常簡單了:onPressed改變了控制器數值內容,TestNotifierWidget控制元件會自動重新整理
class TestNotifierPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final controller = TestNotifierController();
    var count = 0;

    return Scaffold(
      appBar: AppBar(title: Text('ChangeNotifier使用演示')),
      body: Center(
        child: TestNotifierWidget(controller: controller),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          controller.value = '數值變化:${(++count).toString()}';
        },
        child: Icon(Icons.add),
      ),
    );
  }
}
複製程式碼
  • 來看下效果圖

ChangeNotifier演示

Function Call()

這裡說個小知識點,原始碼裡面大量使用了這個技巧,網上搜了下,很少提到這個的,這邊記一筆

每個Function都有個Call()方法

  • 下面倆種方式呼叫是等同的,都能呼叫test方法
void main(){
    test();

    test.call();
}

void test(){
    print('test');
}
複製程式碼

你可能想,這有什麼用,我還多寫一個 .call ?

來看下一個小范例,就知道這個東西能幫我們簡化很多程式碼

  • 平時封裝帶有CallBack回撥Widget
    • 這邊寫了倆個自定義的點選回撥判斷操作
    • 如果不做判空操作,外部未實現這個Function,點選事件會報空異常
class TestWidget extends StatelessWidget {
  const TestWidget({
    Key? key,
    this.onTap,
    this.onBack,
  }) : super(key: key);

  final VoidCallback? onTap;
    
  final VoidCallback? onBack;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        if (onTap != null) {
          onTap!();
        }
        if (onBack != null) {
          onBack!();
        }
      },
      child: Container(),
    );
  }
}
複製程式碼
  • 使用 .call() 後,可以怎麼寫呢?
    • 可以幹掉麻煩的if判空操作了!
class TestWidget extends StatelessWidget {
  const TestWidget({
    Key? key,
    this.onTap,
    this.onBack,
  }) : super(key: key);

  final VoidCallback? onTap;
    
  final VoidCallback? onBack;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        onTap?.call();
        onBack?.call();
      },
      child: Container(),
    );
  }
}
複製程式碼

重新整理機制

Provider的重新整理機制是非常重要的,只要把Provider的重新整理機制搞清楚,這個框架在你面前,將不在神祕!

實際上,大家只要看到ChangeNotifier的應用,那肯定知道,這就是個觀察者模式,但是問題是:它的監聽在何處新增?新增的監聽邏輯是否有完整的初始化鏈路?監聽邏輯是什麼?為什麼觸發監聽邏輯,能導致相應控制元件重新整理?

  • 上面初始化的完整鏈路看的真是有點蛋痛
    • 原始碼東一榔錘西一棒的,而且還用了大量了抽象類,想直接定位邏輯,那是不可能的,你必須找到實現類賦值的地方,才能明白內部運轉
    • 不搞清楚完整初始化鏈路,內心就相當於膈應,明知道他肯定初始化了,卻不知道他在哪初始化的,就很難受
    • 我下面將相關流程理了一遍,希望對大家有所幫助
  • 要讀懂Provider,必須要有個前提,明白什麼觀察者模式:觀察者模式其實很簡單,簡單描述下
    • 定義個List型別,泛型為一個抽象類,初始化這個List
    • 然後給這個List,add這個抽象類的實現類例項
    • 某個合適時候,遍歷這個List所有例項,觸發所有例項的某個方法
    • 如果將這個思想和反射註解結合在一起,就能大大拓寬它的使用面,例如android裡的EventBus。。。

總流程

繼承ChangeNotifier的類,是通過ChangeNotifierProvider傳入到Provider內部,很明顯ChangeNotifierProvider這個類很重要,基本可以算是框架的主入口

這邊梳理下ChangeNotifierProvider 回溯的總流程,其它的旁枝末節,暫時不貼程式碼,這個往上回溯的過程,例項了一個很重要的上下文類,很多關鍵的類初始化都和這個上下文類有關係,先來回溯下這個重要的流程!

  • ChangeNotifierProvider
    • 這地方有個_dispose回撥,是定義好的,內部邏輯是回收ChangeNotifier例項
    • 這裡將該方法賦值給了他的父類ListenableProvider,然後一層層往上回溯
class ChangeNotifierProvider<T extends ChangeNotifier?> extends ListenableProvider<T> {
  ChangeNotifierProvider({
    Key? key,
    required Create<T> create,
    bool? lazy,
    TransitionBuilder? builder,
    Widget? child,
  }) : super(
          key: key,
          create: create,
          dispose: _dispose,
          lazy: lazy,
          builder: builder,
          child: child,
        );
    
  ...
      
  static void _dispose(BuildContext context, ChangeNotifier? notifier) {
    notifier?.dispose();
  }
}
複製程式碼
  • ListenableProvider
    • 這地方有個_startListening回撥,這個方法極其重要
class ListenableProvider<T extends Listenable?> extends InheritedProvider<T> {
  ListenableProvider({
    Key? key,
    required Create<T> create,
    Dispose<T>? dispose,
    bool? lazy,
    TransitionBuilder? builder,
    Widget? child,
  }) : super(
          key: key,
          startListening: _startListening,
          create: create,
          dispose: dispose,
          lazy: lazy,
          builder: builder,
          child: child,
        );  
    
  ...
      
  static VoidCallback _startListening(InheritedContext e, Listenable? value,) {
    value?.addListener(e.markNeedsNotifyDependents);
    return () => value?.removeListener(e.markNeedsNotifyDependents);
  }
}
複製程式碼
  • InheritedProvider
    • 這個類就是邏輯的糾纏點了:我省略了大量和主流程無關的程式碼,不然會十分影響你的關注點,會很難受
    • 這裡就不需要看他的父類了,他的父類是SingleChildStatelessWidget,這個類是對StatelessWidget類的一個封裝,能稍微優化下巢狀問題,無關緊要
    • 需要看下buildWithChild(看成StatelessWidget的build方法就行了)方法裡面的_InheritedProviderScope類,來看下他的原始碼
class InheritedProvider<T> extends SingleChildStatelessWidget {
  InheritedProvider({
    Key? key,
    Create<T>? create,
    T Function(BuildContext context, T? value)? update,
    UpdateShouldNotify<T>? updateShouldNotify,
    void Function(T value)? debugCheckInvalidValueType,
    StartListening<T>? startListening,
    Dispose<T>? dispose,
    this.builder,
    bool? lazy,
    Widget? child,
  })  : _lazy = lazy,
        _delegate = _CreateInheritedProvider(
          create: create,
          update: update,
          updateShouldNotify: updateShouldNotify,
          debugCheckInvalidValueType: debugCheckInvalidValueType,
          startListening: startListening,
          dispose: dispose,
        ),
        super(key: key, child: child);
    
  ...
      
  final _Delegate<T> _delegate;
  final bool? _lazy;
  final TransitionBuilder? builder;

  ...

  @override
  Widget buildWithChild(BuildContext context, Widget? child) {
    ...
    return _InheritedProviderScope<T>(
      owner: this,
      debugType: kDebugMode ? '$runtimeType' : '',
      child: builder != null
          ? Builder(
              builder: (context) => builder!(context, child),
            )
          : child!,
    );
  }
}
複製程式碼
  • _InheritedProviderScope
    • 這裡是繼承了InheritedWidget,裡面重寫createElement方法,在構建Widget的時候,這個方法是肯定會被呼叫的!
    • 馬上就要到最重要的類了,就是createElement中例項化的_InheritedProviderScopeElement類!
class _InheritedProviderScope<T> extends InheritedWidget {
  const _InheritedProviderScope({
    required this.owner,
    required this.debugType,
    required Widget child,
  }) : super(child: child);

  final InheritedProvider<T> owner;
  final String debugType;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return false;
  }

  @override
  _InheritedProviderScopeElement<T> createElement() {
    return _InheritedProviderScopeElement<T>(this);
  }
}
複製程式碼
  • _InheritedProviderScopeElement:實現方法裡面的邏輯全省略了,邏輯太多,看著頭暈
    • 先說明下,這個類是極其極其重要的!大家可以看下他實現了一個什麼抽象類:InheritedContext!
    • InheritedContext繼承了BuildContext,也就是說,這裡作者實現了BuildContext所有抽象方法
      • 是的,BuildContext也是個抽象類,我們可以去實現多個不同實現類
      • 內部系統只需要特定的週期去觸發相應方法,就可以了
      • 你可以在相應的方法裡面實現自己的邏輯,大大的擴充套件了邏輯,怎麼說呢?有點策略模式味道,可以動態替換實現類
    • _InheritedProviderScopeElement算是實現了:InheritedContext和BuildContext;BuildContext中有很多方法是和控制元件生命週期掛鉤的,例如熱過載觸發(reassemble),setState觸發(build、performRebuild)、以及很有意思的強制依賴項元件重新整理(markNeedsNotifyDependents:這是Provider作者在InheritedContext中抽象的方法)。。。
abstract class InheritedContext<T> extends BuildContext {
  T get value;

  void markNeedsNotifyDependents();

  bool get hasValue;
}

class _InheritedProviderScopeElement<T> extends InheritedElement implements InheritedContext<T> {
  _InheritedProviderScopeElement(_InheritedProviderScope<T> widget)
      : super(widget);

  ...

  @override
  void mount(Element? parent, dynamic newSlot) {
    ...
  }

  @override
  _InheritedProviderScope<T> get widget => super.widget as _InheritedProviderScope<T>;

  @override
  void reassemble() {
	...
  }

  @override
  void updateDependencies(Element dependent, Object? aspect) {
    ...
  }

  @override
  void notifyDependent(InheritedWidget oldWidget, Element dependent) {
    ...
  }

  @override
  void performRebuild() {
    ...
  }

  @override
  void update(_InheritedProviderScope<T> newWidget) {
    ...
  }

  @override
  void updated(InheritedWidget oldWidget) {
    ...
  }

  @override
  void didChangeDependencies() {
    ...
  }

  @override
  Widget build() {
    ...
  }

  @override
  void unmount() {
    ...
  }

  @override
  bool get hasValue => _delegateState.hasValue;

  @override
  void markNeedsNotifyDependents() {
    ...
  }

  bool _debugSetInheritedLock(bool value) {
    ...
  }

  @override
  T get value => _delegateState.value;

  @override
  InheritedWidget dependOnInheritedElement(
    InheritedElement ancestor, {
    Object? aspect,
  }) {
    ...
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    ...
  }
}
複製程式碼

上面進行了五步的回溯流程,如果不仔細看清楚相關類裡面的邏輯,很可能就迷失在super方法裡。。。

通過上面的五步回溯,我們可以斷定一個事實:_InheritedProviderScopeElement(實現BuildContext) 被例項化了,而且他在初始化的時候被呼叫了,對應的,其內部相應的週期也能被正常觸發!這樣之前看原始碼困擾我的很多問題,就迎刃而解了!

  • 圖示
    • 上面回溯的層級過多,還有很多的繼承和實現
    • 看了後,腦中可能沒啥印象,所以此處畫了流程圖,可以參照對比

總流程

新增監聽

整個重新整理機制裡面有個相當重要的一環,我們從Create中傳入的類,它內部是怎麼處理的?

class ProEasyCounterPage extends StatelessWidget {
  final provider = ProEasyCounterProvider();

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (BuildContext context) => provider,
      child: Container(),
    );
  }
}
複製程式碼

就算沒看原始碼,我也能斷定傳入的XxxProvider例項,肯定使用了其本身的addListener方法!

但是找這個addListener方法,實在讓我找自閉了,之前因為沒梳理總流程,對其初始化鏈路不明晰,找到了addListener方法,我都十分懷疑,是不是找對了、其它地方是不是還有addListener方法;後來沒辦法,就把Provider原始碼下載下來(之前直接專案裡面點Provider外掛原始碼看的),全域性搜尋addListener方法,排除所有的測試類中使用的,然後斷定我找對了,整個新增監聽的鏈路是通順的!

下面來整體的帶大家過一遍原始碼

靚仔們,我要開始繞了!!!

img

流轉

  • ChangeNotifierProvider
    • 明確下Create是一個Function,返回繼承ChangeNotifier類的例項
    • 這裡一定要記住create這個變數的走向,其中的T就是繼承ChangeNotifier類的關鍵類
    • 增加了_dispose方法,傳給了父類
    • create這裡super給其父類,回溯下父類
typedef Create<T> = T Function(BuildContext context);

class ChangeNotifierProvider<T extends ChangeNotifier?> extends ListenableProvider<T> {
  ChangeNotifierProvider({
    Key? key,
    required Create<T> create,
    bool? lazy,
    TransitionBuilder? builder,
    Widget? child,
  }) : super(
          key: key,
          create: create,
          dispose: _dispose,
          lazy: lazy,
          builder: builder,
          child: child,
        );
    
  ...
      
  static void _dispose(BuildContext context, ChangeNotifier? notifier) {
    notifier?.dispose();
  }
}
複製程式碼
  • ListenableProvider
    • 此處將create例項super給了父類
    • 還增加一個_startListening方法,也同樣給了父類
class ListenableProvider<T extends Listenable?> extends InheritedProvider<T> {
  ListenableProvider({
    Key? key,
    required Create<T> create,
    Dispose<T>? dispose,
    bool? lazy,
    TransitionBuilder? builder,
    Widget? child,
  }) : super(
          key: key,
          startListening: _startListening,
          create: create,
          dispose: dispose,
          lazy: lazy,
          builder: builder,
          child: child,
        );

  ...
 
  static VoidCallback _startListening(InheritedContext e, Listenable? value,) {
    value?.addListener(e.markNeedsNotifyDependents);
    return () => value?.removeListener(e.markNeedsNotifyDependents);
  }
}
複製程式碼
  • InheritedProvider
    • 這地方和上面總流程不太一樣了
    • create、dispose、startListening傳給了_CreateInheritedProvider
    • 需要看下_CreateInheritedProvider
class InheritedProvider<T> extends SingleChildStatelessWidget {
  InheritedProvider({
    Key? key,
    Create<T>? create,
    T Function(BuildContext context, T? value)? update,
    UpdateShouldNotify<T>? updateShouldNotify,
    void Function(T value)? debugCheckInvalidValueType,
    StartListening<T>? startListening,
    Dispose<T>? dispose,
    this.builder,
    bool? lazy,
    Widget? child,
  })  : _lazy = lazy,
        _delegate = _CreateInheritedProvider(
          create: create,
          update: update,
          updateShouldNotify: updateShouldNotify,
          debugCheckInvalidValueType: debugCheckInvalidValueType,
          startListening: startListening,
          dispose: dispose,
        ),
        super(key: key, child: child);

  ...
}
複製程式碼
  • 流程圖示

重新整理機制-流轉

_CreateInheritedProvider

這地方會進行一個很重要的回溯流程,回溯到_InheritedProviderScopeElement

下次再有需要用到這個類,就直接拿這個類來講了

  • _CreateInheritedProvider說明
    • _CreateInheritedProvider繼承了抽象類 _Delegate,實現了其createState抽象方法
    • 按理說,主要邏輯肯定在createState方法中**_CreateInheritedProviderState**例項中
    • 必須要看下_CreateInheritedProvider例項,在何處呼叫 createState方法,然後才能繼續看 _CreateInheritedProviderState的邏輯
@immutable
abstract class _Delegate<T> {
  _DelegateState<T, _Delegate<T>> createState();

  void debugFillProperties(DiagnosticPropertiesBuilder properties) {}
}

class _CreateInheritedProvider<T> extends _Delegate<T> {
  _CreateInheritedProvider({
    this.create,
    this.update,
    UpdateShouldNotify<T>? updateShouldNotify,
    this.debugCheckInvalidValueType,
    this.startListening,
    this.dispose,
  })  : assert(create != null || update != null),
        _updateShouldNotify = updateShouldNotify;

  final Create<T>? create;
  final T Function(BuildContext context, T? value)? update;
  final UpdateShouldNotify<T>? _updateShouldNotify;
  final void Function(T value)? debugCheckInvalidValueType;
  final StartListening<T>? startListening;
  final Dispose<T>? dispose;

  @override
  _CreateInheritedProviderState<T> createState() =>
      _CreateInheritedProviderState();
}
複製程式碼
  • 這裡需要重新回顧下InheritedProvider類
    • 這地方做了一個很重要的操作,將_CreateInheritedProvider例項賦值給 _delegate
    • buildWithChild方法中_InheritedProviderScope的owner接受了InheritedProvider本身的例項
    • 結合這倆個就有戲了,再來看下_InheritedProviderScope類
class InheritedProvider<T> extends SingleChildStatelessWidget {
  InheritedProvider({
    Key? key,
    Create<T>? create,
    T Function(BuildContext context, T? value)? update,
    UpdateShouldNotify<T>? updateShouldNotify,
    void Function(T value)? debugCheckInvalidValueType,
    StartListening<T>? startListening,
    Dispose<T>? dispose,
    this.builder,
    bool? lazy,
    Widget? child,
  })  : _lazy = lazy,
        _delegate = _CreateInheritedProvider(
          create: create,
          update: update,
          updateShouldNotify: updateShouldNotify,
          debugCheckInvalidValueType: debugCheckInvalidValueType,
          startListening: startListening,
          dispose: dispose,
        ),
        super(key: key, child: child);
    
  final _Delegate<T> _delegate;
  final bool? _lazy;
	
  ...

  @override
  Widget buildWithChild(BuildContext context, Widget? child) {
	,,,
    return _InheritedProviderScope<T>(
      owner: this,
      debugType: kDebugMode ? '$runtimeType' : '',
      child: builder != null
          ? Builder(
              builder: (context) => builder!(context, child),
            )
          : child!,
    );
  }
}
複製程式碼
  • _InheritedProviderScope
    • createElement方法傳入_InheritedProviderScope本身的例項
    • 關鍵的在_InheritedProviderScopeElement類中
class _InheritedProviderScope<T> extends InheritedWidget {
  const _InheritedProviderScope({
    required this.owner,
    required this.debugType,
    required Widget child,
  }) : super(child: child);

  final InheritedProvider<T> owner;
  final String debugType;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return false;
  }

  @override
  _InheritedProviderScopeElement<T> createElement() {
    return _InheritedProviderScopeElement<T>(this);
  }
}
複製程式碼
  • _InheritedProviderScopeElement類,我就直接精簡到關鍵程式碼了
    • 有沒有感覺InheritedWidget很像StatefulWidget,實際他倆最終都是繼承Widget,未對Widget的建造者模式那層封裝,所以有倆層結構;而StatelessWidget將建造者模式那層進行了封裝,所以只有一層結構
    • 下面的關鍵程式碼看到沒!**widget.owner._delegate.createState() ... ** 這地方呼叫了_CreateInheritedProvider類的createState() 方法,安心了
    • performRebuild:該回撥會在setState或者build的時候會觸發;此處做了一個判斷,只會在第一次build的時候觸發
    • 這裡可以確定_CreateInheritedProvider類中的createState方法一定會被呼叫;接下來看看其方法裡面呼叫的 _CreateInheritedProviderState類
class _InheritedProviderScopeElement<T> extends InheritedElement
    implements InheritedContext<T> {
  _InheritedProviderScopeElement(_InheritedProviderScope<T> widget)
      : super(widget);

  ...

  @override
  void performRebuild() {
    if (_firstBuild) {
      _firstBuild = false;
      _delegateState = widget.owner._delegate.createState()..element = this;
    }
    super.performRebuild();
  }

  ...
}
複製程式碼
  • 流程圖示

_delegate.createState()呼叫鏈

_InheritedProviderScopeElement

  • _CreateInheritedProviderState:這個類做了很多事情,很多的主體邏輯的都在此處理
    • 該類程式碼很多,此處只留下我們需要關注的程式碼,因為省略了很多程式碼,從下面的主體程式碼來看,流程就清楚了:create、startListening、dispose 都有
    • 但是這些變數是依附在delegate上的,這個delegate是個啥?需要看下繼承的抽象類 _DelegateState
class _CreateInheritedProviderState<T> extends _DelegateState<T, _CreateInheritedProvider<T>> {
  VoidCallback? _removeListener;
  bool _didInitValue = false;
  T? _value;
  _CreateInheritedProvider<T>? _previousWidget;

  @override
  T get value {
    ...

    if (!_didInitValue) {
      _didInitValue = true;
      if (delegate.create != null) {
        assert(debugSetInheritedLock(true));
        try {
          ...
          _value = delegate.create!(element!);
        } finally {
          ...
        }
        ...
      }
      ...
    }

    element!._isNotifyDependentsEnabled = false;
    _removeListener ??= delegate.startListening?.call(element!, _value as T);
    element!._isNotifyDependentsEnabled = true;
    assert(delegate.startListening == null || _removeListener != null);
    return _value as T;
  }

  @override
  void dispose() {
    super.dispose();
    _removeListener?.call();
    if (_didInitValue) {
      delegate.dispose?.call(element!, _value as T);
    }
  }

  ...
}
複製程式碼
  • _DelegateState
    • delegate是通過 _InheritedProviderScopeElement的例項獲取到了owner然後獲取到了 _delegate變數
    • _delegate這個變數是在InheritedProvider類中的例項化 _CreateInheritedProvider賦值給他的,不信的話,可以返回去看看
    • 好吉爾繞!!!
abstract class _DelegateState<T, D extends _Delegate<T>> {
  _InheritedProviderScopeElement<T>? element;

  T get value;

  D get delegate => element!.widget.owner._delegate as D;

  bool get hasValue;

  bool debugSetInheritedLock(bool value) {
    return element!._debugSetInheritedLock(value);
  }

  bool willUpdateDelegate(D newDelegate) => false;

  void dispose() {}

  void debugFillProperties(DiagnosticPropertiesBuilder properties) {}

  void build({required bool isBuildFromExternalSources}) {}
}
複製程式碼
  • element
    • 現在還有個問題,element這個變數在哪例項化的?怎麼大家這麼隨便用它!就不怕它為空嗎?
    • 直接帶大家來_InheritedProviderScopeElement裡面看了,上面已經回顧了到這個必定例項化這個上下文類的流程
    • performRebuild回撥中,在呼叫createState()方法的時候,給element賦值了,element = this
    • 所以在_CreateInheritedProviderState類中,可以隨便使用element 這個變數,他的值肯定不為空!
class _InheritedProviderScopeElement<T> extends InheritedElement
    implements InheritedContext<T> {
  _InheritedProviderScopeElement(_InheritedProviderScope<T> widget)
      : super(widget);

  ...

  @override
  void performRebuild() {
    if (_firstBuild) {
      _firstBuild = false;
      _delegateState = widget.owner._delegate.createState()..element = this;
    }
    super.performRebuild();
  }

  ...
}
複製程式碼

不知道大家對這流程有沒有個清晰的印象

  • 來看看這山路十八彎的初始化鏈路圖

_DelegateState.element初始化鏈

_CreateInheritedProviderState

有了上面分析出的element和_delegate不為空的,且 _delegate能直接訪問 _CreateInheritedProvider這個例項基礎,再來看下 _CreateInheritedProviderState程式碼

  1. get 流程
    1. 我們傳入的create會直接賦值給 _value,現在這個 _value,就是我們在外面傳進來的那個XxxProvider例項了!
    2. 底下也呼叫了 startListening,說明從外面傳進來的這個回撥也呼叫了,將 上下文例項傳進來的XxxProvider例項 作為入參傳進了這個回撥中,此處傳進來的回撥也通過 .call 被呼叫了!
  2. dispose 流程
    1. 呼叫startListening方法時,該方法會返回一個移除監聽Function
    2. 移除監聽的Function在dispose時被呼叫,移除給XxxProvider新增的監聽
    3. 從外部傳入的dispose方法,也在此處被執行
    4. OK!回收資源的操作在此處都搞定了!
class _CreateInheritedProviderState<T> extends _DelegateState<T, _CreateInheritedProvider<T>> {
  VoidCallback? _removeListener;
  bool _didInitValue = false;
  T? _value;
  _CreateInheritedProvider<T>? _previousWidget;

  @override
  T get value {
    ...

    if (!_didInitValue) {
      _didInitValue = true;
      if (delegate.create != null) {
        assert(debugSetInheritedLock(true));
        try {
          ...
          _value = delegate.create!(element!);
        } finally {
          ...
        }
        ...
      }
      ...
    }

    element!._isNotifyDependentsEnabled = false;
    _removeListener ??= delegate.startListening?.call(element!, _value as T);
    element!._isNotifyDependentsEnabled = true;
    assert(delegate.startListening == null || _removeListener != null);
    return _value as T;
  }

  @override
  void dispose() {
    super.dispose();
    _removeListener?.call();
    if (_didInitValue) {
      delegate.dispose?.call(element!, _value as T);
    }
  }

  ...
}
複製程式碼
  • 關鍵的就是startListening回撥了,來看下他的邏輯
    • _startListening在此處 addListener 了!ChangeNotifier 是 Listenable 實現類,姑且把它當成訪問者模式也可,所以這個value就是我們從外面傳進來的 XxxProvider
    • 返回了一個VoidCallback的Function,裡面是移除監聽邏輯
class ListenableProvider<T extends Listenable?> extends InheritedProvider<T> {
  ListenableProvider({
    Key? key,
    required Create<T> create,
    Dispose<T>? dispose,
    bool? lazy,
    TransitionBuilder? builder,
    Widget? child,
  }) : super(
          key: key,
          startListening: _startListening,
          create: create,
          dispose: dispose,
          lazy: lazy,
          builder: builder,
          child: child,
        );

  ...
 
  static VoidCallback _startListening(InheritedContext e, Listenable? value,) {
    value?.addListener(e.markNeedsNotifyDependents);
    return () => value?.removeListener(e.markNeedsNotifyDependents);
  }
}
複製程式碼

還有最後一個問題!!!

需要呼叫_startListening方法,必須呼叫 _CreateInheritedProviderState類裡面的 get value

在哪個初始化入口,使用這個 get value 呢?

  • 這裡直接給出結論了,還是在 _InheritedProviderScopeElement這個上下文類裡面
    • reassemble:全域性狀態的初始化邏輯或熱過載的時候被呼叫
    • _delegateState首先在performRebuild回撥中會賦初值
    • 在reassemble回撥中,_delegateState呼叫了value( _delegateState.value )
    • 所以 get value 肯定會在初始化的時候被呼叫,上面流程是通順的
class _InheritedProviderScopeElement<T> extends InheritedElement
    implements InheritedContext<T> {
  _InheritedProviderScopeElement(_InheritedProviderScope<T> widget)
      : super(widget);
    
  late _DelegateState<T, _Delegate<T>> _delegateState;

  ...
      
  @override
  void performRebuild() {
    if (_firstBuild) {
      _firstBuild = false;
      _delegateState = widget.owner._delegate.createState()..element = this;
    }
    super.performRebuild();
  }

  @override
  void reassemble() {
    super.reassemble();

    final value = _delegateState.hasValue ? _delegateState.value : null;
    if (value is ReassembleHandler) {
      value.reassemble();
    }
  }

  ...
}
複製程式碼

總結

上面分析完了新增監聽,以及相關的初始化鏈路和呼叫鏈路

  • 可以把流程圖整全了,來看看

新增監聽初始化鏈

重新整理邏輯

重新整理邏輯也是相當之繞啊;本菜比,各種debug,在framework裡面各種打斷點,終於把流程理通了!我突然感覺自己打通了任督二脈!

作者為了實現這個重新整理邏輯,和系統api做了大量的互動,相當的精彩!

我會盡力將這個精彩紛呈的操作,展現給大家!

觸發

  • ListenableProvider
    • 這地方邏輯很簡單,新增了InheritedContext這個上下文類中的markNeedsNotifyDependents方法
    • 說明,我們在外部使用notifyListeners() 的時候,一定會觸發InheritedContext實現類中的markNeedsNotifyDependents方法
class ListenableProvider<T extends Listenable?> extends InheritedProvider<T> {
  ListenableProvider({
    Key? key,
    required Create<T> create,
    Dispose<T>? dispose,
    bool? lazy,
    TransitionBuilder? builder,
    Widget? child,
  }) : super(
          key: key,
          startListening: _startListening,
          create: create,
          dispose: dispose,
          lazy: lazy,
          builder: builder,
          child: child,
        );

  ...
 
  static VoidCallback _startListening(InheritedContext e, Listenable? value,) {
    value?.addListener(e.markNeedsNotifyDependents);
    return () => value?.removeListener(e.markNeedsNotifyDependents);
  }
}
複製程式碼
  • _InheritedProviderScopeElement: _InheritedProviderScopeElement是InheritedContext的實現類
    • 還是要來這個類看看,只保留了和markNeedsNotifyDependents有關的程式碼
    • markNeedsNotifyDependents回撥作用,總的來說:會將強制依賴於T視窗小部件進行重建
    • 說的這麼籠統沒啥用,下面會全面分析,他是怎麼做到讓依賴於T視窗小部件進行重建的! 我想了下,還是觀察者模式的應用。。。
class _InheritedProviderScopeElement<T> extends InheritedElement implements InheritedContext<T> {
  _InheritedProviderScopeElement(_InheritedProviderScope<T> widget)
      : super(widget);
    
  ...

  @override
  void markNeedsNotifyDependents() {
    if (!_isNotifyDependentsEnabled) {
      return;
    }

    markNeedsBuild();
    _shouldNotifyDependents = true;
  }

  ...
}
複製程式碼

重新整理流程

我們們現在來理一下重新整理的流程!

  • markNeedsNotifyDependents
    • 當我們使用 notifyListeners(),就會觸發,這個回撥
    • 此處呼叫了 markNeedsBuild(),然後給 _shouldNotifyDependents 設定為true
    • 必備操作,來看下 markNeedsBuild() 作用
class _InheritedProviderScopeElement<T> extends InheritedElement implements InheritedContext<T> {
  _InheritedProviderScopeElement(_InheritedProviderScope<T> widget)
      : super(widget);
    
  bool _shouldNotifyDependents = false;
  ...

  @override
  void markNeedsNotifyDependents() {
    if (!_isNotifyDependentsEnabled) {
      return;
    }

    markNeedsBuild();
    _shouldNotifyDependents = true;
  }

  ...
}
複製程式碼
  • markNeedsBuild
    • _InheritedProviderScopeElement最終繼承的還是Element抽象類,markNeedsBuild()方法是Element中的
    • Element類是一個實現了BuildContext抽象類中抽象方法的抽象類,該類十分重要
    • 這個方法花裡胡哨的程式碼寫了一大堆,他最主要的功能:就是會呼叫Element的performRebuild()方法,然後觸發ComponentElement的build()方法,最終觸發_InheritedProviderScopeElement的build方法
    • _InheritedProviderScopeElement extends InheritedElement extends ProxyElement extends ComponentElement extends Element
abstract class Element extends DiagnosticableTree implements BuildContext {
  ...
      
  void markNeedsBuild() {
    assert(_lifecycleState != _ElementLifecycle.defunct);
    if (_lifecycleState != _ElementLifecycle.active)
      return;
    assert(owner != null);
    assert(_lifecycleState == _ElementLifecycle.active);
    assert(() {
      if (owner!._debugBuilding) {
        assert(owner!._debugCurrentBuildTarget != null);
        assert(owner!._debugStateLocked);
        if (_debugIsInScope(owner!._debugCurrentBuildTarget!))
          return true;
        if (!_debugAllowIgnoredCallsToMarkNeedsBuild) {
          final List<DiagnosticsNode> information = <DiagnosticsNode>[
            ErrorSummary('setState() or markNeedsBuild() called during build.'),
            ErrorDescription(
              'This ${widget.runtimeType} widget cannot be marked as needing to build because the framework '
              'is already in the process of building widgets.  A widget can be marked as '
              'needing to be built during the build phase only if one of its ancestors '
              'is currently building. This exception is allowed because the framework '
              'builds parent widgets before children, which means a dirty descendant '
              'will always be built. Otherwise, the framework might not visit this '
              'widget during this build phase.',
            ),
            describeElement(
              'The widget on which setState() or markNeedsBuild() was called was',
            ),
          ];
          if (owner!._debugCurrentBuildTarget != null)
            information.add(owner!._debugCurrentBuildTarget!.describeWidget('The widget which was currently being built when the offending call was made was'));
          throw FlutterError.fromParts(information);
        }
        assert(dirty); // can only get here if we're not in scope, but ignored calls are allowed, and our call would somehow be ignored (since we're already dirty)
      } else if (owner!._debugStateLocked) {
        assert(!_debugAllowIgnoredCallsToMarkNeedsBuild);
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() or markNeedsBuild() called when widget tree was locked.'),
          ErrorDescription(
            'This ${widget.runtimeType} widget cannot be marked as needing to build '
            'because the framework is locked.',
          ),
          describeElement('The widget on which setState() or markNeedsBuild() was called was'),
        ]);
      }
      return true;
    }());
    if (dirty)
      return;
    _dirty = true;
    owner!.scheduleBuildFor(this);
  }

  ...
}
複製程式碼
  • build
    • 這裡說明下,這個子類呼叫父類方法,然後父類呼叫自身方法,是先觸發這個子類的重寫方法,然後可以通過 super. 的方式去執行父類邏輯
    • 上面給_shouldNotifyDependents設定為true,所以build內部邏輯會執行notifyClients(widget)方法
    • 接下來看下notifyClients(widget)方法
class _InheritedProviderScopeElement<T> extends InheritedElement implements InheritedContext<T> {
  _InheritedProviderScopeElement(_InheritedProviderScope<T> widget)
      : super(widget);
    
  bool _shouldNotifyDependents = false;
  ...

    @override
  Widget build() {
    if (widget.owner._lazy == false) {
      value; // this will force the value to be computed.
    }
    _delegateState.build(
      isBuildFromExternalSources: _isBuildFromExternalSources,
    );
    _isBuildFromExternalSources = false;
    if (_shouldNotifyDependents) {
      _shouldNotifyDependents = false;
      notifyClients(widget);
    }
    return super.build();
  }

  ...
}
複製程式碼
  • notifyClients:notifyClients()是InheritedElement類中的,notifyClients()方法是ProxyElement類中的一個抽象方法,InheritedElement在此處做了一個實現
    1. notifyClients()是一個非常非常重要的方法,它內部有個for迴圈,遍歷了_dependents這個HashMap型別的所有key值, _dependents的key是Element型別
      1. 什麼是Element?它可以表示為Widget在樹中特定位置的例項,一個Element可以形成一棵樹(想想每個Container都有Element,然後其child再套其它的widget,這樣就形成了一顆樹)
      2. Element在此處將其理解為:本身Widget和其子節點形成的樹,Element是這棵樹的頭結點,這特定位置的節點是例項化的,對這個特定位置的例項節點操作,會影響到他的子節點
      3. Widget的createElement()方法會例項化Element
    2. 這地方遍歷_dependents的key取Element,可以猜測:他肯定是想取某個元素或者說某個Widget
    3. 取到相關Element例項後,她會傳入notifyDependent(oldWidget, dependent)方法中
    4. 接下來,需要看看notifyDependent(oldWidget, dependent)方法邏輯了
class InheritedElement extends ProxyElement {
  final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
    
  ...
    
  @override
  void notifyClients(InheritedWidget oldWidget) {
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (final Element dependent in _dependents.keys) {
      assert(() {
        // check that it really is our descendant
        Element? ancestor = dependent._parent;
        while (ancestor != this && ancestor != null)
          ancestor = ancestor._parent;
        return ancestor == this;
      }());
      // check that it really depends on us
      assert(dependent._dependencies!.contains(this));
      notifyDependent(oldWidget, dependent);
    }
  }
}
複製程式碼
  • notifyDependent
    • if (dependencies is _Dependency) 這判斷的邏輯題裡面還有很多邏輯,是作者在BuildContext上面搞了一個select擴充套件方法(判斷是否需要重新整理),但和現在講了重新整理流程無關,我在裡面繞了好久,凎!
    • 去掉上面的邏輯就簡單了,shouldNotify賦值為true,最後呼叫dependent.didChangeDependencies()
    • dependent還記得是啥嗎?是父類裡面迴圈取得的Element例項
    • 這地方直接去掉super操作,這也是系統建議的,我們可以重寫notifyDependent方法,自定義相關邏輯;因為有時我們需要可選擇性的呼叫dependent.didChangeDependencies()!
class _InheritedProviderScopeElement<T> extends InheritedElement implements InheritedContext<T> {
  _InheritedProviderScopeElement(_InheritedProviderScope<T> widget)
      : super(widget);
    
  ...

  @override
  void notifyDependent(InheritedWidget oldWidget, Element dependent) {
    final dependencies = getDependencies(dependent);

    if (kDebugMode) {
      ProviderBinding.debugInstance.providerDidChange(_debugId);
    }

    var shouldNotify = false;
    if (dependencies != null) {
      if (dependencies is _Dependency<T>) {
        ...
      } else {
        shouldNotify = true;
      }
    }

    if (shouldNotify) {
      dependent.didChangeDependencies();
    }
  }

  ...
}
複製程式碼
  • didChangeDependencies
    • didChangeDependencies邏輯就很簡單了,會呼叫markNeedsBuild()
    • 可以理解為:最終會呼叫該Widget的build方法
    • markNeedsBuild()就不講了,內部涉及邏輯太多了,還涉及bind類,還會涉及到繪製流程,我嘞個去。。。
abstract class Element extends DiagnosticableTree implements BuildContext {
  ...
    
  @mustCallSuper
  void didChangeDependencies() {
    assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
    assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
    markNeedsBuild();
  }

  ...
}
複製程式碼

現在有個超糾結的事情,這個點關乎整個重新整理流程的樞紐!

InheritedElement中的_dependents這個map的key是Element,這個Element是什麼?上面所有流程都是為了呼叫 _dependents這個Map中key(Element)的markNeedsBuild()方法,最終是為了呼叫這個Element的Widget的build方法!

大家明白了嗎?我們就算大膽去蒙,去猜,去賭,這個Widget十有八九就是Consumer這類重新整理Widget啊!

但是!但是!他到底是怎麼將這類重新整理Widget新增到InheritedElement的 _dependents變數中的呢 !?

  • 上述流程圖示

重新整理流程

BuildContext

插播一個小知識點,這個知識和下述內容相關,這邊先介紹一下

BuildContext是什麼?

  • BuildContext
    • 每個抽象方法上面註釋超級多,我刪掉了(佔篇幅),有興趣的可以自己去原始碼裡看看
    • BuildContext就是抽象類,是約定好的一個抽象類,相關方法的功能已經被約定,你如果想實現這個抽象類類,相關方法功能實現可以有出入,但不應該偏離抽象方法註釋所描述的功能範圍
abstract class BuildContext {
  Widget get widget;

  BuildOwner? get owner;

  bool get debugDoingBuild;

  RenderObject? findRenderObject();

  Size? get size;

  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect });

  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });

  InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>();

  T? findAncestorWidgetOfExactType<T extends Widget>();

  T? findAncestorStateOfType<T extends State>();

  T? findRootAncestorStateOfType<T extends State>();

  T? findAncestorRenderObjectOfType<T extends RenderObject>();

  void visitAncestorElements(bool Function(Element element) visitor);

  void visitChildElements(ElementVisitor visitor);

  DiagnosticsNode describeElement(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty});

  DiagnosticsNode describeWidget(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty});

  List<DiagnosticsNode> describeMissingAncestor({ required Type expectedAncestorType });

  DiagnosticsNode describeOwnershipChain(String name);
}
複製程式碼
  • StatelessWidget:看下StatelessWidget對BuildContext的實現(StatefulWidget同理,不貼了)
    • 程式碼超級簡單,StatelessWidget抽象了build方法,入參為BuildContext
    • createElement()方法例項了StatelessElement類,並將StatelessWidget本身例項傳入
    • StatelessElement裡面實現了ComponentElement的build方法:該方法呼叫了widget裡面的build方法,並將本身的例項傳入,流程通了,此處呼叫StatelessWidget的build方法,並傳入了BuildContext的實現類
    • ComponentElement的父類中肯定有實現BuildContext,往上看看
abstract class StatelessWidget extends Widget {
  const StatelessWidget({ Key? key }) : super(key: key);

  @override
  StatelessElement createElement() => StatelessElement(this);

  @protected
  Widget build(BuildContext context);
}

class StatelessElement extends ComponentElement {
  StatelessElement(StatelessWidget widget) : super(widget);

  @override
  StatelessWidget get widget => super.widget as StatelessWidget;

  @override
  Widget build() => widget.build(this);

  @override
  void update(StatelessWidget newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
    _dirty = true;
    rebuild();
  }
}
複製程式碼
  • ComponentElement
    • ComponentElement繼承Element,它抽象了一個build方法,StatelessElement實現了這個方法,沒毛病
    • 來看看Element
abstract class ComponentElement extends Element {
  ...
  
  @protected
  Widget build();

  ...
}
複製程式碼
  • Element
    • Element此處實現了BuildContext,所以繼承他的子類,直接將本身例項傳給BuildContext就OK了
    • 如果沒做什麼騷操作,BuildContext可以理解為:每個Widget都有對應的Element( 通過createElement()生成 ),Element是BuildContext實現類
abstract class Element extends DiagnosticableTree implements BuildContext {
 	...
}
複製程式碼
  • Widget
    • Widget抽象了一個createElement()方法
    • 每個Widget的子類,理應都有自己對應的Element
@immutable
abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });

  final Key? key;
    
  @protected
  @factory
  Element createElement();

  ...
}
複製程式碼
  • 圖示

BuildContext(StatelessWidget)

  • 關於Widget和Element再多說倆句

知道為什麼好多文章說Widget對Element是一對多嗎?

首先Widget是Element的一個配置描述,我們通過類似StatelessElement createElement() => StatelessElement(this),將widget本身的配置資訊例項傳入XxxElemen(this)中,然後XxxElement可以通過傳入的Widget配置資訊去生成對應的Element例項

大家發現沒?每一個Widget都有對應的Element例項!

假設寫了下面這個Widget

Widget _myWidget({Widget child}){
    return Container(width:30, height:30, child:child);
}
複製程式碼
  • 我們們這樣用
_myWidget(
    child: Container(
        child: _myWidget(),
    )
)
複製程式碼

這不就對了嘛,只有一份Widget配置資訊,但是會生成倆個Element!

但是還是會有倆個Widget例項,但從配置資訊層次上看,倆個Widget例項的配置資訊都是一樣的,所以是一份配置資訊。。。

所以就有了Widget對Element是一對多的說法;反正我是這樣理解的,僅供參考。。。

可能大佬們寫文章,這些簡單例項腦子自然生成,但是對這些沒啥概念的靚仔,這或許就成了:一條定理或者既定概念

img

神奇的Provider.of()

為了將上面的流程連線起來,需要一位神奇的魔術師登場,下面就要請上我們的王炸:Provider.of() !

將重新整理元件新增到了InheritedElement中的_dependents變數裡,他到底是怎麼做到的呢?

  • Provider.of() :下面就是該方法所有的邏輯,程式碼很少,實現的功能卻很強!
    1. of方法中,會通過 _inheritedElementOf(context)方法獲取到,和當前Widget距離最近的(往父節點遍歷)繼承InheritedElement的XxxElement
    2. 上面是通過 _inheritedElementOf(context)方法中的 context.getElementForInheritedWidgetOfExactType()方法去獲取的;繼承InheritedElement的Widget的子節點,是可以通過這個方法去拿到距離他最近的繼承InheritedElement的Widget的XxxElement例項,同樣的,也可以獲取其中儲存的資料
    3. 你可能想,我拿到 繼承InheritedElement的XxxElement的例項有啥?我們好好想想:我們拿到這個XxxElement例項後,我們不就可以往它的父類InheritedElement裡面的 _dependents的map變數塞值了嗎?狂喜...
    4. 它是怎麼做到的呢?就是通過這個:context.dependOnInheritedElement(inheritedElement)
static T of<T>(BuildContext context, {bool listen = true}) {
    ...

    final inheritedElement = _inheritedElementOf<T>(context);

    if (listen) {
        context.dependOnInheritedElement(inheritedElement);
    }
    return inheritedElement.value;
}


static _InheritedProviderScopeElement<T> _inheritedElementOf<T>(BuildContext context) {
    ...

    _InheritedProviderScopeElement<T>? inheritedElement;

    if (context.widget is _InheritedProviderScope<T>) {
        context.visitAncestorElements((parent) {
            inheritedElement = parent.getElementForInheritedWidgetOfExactType<
                _InheritedProviderScope<T>>() as _InheritedProviderScopeElement<T>?;
            return false;
        });
    } else {
        inheritedElement = context.getElementForInheritedWidgetOfExactType<
            _InheritedProviderScope<T>>() as _InheritedProviderScopeElement<T>?;
    }

    if (inheritedElement == null) {
        throw ProviderNotFoundException(T, context.widget.runtimeType);
    }

    return inheritedElement!;
}
複製程式碼
  • dependOnInheritedElement
    • BuildContext中的dependOnInheritedElement方法點進去是個抽象方法,畢竟BuildContext是個純抽象類,方法都沒有邏輯
    • 關於BuildContext上面已經說過了,我們直接去Element類裡面找dependOnInheritedElement方法,看看他的實現邏輯
    • 直接看最重要的程式碼 ancestor.updateDependencies(this, aspect):我們傳入的繼承了InheritedElement的XxxElement,被傳入了updateDependencies方法,然後他還將當前Widget的Element例項傳入了updateDependencies方法中
abstract class Element extends DiagnosticableTree implements BuildContext {
  ...
    
  @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies!.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }

  ...
}
複製程式碼
  • updateDependencies:流程終於完整的跑通了!
    • updateDependencies方法呼叫了setDependencies方法
    • setDependencies方法,將子Widget的Element例項賦值給了繼承InheritedElement的類的 _dependents 變數
class InheritedElement extends ProxyElement {
  ...
      
  @protected
  void setDependencies(Element dependent, Object? value) {
    _dependents[dependent] = value;
  }
      
  @protected
  void updateDependencies(Element dependent, Object? aspect) {
    setDependencies(dependent, null);
  }

  ...
}
複製程式碼
  • 看下圖示:這圖調了好久,不規劃下,線很容易交叉,吐血...

Provider.of流程

自定義Builder

通過上面的分析,Provider的widget定點重新整理,已經不再神祕了...

學以致用,我們們來整一個自定義Builder!

  • 自定義的EasyBuilder控制元件能起到和Consumer一樣的重新整理作用
class EasyBuilder<T> extends StatelessWidget {
  const EasyBuilder(
    this.builder, {
    Key? key,
  }) : super(key: key);

  final Widget Function() builder;

  @override
  Widget build(BuildContext context) {
    Provider.of<T>(context);
    return builder();
  }
}
複製程式碼

寫下完整的使用

  • view
class CustomBuilderPage extends StatelessWidget {
  final provider = CustomBuilderProvider();

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (BuildContext context) => provider,
      child: _buildPage(),
    );
  }

  Widget _buildPage() {
    return Scaffold(
      appBar: AppBar(title: Text('Provider-自定義Builder範例')),
      body: Center(
        child: EasyBuilder<CustomBuilderProvider>(
          () => Text(
            '點選了 ${provider.count} 次',
            style: TextStyle(fontSize: 30.0),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => provider.increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}

///自定義Builder
class EasyBuilder<T> extends StatelessWidget {
  const EasyBuilder(
    this.builder, {
    Key? key,
  }) : super(key: key);

  final Widget Function() builder;

  @override
  Widget build(BuildContext context) {
    Provider.of<T>(context);
    return builder();
  }
}
複製程式碼
  • provider
class CustomBuilderProvider extends ChangeNotifier {
  int count = 0;

  void increment() {
    count++;
    notifyListeners();
  }
}
複製程式碼
  • 效果圖

provider_custom_builder

總結

以上,就將Provider的重新整理機制完整的說完了~~

撒花 ✿✿ヽ(°▽°)ノ✿

img

如果那裡寫的欠妥,請各位大佬不吝賜教 ~ . ~

手搓一個狀態管理框架

看完Provider的原理後,大家是不是感覺胸中萬千溝壑,腹中萬千才華無法釋放!我們們就來將自己想法統統釋放出來吧!

學以致用,我們們就來按照Provider重新整理機制,手搓一個狀態管理框架。。。

手搓框架就叫:EasyP(後面應該還會接著寫Bloc和GetX;依次叫EasyC,EasyX,省事...),取Provider的頭字母

手搓狀態框架

這個手搓框架做了很多簡化,但是絕對保留了原汁原味的Provider重新整理機制!

  • ChangeNotifierEasyP:類比Provider的ChangeNotifierProvider
    • 程式碼做了大量的精簡,只保留了provider的重新整理機制的精髓
    • 程式碼我就不解釋了,上面的重新整理機制如果看懂了,下面的程式碼很容易理解;如果沒看懂,我解釋下面程式碼也沒用啊。。。
class ChangeNotifierEasyP<T extends ChangeNotifier> extends InheritedWidget {
  ChangeNotifierEasyP({
    Key? key,
    Widget? child,
    required this.create,
  }) : super(key: key, child: child ?? Container());

  final T Function(BuildContext context) create;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => false;

  @override
  InheritedElement createElement() => EasyPInheritedElement(this);
}

class EasyPInheritedElement<T extends ChangeNotifier> extends InheritedElement {
  EasyPInheritedElement(ChangeNotifierEasyP<T> widget) : super(widget);

  bool _firstBuild = true;
  bool _shouldNotify = false;
  late T _value;
  late void Function() callBack;

  T get value => _value;

  @override
  void performRebuild() {
    if (_firstBuild) {
      _firstBuild = false;
      _value = (widget as ChangeNotifierEasyP<T>).create(this);

      _value.addListener(callBack = () {
        // 處理重新整理邏輯,此處無法直接呼叫notifyClients
        // 會導致owner!._debugCurrentBuildTarget為null,觸發斷言條件,無法向後執行
        _shouldNotify = true;
        markNeedsBuild();
      });
    }

    super.performRebuild();
  }

  @override
  Widget build() {
    if (_shouldNotify) {
      _shouldNotify = false;
      notifyClients(widget);
    }
    return super.build();
  }

  @override
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    //此處就直接重新整理新增的監聽子Element了,不各種super了
    dependent.markNeedsBuild();
    // super.notifyDependent(oldWidget, dependent);
  }

  @override
  void unmount() {
    _value.removeListener(callBack);
    _value.dispose();
    super.unmount();
  }
}
複製程式碼
  • EasyP:類比Provider的Provider類
class EasyP {
  /// 獲取EasyP例項
  /// 獲取例項的時候,listener引數老是寫錯,這邊直接用倆個方法區分了
  static T of<T extends ChangeNotifier>(BuildContext context) {
    return _getInheritedElement<T>(context).value;
  }

  /// 註冊監聽控制元件
  static T register<T extends ChangeNotifier>(BuildContext context) {
    var element = _getInheritedElement<T>(context);
    context.dependOnInheritedElement(element);
    return element.value;
  }

  /// 獲取距離當前Element最近繼承InheritedElement<T>的元件
  static EasyPInheritedElement<T>
      _getInheritedElement<T extends ChangeNotifier>(BuildContext context) {
    var inheritedElement = context
            .getElementForInheritedWidgetOfExactType<ChangeNotifierEasyP<T>>()
        as EasyPInheritedElement<T>?;

    if (inheritedElement == null) {
      throw EasyPNotFoundException(T);
    }

    return inheritedElement;
  }
}

class EasyPNotFoundException implements Exception {
  EasyPNotFoundException(this.valueType);

  final Type valueType;

  @override
  String toString() => 'Error: Could not find the EasyP<$valueType>';
}
複製程式碼
  • build:最後整一個Build類就行了
class EasyPBuilder<T extends ChangeNotifier> extends StatelessWidget {
  const EasyPBuilder(
    this.builder, {
    Key? key,
  }) : super(key: key);

  final Widget Function() builder;

  @override
  Widget build(BuildContext context) {
    EasyP.register<T>(context);
    return builder();
  }
}
複製程式碼

大功告成,上面這三個類,就能起到和Provider一樣的區域性重新整理功能!

重新整理機制一模一樣,絕對沒有吹牛皮!

下面來看看怎麼使用吧!

使用

用法基本和Provider一摸一樣...

  • view
class CounterEasyPPage extends StatelessWidget {
  final easyP = CounterEasyP();

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierEasyP(
      create: (BuildContext context) => easyP,
      child: _buildPage(),
    );
  }

  Widget _buildPage() {
    return Scaffold(
      appBar: AppBar(title: Text('自定義狀態管理框架-EasyP範例')),
      body: Center(
        child: EasyPBuilder<CounterEasyP>(() {
          return Text(
            '點選了 ${easyP.count} 次',
            style: TextStyle(fontSize: 30.0),
          );
        }),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => easyP.increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}
複製程式碼
  • easyP
class CounterEasyP extends ChangeNotifier {
  int count = 0;

  void increment() {
    count++;
    notifyListeners();
  }
}
複製程式碼
  • 效果圖:體驗一下
    • 如果網頁打不開,可能需要你清下瀏覽器快取

easy_p

全域性EasyP

  • 全域性也是可以的,直接把ChangeNotifierEasyP類套在主入口,程式碼就不貼了,給大家看下效果圖

easy_p_global

總結

如果有靚仔的公司,不想使用第三方狀態管理框架,完全可以參照Provider的重新整理機制,擼一個狀態管理框架出來!我上面已經擼了一個極簡版,畫龍畫虎難畫骨,上面我大致把他的骨架整好了;如果有需要的話,發揮你的聰明才智,copy過去給他填充血肉吧。。。

如果大家看懂了Provider的重新整理機制,就會發現Provider狀態框架,對系統資源佔用極低,它僅僅只使用了ChangeNotifier,這僅僅是最基礎的Callback回撥,這會佔用多少資源?重新整理邏輯全是呼叫Flutte的framework層自帶的那些api(獲取InheritedElement的內部操作很簡單,有興趣可以看看)。。。所以完全不用擔心,他會佔用多少資源,幾乎忽略不計!

最後

一本祕籍

寫完整篇文章,我突然感覺自己掌握一本武功祕籍!知道了怎麼去寫出高階大氣上檔次且深奧的專案!

我現在就來傳授給大家...

0B484D36_副本

  • 首先一定要善用面向介面程式設計的思想!
    • 如果要想非常深奧,深奧的自己都難以看懂,那直接濫用這種思想就穩了!
  • 多用各種設計模式,別和我扯什麼簡單易用,老夫寫程式碼,就是設計模式一把梭,不管合適不合適,全懟上面
    • 一定要多用命令模式和訪問者模式,就是要讓自己的函式入參超高度可擴充套件,難以被別人和自己讀懂
    • if else內部邏輯直接拋棄,全用策略模式往上懟
    • 不管內部狀態閉不閉環,狀態模式直接強行閉環
    • for要少用,多用List遍歷,防止別人不懂你的良苦用心,一定在旁註釋:迭代器模式
    • 外觀模式,一般都是做一層外觀吧,我們們直接搞倆層,三層外觀類!代理模式五層代理類起步!
    • 物件或變數不管是不是隻用一次,我們們全都快取起來,將享元模式的思想貫徹到底
    • 變換莫測的就是橋接模式了,一般倆個維度橋接,我們們直接9個維度,俗話說的好,九九八十一難嘛,不是把你繞進去,就是把自己繞起來!頭髮和命,只有一個能活!
    • 所有的類與類絕不強耦合,一定要有中介類橋接,別人要噴你;你就自信的往後一仰,淡淡的說:“迪米特法則,瞭解一下。”
  • 最重要的,要多用Framework層的回撥
    • 不管那個系統回撥我們們懂不懂,都在裡面整點程式碼,假裝很懂
    • 最關鍵的時候,系統抽象類要繼承,多寫點自己的抽象方法,千萬不能寫註釋,不然以後自己看懂了,咋辦?

以上純屬調侃

切勿對號入座進Provider,Provider相關思想用的張弛有度,他所抽象的類,實際在多處實現了不同的實現類,大大的增加了擴充套件;而且他所繼承的系統上下文類裡,所抽象的方法,給了非常詳盡的註釋。

從Provider的原始碼上看,能看出Provider的作者絕對是個高手,必須對framework層有足夠了解,才能寫出那樣精彩的重新整理機制!

這是一個很優秀的框架!

我為啥寫上面這些調侃?ε=(´ο`*)))唉,前人練手,後人抓頭。。。

相關地址

系列文章

相關文章