<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Weiye&#39;s Blog</title>
  <icon>https://www.gravatar.com/avatar/4868655ab178a145c6d43f310310383e</icon>
  <subtitle>好好学习,天天向上</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://yoursite.com/"/>
  <updated>2022-04-01T15:57:33.014Z</updated>
  <id>http://yoursite.com/</id>
  
  <author>
    <name>Weiye Lee</name>
    <email>9011532@qq.com</email>
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Flutter状态管理之路（三）</title>
    <link href="http://yoursite.com/2022/03/30/Flutter%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E4%B9%8B%E8%B7%AF%EF%BC%88%E4%B8%89%EF%BC%89/"/>
    <id>http://yoursite.com/2022/03/30/Flutter状态管理之路（三）/</id>
    <published>2022-03-30T02:13:30.000Z</published>
    <updated>2022-04-01T15:57:33.014Z</updated>
    
    <content type="html"><![CDATA[<p>接上一篇 <a href="https://www.jianshu.com/p/913e111e2232" target="_blank" rel="noopener">Flutter状态管理之路（二）</a>，<br>此篇主要介绍Flutter_Bloc</p><h2 id="Flutter-Bloc"><a href="#Flutter-Bloc" class="headerlink" title="Flutter_Bloc"></a>Flutter_Bloc</h2><p>版本：bloc 3.0.0 flutter_bloc 3.0.0</p><p>库地址：<a href="https://github.com/felangel/bloc" target="_blank" rel="noopener">https://github.com/felangel/bloc</a></p><p>全称为 Business Logic Component,表示为业务逻辑组件,简称 <em>BLoC</em></p><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><table><thead><tr><th>对象</th><th>说明</th></tr></thead><tbody><tr><td>Event</td><td>表示触发某个状态改变的事件</td></tr><tr><td>State</td><td>状态值</td></tr><tr><td>Stream</td><td>用于传输各个时刻的状态值的流</td></tr><tr><td>Bloc</td><td>“Bussiness Logic Component”即业务逻辑组件，响应Action、作出处理、通过stream输出新的状态</td></tr><tr><td>BlocBuilder</td><td>flutter组件，封装Bloc和组件响应State变化更新UI的逻辑</td></tr><tr><td>BlocProvider</td><td>利用DI注入使得子孙组件可以获取其绑定的Bloc</td></tr></tbody></table><h3 id="使用例子"><a href="#使用例子" class="headerlink" title="使用例子"></a>使用例子</h3><p>依然是计数器例子，官方Demo : <a href="https://bloclibrary.dev/#/fluttercountertutorial" target="_blank" rel="noopener">CounterPage</a></p><ol><li>counter_bloc.dart  </li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">enum CounterEvent &#123; increment, decrement &#125;  /// 1. 定义事件</span><br><span class="line"></span><br><span class="line">class CounterBloc extends Bloc&lt;CounterEvent, int&gt; &#123;   /// 2. 定义Bloc</span><br><span class="line">  @override</span><br><span class="line">  int get initialState =&gt; 0;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  Stream&lt;int&gt; mapEventToState(CounterEvent event) async* &#123;</span><br><span class="line">  /// 3. 定义根据Event作出的状态改变响应</span><br><span class="line">    switch (event) &#123; </span><br><span class="line">      case CounterEvent.decrement:</span><br><span class="line">        yield state - 1;</span><br><span class="line">        break;</span><br><span class="line">      case CounterEvent.increment:</span><br><span class="line">        yield state + 1;</span><br><span class="line">        break;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>ps : async* yield 异步生成器语法，用于生成异步序列：Stream</p><ol start="2"><li>counter_page.dart</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">class CounterPage extends StatelessWidget &#123;</span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    final CounterBloc counterBloc = BlocProvider.of&lt;CounterBloc&gt;(context); 4. 子Widget获取</span><br><span class="line"></span><br><span class="line">    return Scaffold(</span><br><span class="line">      appBar: AppBar(title: Text(&apos;Counter&apos;)),</span><br><span class="line">      body: BlocBuilder&lt;CounterBloc, int&gt;(   5. 用BlocBuilder完成状态绑定</span><br><span class="line">        builder: (context, count) &#123;</span><br><span class="line">          return Center(</span><br><span class="line">            child: Text(</span><br><span class="line">              &apos;$count&apos;,</span><br><span class="line">              style: TextStyle(fontSize: 24.0),</span><br><span class="line">            ),</span><br><span class="line">          );</span><br><span class="line">        &#125;,</span><br><span class="line">      ),</span><br><span class="line">      floatingActionButton: Column(</span><br><span class="line">        crossAxisAlignment: CrossAxisAlignment.end,</span><br><span class="line">        mainAxisAlignment: MainAxisAlignment.end,</span><br><span class="line">        children: &lt;Widget&gt;[</span><br><span class="line">          Padding(</span><br><span class="line">            padding: EdgeInsets.symmetric(vertical: 5.0),</span><br><span class="line">            child: FloatingActionButton(</span><br><span class="line">              child: Icon(Icons.add),</span><br><span class="line">              onPressed: () &#123;</span><br><span class="line">                counterBloc.add(CounterEvent.increment);</span><br><span class="line">              &#125;,</span><br><span class="line">            ),</span><br><span class="line">          ),</span><br><span class="line">          Padding(</span><br><span class="line">            padding: EdgeInsets.symmetric(vertical: 5.0),</span><br><span class="line">            child: FloatingActionButton(</span><br><span class="line">              child: Icon(Icons.remove),</span><br><span class="line">              onPressed: () &#123;</span><br><span class="line">                counterBloc.add(CounterEvent.decrement);  /// 6.发出事件</span><br><span class="line">              &#125;,</span><br><span class="line">            ),</span><br><span class="line">          ),</span><br><span class="line">        ],</span><br><span class="line">      ),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li>app.dart</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">class App extends StatelessWidget &#123;</span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    return MaterialApp(</span><br><span class="line">            title: &apos;Flutter Demo&apos;,</span><br><span class="line">            home: BlocProvider(     /// 7. 注入Bloc实例</span><br><span class="line">              create: (context) =&gt; CounterBloc(),</span><br><span class="line">              child: CounterPage(),</span><br><span class="line">            ),</span><br><span class="line">            theme: theme,</span><br><span class="line">          );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="图示"><a href="#图示" class="headerlink" title="图示"></a>图示</h3><p>架构思想:</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-cb194f2933f3f8f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="bloc_architecture.png" title>                </div>                <div class="image-caption">bloc_architecture.png</div>            </figure><p>原理：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-89580c8f8ec4ed07.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="bloc图示1.png" title>                </div>                <div class="image-caption">bloc图示1.png</div>            </figure><h3 id="关键对象"><a href="#关键对象" class="headerlink" title="关键对象"></a>关键对象</h3><h4 id="Bloc"><a href="#Bloc" class="headerlink" title="Bloc"></a>Bloc</h4><p>“Bussiness Logic Component”即业务逻辑组件，响应Action、作出处理、通过stream输出新的状态</p><p>构造方法如下</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">abstract class Bloc&lt;Event, State&gt; extends Stream&lt;State&gt; implements Sink&lt;Event&gt; &#123;</span><br><span class="line">  final PublishSubject&lt;Event&gt; _eventSubject = PublishSubject&lt;Event&gt;();</span><br><span class="line">  BehaviorSubject&lt;State&gt; _stateSubject;</span><br><span class="line">  ...</span><br><span class="line">  Bloc() &#123;</span><br><span class="line">    _stateSubject = BehaviorSubject&lt;State&gt;.seeded(initialState);  /// 初始化状态订阅器</span><br><span class="line">    _bindStateSubject();</span><br><span class="line">  &#125;</span><br><span class="line">  ...</span><br><span class="line">  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>_bindStateSubject方法实现事件流通道处理Event并转接到状态流的过程</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">Stream&lt;State&gt; transformStates(Stream&lt;State&gt; states) =&gt; states;</span><br><span class="line"></span><br><span class="line">Stream&lt;State&gt; transformEvents(</span><br><span class="line">    Stream&lt;Event&gt; events,</span><br><span class="line">    Stream&lt;State&gt; Function(Event) next,</span><br><span class="line">  ) &#123;</span><br><span class="line">    return events.asyncExpand(next);  /// 将events流通道的元素一个个调用next方法并返回State的流</span><br><span class="line">&#125;</span><br><span class="line">  </span><br><span class="line">void _bindStateSubject() &#123;</span><br><span class="line">    Event currentEvent;</span><br><span class="line"></span><br><span class="line">    transformStates(transformEvents(_eventSubject, (Event event) &#123;</span><br><span class="line">      currentEvent = event;</span><br><span class="line">      return mapEventToState(currentEvent).handleError(_handleError);</span><br><span class="line">    &#125;)).forEach(</span><br><span class="line">      (State nextState) &#123;</span><br><span class="line">        if (state == nextState || _stateSubject.isClosed) return;</span><br><span class="line">...</span><br><span class="line">          _stateSubject.add(nextState);  /// 往State流通道加入新的状态元素并触发对应的观察者</span><br><span class="line">        ...</span><br><span class="line">      &#125;,</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>其余关键方法</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">@override</span><br><span class="line"> StreamSubscription&lt;State&gt; listen(    /// 监听状态流</span><br><span class="line">   void Function(State) onData, &#123;</span><br><span class="line">   Function onError,</span><br><span class="line">   void Function() onDone,</span><br><span class="line">   bool cancelOnError,</span><br><span class="line"> &#125;) &#123;</span><br><span class="line">   return _stateSubject.listen(</span><br><span class="line">     onData,</span><br><span class="line">     onError: onError,</span><br><span class="line">     onDone: onDone,</span><br><span class="line">     cancelOnError: cancelOnError,</span><br><span class="line">   );</span><br><span class="line"> &#125;</span><br><span class="line"> </span><br><span class="line"> Future&lt;void&gt; close() async &#123;</span><br><span class="line">   await _eventSubject.close();</span><br><span class="line">   await _stateSubject.close();</span><br><span class="line"> &#125;</span><br><span class="line"> </span><br><span class="line">  @override</span><br><span class="line"> void add(Event event) &#123;</span><br><span class="line">   ...</span><br><span class="line">     _eventSubject.sink.add(event);   /// 往事件流里输入</span><br><span class="line">...</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><h4 id="BlocBuilder"><a href="#BlocBuilder" class="headerlink" title="BlocBuilder"></a>BlocBuilder</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">class BlocBuilder&lt;B extends Bloc&lt;dynamic, S&gt;, S&gt; extends BlocBuilderBase&lt;B, S&gt; &#123;</span><br><span class="line"></span><br><span class="line">  final BlocWidgetBuilder&lt;S&gt; builder;</span><br><span class="line"></span><br><span class="line">  const BlocBuilder(&#123;</span><br><span class="line">    Key key,</span><br><span class="line">    @required this.builder,</span><br><span class="line">    B bloc,</span><br><span class="line">    BlocBuilderCondition&lt;S&gt; condition,</span><br><span class="line">  &#125;)  : assert(builder != null),</span><br><span class="line">        super(key: key, bloc: bloc, condition: condition);</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context, S state) =&gt; builder(context, state);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line">abstract class BlocBuilderBase&lt;B extends Bloc&lt;dynamic, S&gt;, S&gt;</span><br><span class="line">    extends StatefulWidget &#123;</span><br><span class="line">    </span><br><span class="line">const BlocBuilderBase(&#123;Key key, this.bloc, this.condition&#125;) : super(key: key);</span><br><span class="line"></span><br><span class="line">Widget build(BuildContext context, S state);</span><br><span class="line"></span><br><span class="line">    @override</span><br><span class="line">    State&lt;BlocBuilderBase&lt;B, S&gt;&gt; createState() =&gt; _BlocBuilderBaseState&lt;B, S&gt;();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class _BlocBuilderBaseState&lt;B extends Bloc&lt;dynamic, S&gt;, S&gt;</span><br><span class="line">    extends State&lt;BlocBuilderBase&lt;B, S&gt;&gt; &#123;</span><br><span class="line">  StreamSubscription&lt;S&gt; _subscription;</span><br><span class="line">  S _previousState;</span><br><span class="line">  S _state;</span><br><span class="line">  B _bloc;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  void initState() &#123;</span><br><span class="line">    super.initState();</span><br><span class="line">    _bloc = widget.bloc ?? BlocProvider.of&lt;B&gt;(context);</span><br><span class="line">    _previousState = _bloc?.state;</span><br><span class="line">    _state = _bloc?.state;</span><br><span class="line">    /// 订阅</span><br><span class="line">    _subscribe();</span><br><span class="line">  &#125;</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) =&gt; widget.build(context, _state);</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  void dispose() &#123;</span><br><span class="line">  /// 取消订阅</span><br><span class="line">    _unsubscribe();</span><br><span class="line">    super.dispose();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  void _subscribe() &#123;</span><br><span class="line">    if (_bloc != null) &#123;</span><br><span class="line">      _subscription = _bloc.skip(1).listen((S state) &#123;</span><br><span class="line">        if (widget.condition?.call(_previousState, state) ?? true) &#123;  /// 此处condition可作性能优化判断状态改变是否需要出发build</span><br><span class="line">          setState(() &#123;</span><br><span class="line">            _state = state;</span><br><span class="line">          &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">        _previousState = state;</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  void _unsubscribe() &#123;</span><br><span class="line">    if (_subscription != null) &#123;</span><br><span class="line">      _subscription.cancel();</span><br><span class="line">      _subscription = null;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="BlocProvider"><a href="#BlocProvider" class="headerlink" title="BlocProvider"></a>BlocProvider</h4><p>BlocProvider实际是利用了Provider库的DI注入功能，并完成bloc生命周期的管理</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line">class BlocProvider&lt;T extends Bloc&lt;dynamic, dynamic&gt;&gt;</span><br><span class="line">    extends SingleChildStatelessWidget &#123;</span><br><span class="line"> ...</span><br><span class="line">  BlocProvider(&#123;</span><br><span class="line">    Key key,</span><br><span class="line">    @required Create&lt;T&gt; create,</span><br><span class="line">    Widget child,</span><br><span class="line">    bool lazy,</span><br><span class="line">  &#125;) : this._(</span><br><span class="line">          key: key,</span><br><span class="line">          create: create,</span><br><span class="line">          dispose: (_, bloc) =&gt; bloc?.close(),</span><br><span class="line">          child: child,</span><br><span class="line">          lazy: lazy,</span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">   BlocProvider.value(&#123;</span><br><span class="line">    Key key,</span><br><span class="line">    @required T value,</span><br><span class="line">    Widget child,</span><br><span class="line">  &#125;) : this._(</span><br><span class="line">          key: key,</span><br><span class="line">          create: (_) =&gt; value,</span><br><span class="line">          child: child,</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">  BlocProvider._(&#123;</span><br><span class="line">    Key key,</span><br><span class="line">    @required Create&lt;T&gt; create,</span><br><span class="line">    Dispose&lt;T&gt; dispose,</span><br><span class="line">    this.child,</span><br><span class="line">    this.lazy,</span><br><span class="line">  &#125;)  : _create = create,</span><br><span class="line">        _dispose = dispose,</span><br><span class="line">        super(key: key, child: child);</span><br><span class="line"></span><br><span class="line">  static T of&lt;T extends Bloc&lt;dynamic, dynamic&gt;&gt;(BuildContext context) &#123;</span><br><span class="line">...</span><br><span class="line">      return Provider.of&lt;T&gt;(context, listen: false);</span><br><span class="line">  ...</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  Widget buildWithChild(BuildContext context, Widget child) &#123;</span><br><span class="line">    return InheritedProvider&lt;T&gt;(   /// 使用Provider库完成DI注入</span><br><span class="line">      create: _create,</span><br><span class="line">      dispose: _dispose,</span><br><span class="line">      child: child,</span><br><span class="line">      lazy: lazy,</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="进阶"><a href="#进阶" class="headerlink" title="进阶"></a>进阶</h3><p>bloc还提供一系列的辅助方法让我们更好地控制数据流</p><ol><li><p>Transition</p><p>在bloc里的回调，在往状态流通道加入数据前调用，可以获取状态的改变情况</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">abstract class Bloc&lt;Event, State&gt; extends Stream&lt;State&gt; implements Sink&lt;Event&gt; &#123;</span><br><span class="line">...</span><br><span class="line">void onTransition(Transition&lt;Event, State&gt; transition) =&gt; null;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">class Transition&lt;Event, State&gt; &#123;</span><br><span class="line">  final State currentState;</span><br><span class="line">  final Event event;</span><br><span class="line">  final State nextState;</span><br><span class="line"></span><br><span class="line">  const Transition(&#123;</span><br><span class="line">    @required this.currentState,  /// 当前状态</span><br><span class="line">    @required this.event,/// 触发此次状态转换的事件</span><br><span class="line">    @required this.nextState,/// 将要改变成的状态</span><br><span class="line">  &#125;);</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>BlocDelegate</p><p>bloc的全局代理对象，可继承它并覆写相应的回调方法，如下</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">class SimpleBlocDelegate extends BlocDelegate &#123;</span><br><span class="line">  @override</span><br><span class="line">  void onEvent(Bloc bloc, Object event) =&gt; null;  /// 事件监听</span><br><span class="line">  </span><br><span class="line">  @override</span><br><span class="line">  void onTransition(Bloc bloc, Transition transition) =&gt; null;/// 状态改变</span><br><span class="line">  </span><br><span class="line">  @override</span><br><span class="line">  void onError(Bloc bloc, Object error, StackTrace stacktrace) =&gt; null; /// 错误捕捉</span><br><span class="line">  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后注册</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">void main() &#123;</span><br><span class="line">  BlocSupervisor.delegate = SimpleBlocDelegate();  /// BlocSupervisor全局单例</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>MultiBlocProvider</p><p>用来将嵌套的BlocProvider给扁平化(使用了库 <a href="https://pub.dev/packages/nested" target="_blank" rel="noopener">nested</a> )，如：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">BlocProvider&lt;BlocA&gt;(</span><br><span class="line">  create: (BuildContext context) =&gt; BlocA(),</span><br><span class="line">  child: BlocProvider&lt;BlocB&gt;(</span><br><span class="line">    create: (BuildContext context) =&gt; BlocB(),</span><br><span class="line">    child: BlocProvider&lt;BlocC&gt;(</span><br><span class="line">      create: (BuildContext context) =&gt; BlocC(),</span><br><span class="line">      child: ChildA(),</span><br><span class="line">    )</span><br><span class="line">  )</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>变为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">MultiBlocProvider(</span><br><span class="line">  providers: [</span><br><span class="line">    BlocProvider&lt;BlocA&gt;(</span><br><span class="line">      create: (BuildContext context) =&gt; BlocA(),</span><br><span class="line">    ),</span><br><span class="line">    BlocProvider&lt;BlocB&gt;(</span><br><span class="line">      create: (BuildContext context) =&gt; BlocB(),</span><br><span class="line">    ),</span><br><span class="line">    BlocProvider&lt;BlocC&gt;(</span><br><span class="line">      create: (BuildContext context) =&gt; BlocC(),</span><br><span class="line">    ),</span><br><span class="line">  ],</span><br><span class="line">  child: ChildA(),</span><br><span class="line">)</span><br></pre></td></tr></table></figure></li><li><p>BlocListener</p><p>实际是  BlocBuilder的另一种实现变化，应用缓存child的方式，每次setState只触发listener而不重新生成child，可用于拦截状态弹Toast，导航等等操作</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">class BlocListener&lt;B extends Bloc&lt;dynamic, S&gt;, S&gt;</span><br><span class="line">    extends BlocListenerBase&lt;B, S&gt; &#123;</span><br><span class="line">  final Widget child;</span><br><span class="line"></span><br><span class="line">  const BlocListener(&#123;</span><br><span class="line">    Key key,</span><br><span class="line">    @required BlocWidgetListener&lt;S&gt; listener,/// 作用同</span><br><span class="line">    B bloc,/// 关注的bloc，不提供则会自动用泛型of往上找</span><br><span class="line">    BlocListenerCondition&lt;S&gt; condition,     /// 同BlocBuilder里的作用</span><br><span class="line">    this.child,/// 应用缓存child的方式，每次build只触发listener而用同一个child</span><br><span class="line">  &#125;)  : super(</span><br><span class="line">          key: key,</span><br><span class="line">          child: child,</span><br><span class="line">          listener: listener,</span><br><span class="line">          bloc: bloc,</span><br><span class="line">          condition: condition,</span><br><span class="line">        );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>优点：</p><ol><li>事件通道处理事件转换状态，串联状态通道通知外部订阅对象</li><li>可实现局部 按需刷新</li><li>状态源各自Bloc管理，实现分治，同时也可以利用Delegate实现全局的事件管理</li></ol><p>缺点：</p><ol><li>相较于redux，更集中在”分治”的焦点上，但是bloc组件之间缺少有效的通信机制</li><li>缺少Middleware(AOP)模式的有效支持</li><li>如果父组件发生更新，子组件绑定的数据源并未发生变化，仍会导致子的rebuild(可利用缓存child解决)</li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;接上一篇 &lt;a href=&quot;https://www.jianshu.com/p/913e111e2232&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter状态管理之路（二）&lt;/a&gt;，&lt;br&gt;此篇主要介绍Flutter_Bloc&lt;/p&gt;
&lt;h2 
      
    
    </summary>
    
      <category term="Flutter" scheme="http://yoursite.com/categories/Flutter/"/>
    
    
      <category term="flutter" scheme="http://yoursite.com/tags/flutter/"/>
    
  </entry>
  
  <entry>
    <title>Flutter状态管理之路（二）</title>
    <link href="http://yoursite.com/2022/03/30/Flutter%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E4%B9%8B%E8%B7%AF%EF%BC%88%E4%BA%8C%EF%BC%89/"/>
    <id>http://yoursite.com/2022/03/30/Flutter状态管理之路（二）/</id>
    <published>2022-03-30T02:13:30.000Z</published>
    <updated>2022-04-01T15:53:41.966Z</updated>
    
    <content type="html"><![CDATA[<p>接上一篇 <a href="https://www.jianshu.com/p/10a1a16dfff7" target="_blank" rel="noopener">Flutter状态管理之路（一）</a>，主要针对一些三方库来继续聊聊解决方案，介绍scope_model、provider、flutter_redux</p><h2 id="ScopedModel"><a href="#ScopedModel" class="headerlink" title="ScopedModel"></a>ScopedModel</h2><p>版本：1.0.1</p><p>这个库封装的比较简易，看例子就直接上关键源码吧</p><h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><p>如下例子来自官方demo</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">class CounterModel extends Model &#123;</span><br><span class="line">  int _counter = 0;</span><br><span class="line"></span><br><span class="line">  int get counter =&gt; _counter;</span><br><span class="line"></span><br><span class="line">  void increment() &#123;</span><br><span class="line">    // First, increment the counter</span><br><span class="line">    _counter++;</span><br><span class="line">    </span><br><span class="line">    // Then notify all the listeners.</span><br><span class="line">    notifyListeners();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">//在入口处也需要将根组件抱在ScopedModel中，这样就可以正常工作了。</span><br><span class="line">class CounterApp extends StatelessWidget &#123;</span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    return new ScopedModel&lt;CounterModel&gt;(</span><br><span class="line">      model: new CounterModel(),</span><br><span class="line">      child: new Column(children: [</span><br><span class="line">        new ScopedModelDescendant&lt;CounterModel&gt;(</span><br><span class="line">          builder: (context, child, model) =&gt; new Text(&apos;$&#123;model.counter&#125;&apos;),</span><br><span class="line">        ),</span><br><span class="line">        new Text(&quot;Another widget that doesn&apos;t depend on the CounterModel&quot;)</span><br><span class="line">      ])</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="关键对象"><a href="#关键对象" class="headerlink" title="关键对象"></a>关键对象</h3><h4 id="Model"><a href="#Model" class="headerlink" title="Model"></a>Model</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">abstract class Model extends Listenable &#123;</span><br><span class="line">  final Set&lt;VoidCallback&gt; _listeners = Set&lt;VoidCallback&gt;();  /// 注册观察者函数句柄</span><br><span class="line">  int _version = 0;</span><br><span class="line">  int _microtaskVersion = 0;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  void addListener(VoidCallback listener) &#123; /// 添加观察者</span><br><span class="line">    _listeners.add(listener);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  void removeListener(VoidCallback listener) &#123;  /// 去除观察者</span><br><span class="line">    _listeners.remove(listener);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  int get listenerCount =&gt; _listeners.length;</span><br><span class="line"></span><br><span class="line">  @protected</span><br><span class="line">  void notifyListeners() &#123; /// 遍历通知观察者</span><br><span class="line">    if (_microtaskVersion == _version) &#123;</span><br><span class="line">      _microtaskVersion++;</span><br><span class="line">      scheduleMicrotask(() &#123;</span><br><span class="line">        _version++;</span><br><span class="line">        _microtaskVersion = _version;</span><br><span class="line"></span><br><span class="line">        _listeners.toList().forEach((VoidCallback listener) =&gt; listener());</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上，其实就是观察者模式，在子类的setter方法里调用下notify通知订阅的Widget</p><h4 id="ScopedModelDescendant"><a href="#ScopedModelDescendant" class="headerlink" title="ScopedModelDescendant"></a>ScopedModelDescendant</h4><p>用来向上搜索从ScopedModel（继承InheritedWidget）中获取状态，由于InheritedWidget</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">class ScopedModelDescendant&lt;T extends Model&gt; extends StatelessWidget &#123;</span><br><span class="line">  final ScopedModelDescendantBuilder&lt;T&gt; builder;</span><br><span class="line"></span><br><span class="line">  final Widget child;</span><br><span class="line"></span><br><span class="line">  final bool rebuildOnChange;</span><br><span class="line"></span><br><span class="line">  ScopedModelDescendant(&#123;</span><br><span class="line">    @required this.builder,</span><br><span class="line">    this.child,</span><br><span class="line">    this.rebuildOnChange = true,</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    return builder(</span><br><span class="line">      context,</span><br><span class="line">      child,  /// 缓存child机制</span><br><span class="line">      ScopedModel.of&lt;T&gt;(context, rebuildOnChange: rebuildOnChange),  /// 向上获取InheritedWidget来获取状态</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="ScopedModel-1"><a href="#ScopedModel-1" class="headerlink" title="ScopedModel"></a>ScopedModel</h3><p>主要利用AnimatedBuilder和Listenable配合实现通知订阅，里面还使用了自定义的InheritedWidget(即_InheritedModel)来存储Model，向下暴露</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">class ScopedModel&lt;T extends Model&gt; extends StatelessWidget &#123;</span><br><span class="line">  final T model;</span><br><span class="line"></span><br><span class="line">  final Widget child;</span><br><span class="line"></span><br><span class="line">  ScopedModel(&#123;@required this.model, @required this.child&#125;)</span><br><span class="line">      : assert(model != null),</span><br><span class="line">        assert(child != null);</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    return AnimatedBuilder(</span><br><span class="line">      animation: model,</span><br><span class="line">      builder: (context, _) =&gt; _InheritedModel&lt;T&gt;(model: model, child: child),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>优点：</p><ol><li>自动订阅</li><li>可跨组件传递状态</li><li>简单易用，对前端开发者来说学习成本几乎为零</li></ol><p>缺点：</p><ol><li>无法分离视图逻辑和业务逻辑</li><li><code>ScopedModel</code>其实只是将InheritedWidget简单的封装了一下，局限性较大</li></ol><h2 id="Provider"><a href="#Provider" class="headerlink" title="Provider"></a>Provider</h2><p>版本：4.0.1</p><p>一个DI依赖注入和状态管理的框架</p><h3 id="使用-1"><a href="#使用-1" class="headerlink" title="使用"></a>使用</h3><p>来自官方计数器Demo</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> main() =&gt; runApp(MyApp());</span><br><span class="line"></span><br><span class="line"><span class="comment">/// 1.定义状态Model</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Counter</span> <span class="title">with</span> <span class="title">ChangeNotifier</span> </span>&#123;</span><br><span class="line">  <span class="built_in">int</span> _count = <span class="number">0</span>;</span><br><span class="line">  <span class="built_in">int</span> <span class="keyword">get</span> count =&gt; _count;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> increment() &#123;</span><br><span class="line">    _count++;</span><br><span class="line">    notifyListeners();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyApp</span> <span class="keyword">extends</span> <span class="title">StatelessWidget</span> </span>&#123;</span><br><span class="line">  <span class="meta">@override</span></span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    <span class="keyword">return</span> MultiProvider(</span><br><span class="line">      providers: [</span><br><span class="line">        ChangeNotifierProvider(create: (_) =&gt; Counter()), <span class="comment">/// 2. 在树根注入状态</span></span><br><span class="line">      ],</span><br><span class="line">      child: MaterialApp(</span><br><span class="line">            home: Consumer&lt;Counter&gt;(<span class="comment">/// 3. 利用Consumer组件自动获取counter</span></span><br><span class="line">            builder: (context, counter, _) &#123;</span><br><span class="line">              <span class="keyword">return</span> MaterialApp(</span><br><span class="line">                home: <span class="keyword">const</span> MyHomePage(),</span><br><span class="line">              );</span><br><span class="line">            &#125;,</span><br><span class="line">          ),</span><br><span class="line">          )</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyHomePage</span> <span class="keyword">extends</span> <span class="title">StatelessWidget</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> MyHomePage(&#123;Key key&#125;) : <span class="keyword">super</span>(key: key);</span><br><span class="line"></span><br><span class="line">  <span class="meta">@override</span></span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    <span class="keyword">return</span> Scaffold(</span><br><span class="line">      appBar: AppBar(title: Text(<span class="string">""</span>)),</span><br><span class="line">      body: <span class="keyword">const</span> Center(child: CounterLabel()),</span><br><span class="line">      floatingActionButton: <span class="keyword">const</span> IncrementCounterButton(),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">IncrementCounterButton</span> <span class="keyword">extends</span> <span class="title">StatelessWidget</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> IncrementCounterButton(&#123;Key key&#125;) : <span class="keyword">super</span>(key: key);</span><br><span class="line"></span><br><span class="line">  <span class="meta">@override</span></span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    <span class="keyword">return</span> FloatingActionButton(</span><br><span class="line">      onPressed: () &#123;</span><br><span class="line">        Provider.of&lt;Counter&gt;(context, listen: <span class="keyword">false</span>).increment();  <span class="comment">/// 4. 获取使用(不加入监听)</span></span><br><span class="line">      &#125;,</span><br><span class="line">      tooltip: <span class="string">'Increment'</span>,</span><br><span class="line">      child: <span class="keyword">const</span> Icon(Icons.add),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CounterLabel</span> <span class="keyword">extends</span> <span class="title">StatelessWidget</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> CounterLabel(&#123;Key key&#125;) : <span class="keyword">super</span>(key: key);</span><br><span class="line"></span><br><span class="line">  <span class="meta">@override</span></span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    <span class="keyword">final</span> counter = Provider.of&lt;Counter&gt;(context);<span class="comment">/// 5. 获取使用(加入监听)</span></span><br><span class="line">    <span class="keyword">return</span> Column(</span><br><span class="line">      mainAxisSize: MainAxisSize.min,</span><br><span class="line">      mainAxisAlignment: MainAxisAlignment.center,</span><br><span class="line">      children: &lt;Widget&gt;[</span><br><span class="line">        <span class="keyword">const</span> Text(</span><br><span class="line">          <span class="string">'You have pushed the button this many times:'</span>,</span><br><span class="line">        ),</span><br><span class="line">        Text(</span><br><span class="line">          <span class="string">'<span class="subst">$&#123;counter.count&#125;</span>'</span>,</span><br><span class="line">        ),</span><br><span class="line">      ],</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="流程图"><a href="#流程图" class="headerlink" title="流程图"></a>流程图</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-d3b642122b60bcae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="provider流程.png" title>                </div>                <div class="image-caption">provider流程.png</div>            </figure><h3 id="核心实现"><a href="#核心实现" class="headerlink" title="核心实现"></a>核心实现</h3><p>关于Provider的源码实现，这里推荐阅读 <a href="https://book.flutterchina.club/chapter7/provider.html" target="_blank" rel="noopener">跨组件状态共享（Provider）</a>，文章将主要的内容都剥离出来带着实现了一遍</p><h3 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h3><p>优点：</p><ol><li><p>利用child缓存机制优化了InheritedWidget的全量build缺点</p></li><li><p>我们的业务代码更关注数据了，只要更新Model，则UI会自动更新，而不用在状态改变后再去手动调用<code>setState()</code>来显式更新页面。</p></li><li>数据改变的消息传递被屏蔽了，我们无需手动去处理状态改变事件的发布和订阅了，这一切都被封装在Provider中了；不用像eventbus一样自己去定义事件和注册、解注册等</li><li>在大型复杂应用中，尤其是需要全局共享的状态非常多时，使用Provider将会大大简化我们的代码逻辑，降低出错的概率，提高开发效率。</li></ol><p>缺点：</p><ol><li>没有有效解决逻辑和视图解耦的问题</li><li>对于状态的集中和分治管理没有提供有效的方式</li></ol><h2 id="Redux"><a href="#Redux" class="headerlink" title="Redux"></a>Redux</h2><p>版本：flutter_redux 0.6.0、redux 4.0.0</p><p>库：<a href="https://pub.dev/packages/flutter_redux" target="_blank" rel="noopener">flutter_redux</a></p><p>Redux 是一个用来做可预测、易调试的数据管理的框架。 所有对数据的增删改查等操作都由 Redux 来集中负责。</p><p>在了解Redux前，先介绍下Stream</p><h3 id="Stream"><a href="#Stream" class="headerlink" title="Stream"></a>Stream</h3><h4 id="创建方式"><a href="#创建方式" class="headerlink" title="创建方式"></a>创建方式</h4><ol><li><p>通过构造函数</p><p><strong>Stream.fromFuture</strong>:从Future创建新的单订阅流,当future完成时将触发一个data或者error，然后使用Down事件关闭这个流。</p><p><strong>Stream.fromFutures</strong>:从一组Future创建一个单订阅流，每个future都有自己的data或者error事件，当整个Futures完成后，流将会关闭。如果Futures为空，流将会立刻关闭。</p><p><strong>Stream.fromIterable</strong>:创建从一个集合中获取其数据的单订阅流</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Stream.fromIntreable([1,2,3]);</span><br></pre></td></tr></table></figure><p>监听一个流最常见的方法就是listen。当有事件发出时，流将会通知listener。Listen方法提供了这几种触发事件：</p><ul><li>onData(必填)：收到数据时触发</li><li>onError：收到Error时触发</li><li>onDone：结束时触发</li><li>unsubscribeOnError：遇到第一个Error时是否取消订阅，默认为false</li></ul></li><li><p>使用StreamController</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">//任意类型的流</span><br><span class="line">StreamController controller = StreamController();</span><br><span class="line">controller.sink.add(123);</span><br><span class="line">controller.sink.add(&quot;xyz&quot;);</span><br><span class="line">controller.sink.add(Anything);</span><br><span class="line"></span><br><span class="line">//创建一条处理int类型的流</span><br><span class="line">StreamController&lt;int&gt; numController = StreamController();</span><br><span class="line">numController.sink.add(123);</span><br></pre></td></tr></table></figure></li></ol><h4 id="种类"><a href="#种类" class="headerlink" title="种类"></a>种类</h4><ol><li><p>“Single-subscription” streams 单订阅流</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">StreamController controller = StreamController();</span><br><span class="line"></span><br><span class="line">controller.stream.listen((data)=&gt; print(data));</span><br><span class="line">controller.stream.listen((data)=&gt; print(data));</span><br><span class="line"></span><br><span class="line">controller.sink.add(123);</span><br></pre></td></tr></table></figure><p><strong>输出：</strong> Bad state: Stream has already been listened to. <strong>单订阅流不能有多个收听者</strong></p><p>只能有一个监听，并且取消后也不能加入新的；流的发送是在增加监听之后才触发的，所以不会错过事件，如io流</p></li><li><p>“broadcast” streams 多订阅流</p><p>可以增加多个监听，监听器只能监听到添加之后发出的事件，正在和之前发出的均不会收到</p><p>从Stream继承的广播流必须重写isBroadcast 才能返回true</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">StreamController controller = StreamController();</span><br><span class="line">//将单订阅流转化为广播流</span><br><span class="line">Stream stream = controller.stream.asBroadcastStream();</span><br><span class="line"></span><br><span class="line">stream.listen((data)=&gt; print(data));</span><br><span class="line">stream.listen((data)=&gt; print(data));</span><br><span class="line"></span><br><span class="line">controller.sink.add(123);</span><br><span class="line">/// 输出： 123 123</span><br></pre></td></tr></table></figure></li></ol><h4 id="转换方法"><a href="#转换方法" class="headerlink" title="转换方法"></a>转换方法</h4><ol><li><p>流提供了众多操作符map()，where()，expand()，和take()方法，能够轻松将已有的流转化为新的流</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">stream.where((event)&#123;...&#125;)</span><br><span class="line"></span><br><span class="line">StreamController&lt;int&gt; controller = StreamController&lt;int&gt;();</span><br><span class="line"></span><br><span class="line">final transformer = StreamTransformer&lt;int,String&gt;.fromHandlers(</span><br><span class="line">    handleData:(value, sink)&#123;</span><br><span class="line">  if(value==100)&#123;</span><br><span class="line">      sink.add(&quot;你猜对了&quot;);</span><br><span class="line">    &#125;</span><br><span class="line">else&#123; sink.addError(&apos;还没猜中，再试一次吧&apos;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">  </span><br><span class="line">  controller.stream</span><br><span class="line">            .transform(transformer)</span><br><span class="line">            .listen(</span><br><span class="line">                (data) =&gt; print(data),</span><br><span class="line">                onError:(err) =&gt; print(err));</span><br><span class="line">    </span><br><span class="line">    controller.sink.add(23);</span><br></pre></td></tr></table></figure></li></ol><h3 id="三项原则"><a href="#三项原则" class="headerlink" title="三项原则"></a><a href="https://github.com/johnpryan/redux.dart/blob/master/doc/why.md" target="_blank" rel="noopener">三项原则</a></h3><ol><li><p>可信任的单一数据源</p><p>整个应用的状态应该都被存储在一颗状态对象树中(store)</p></li><li><p>状态只读</p><p>每个状态对象都是普通dart对象，并且是Imutation；状态对外只读，要改变state只能通过发出action经由相应的reducer里修改</p></li><li><p>状态只由纯函数更改</p><p>这里的纯函数指的是reducer，接收前一个状态值和action，这里根据上述2个变量生成新的状态值</p></li></ol><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><table><thead><tr><th>对象</th><th>说明</th><th>所属库</th></tr></thead><tbody><tr><td>Store</td><td>状态的载体(仓库)，持有Reducer、State、Middleware</td><td>redux包</td></tr><tr><td>Reducer</td><td>对State进行改变操作的地方，其他地方不能改变，因为State<br>都是Imutaion的</td><td>redux包</td></tr><tr><td>Action</td><td>行为的抽象，标识一种对State改变的行为</td><td>redux包</td></tr><tr><td>State</td><td>影响View树的状态值</td><td>redux包</td></tr><tr><td>StoreProvider</td><td>继承自InheritedWidget，主要用于Store的DI注入</td><td>Flutter_redux包</td></tr><tr><td>StoreConnector</td><td>关联Store，利用Stream和StreamBuilder实现局部刷新，可利用Stream丰富的API进行功能的添加</td><td>Flutter_redux包</td></tr><tr><td>Middleware</td><td>中间件，用于在reducer执行前拦截进行一些操作，<br>调用next则执行下一个中间件直到Reducer</td><td>redux包</td></tr></tbody></table><h3 id="使用例子"><a href="#使用例子" class="headerlink" title="使用例子"></a>使用例子</h3><p>官方Demo: <a href="https://github.com/brianegan/flutter_redux/tree/master/example/counter" target="_blank" rel="noopener">Counter</a></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line">enum Actions &#123; Increment &#125;  /// 1. 定义Action</span><br><span class="line"></span><br><span class="line">int counterReducer(int state, dynamic action) &#123;</span><br><span class="line">  if (action == Actions.Increment) &#123;  /// 2. 创建Reducer</span><br><span class="line">    return state + 1;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  return state;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">void main() &#123;</span><br><span class="line">  final store = Store&lt;int&gt;(counterReducer, initialState: 0);   /// 3. 初始化store</span><br><span class="line"></span><br><span class="line">  runApp(FlutterReduxApp(</span><br><span class="line">    title: &apos;Flutter Redux Demo&apos;,</span><br><span class="line">    store: store,</span><br><span class="line">  ));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class FlutterReduxApp extends StatelessWidget &#123;</span><br><span class="line">  final Store&lt;int&gt; store;</span><br><span class="line">  final String title;</span><br><span class="line"></span><br><span class="line">  FlutterReduxApp(&#123;Key key, this.store, this.title&#125;) : super(key: key);</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    return StoreProvider&lt;int&gt;(    /// 4. 在根Widget注入store</span><br><span class="line">      store: store,</span><br><span class="line">      child: MaterialApp(</span><br><span class="line">        title: title,</span><br><span class="line">        home: Scaffold(</span><br><span class="line">          appBar: AppBar(</span><br><span class="line">            title: Text(title),</span><br><span class="line">          ),</span><br><span class="line">          body: Center(</span><br><span class="line">            child: Column(</span><br><span class="line">              mainAxisAlignment: MainAxisAlignment.center,</span><br><span class="line">              children: [</span><br><span class="line">                Text(</span><br><span class="line">                  &apos;You have pushed the button this many times:&apos;,</span><br><span class="line">                ),</span><br><span class="line">               </span><br><span class="line">                StoreConnector&lt;int, String&gt;(      /// 5. 连接Store和子树</span><br><span class="line">                  converter: (store) =&gt; store.state.toString(),   /// 6. 从store中 取出转换成需要的state</span><br><span class="line">                  builder: (context, count) &#123;                     /// 7. 构建子widget树</span><br><span class="line">                    return Text(</span><br><span class="line">                      count,</span><br><span class="line">                      style: Theme.of(context).textTheme.display1,</span><br><span class="line">                    );</span><br><span class="line">                  &#125;,</span><br><span class="line">                )</span><br><span class="line">              ],</span><br><span class="line">            ),</span><br><span class="line">          ),</span><br><span class="line">         </span><br><span class="line">          floatingActionButton: StoreConnector&lt;int, VoidCallback&gt;(</span><br><span class="line">            converter: (store) &#123;</span><br><span class="line">              return () =&gt; store.dispatch(Actions.Increment);   /// 8. 获取dispatch</span><br><span class="line">            &#125;,</span><br><span class="line">            builder: (context, callback) &#123;</span><br><span class="line">              return FloatingActionButton(</span><br><span class="line">                onPressed: callback,                         /// 9. 发送&quot;加数&quot;的Action</span><br><span class="line">                tooltip: &apos;asdasdasd&apos;,</span><br><span class="line">                child: Icon(Icons.add),</span><br><span class="line">              );</span><br><span class="line">            &#125;,</span><br><span class="line">          ),</span><br><span class="line">        ),</span><br><span class="line">      ),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="图示"><a href="#图示" class="headerlink" title="图示"></a>图示</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-dcd6e2dfd7f65d38.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/800" alt="redux图示1.png" title>                </div>                <div class="image-caption">redux图示1.png</div>            </figure><h3 id="关键对象-1"><a href="#关键对象-1" class="headerlink" title="关键对象"></a>关键对象</h3><h4 id="Store"><a href="#Store" class="headerlink" title="Store"></a>Store</h4><p>主要实现 </p><ol><li>中间件及状态改变(reducer)功能</li><li>利用streamBroadcast 实现订阅功能</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">class Store&lt;State&gt; &#123;</span><br><span class="line">  Reducer&lt;State&gt; reducer;</span><br><span class="line"></span><br><span class="line">  final StreamController&lt;State&gt; _changeController;</span><br><span class="line">  State _state;</span><br><span class="line">  List&lt;NextDispatcher&gt; _dispatchers;</span><br><span class="line"></span><br><span class="line">  Store(</span><br><span class="line">    this.reducer, &#123;</span><br><span class="line">    State initialState,</span><br><span class="line">    List&lt;Middleware&lt;State&gt;&gt; middleware = const [],</span><br><span class="line">    bool syncStream = false,</span><br><span class="line">    bool distinct = false,</span><br><span class="line">  &#125;) : _changeController = StreamController.broadcast(sync: syncStream) &#123;</span><br><span class="line">    _state = initialState;</span><br><span class="line">    _dispatchers = _createDispatchers(  /// 创建链式中间件和Reducer的链式结构</span><br><span class="line">      middleware,</span><br><span class="line">      _createReduceAndNotify(distinct),/// 用NextDispatcher包装reducer</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">/// NextDispatcher定义如下：</span><br><span class="line">typedef dynamic NextDispatcher(dynamic action);</span><br></pre></td></tr></table></figure><ol><li><p><strong>创建链式结构</strong></p><p>将Reducer包装成NextDispatcher，实际利用了闭包来访问reducer</p><pre><code>NextDispatcher _createReduceAndNotify(bool distinct) {    return (dynamic action) {      final state = reducer(_state, action);      if (distinct &amp;&amp; state == _state) return;      _state = state;      _changeController.add(state);  /// 往stream添加事件，会触发流的监听    };  }</code></pre><p>闭包将reducer和middleware处理后生成一个链式的NextDispatcher结构</p><pre><code>List&lt;NextDispatcher&gt; _createDispatchers(    List&lt;Middleware&lt;State&gt;&gt; middleware,    NextDispatcher reduceAndNotify,  ) {    final dispatchers = &lt;NextDispatcher&gt;[]..add(reduceAndNotify);    // Convert each [Middleware] into a [NextDispatcher]    for (var nextMiddleware in middleware.reversed) {      final next = dispatchers.last;    /// 每次取NextDispatcher集合末尾的元素      dispatchers.add(        (dynamic action) =&gt; nextMiddleware(this, action, next),  /// 往集合尾部追加中间件的封装函数(NextDispatcher)      );    }    return dispatchers.reversed.toList();        /// 最后将集合逆序，即先按顺序执行中间件，最后执行Reducer  }</code></pre></li><li><p>Store里有一个dispatch方法，用于发出改变状态的行为</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">dynamic dispatch(dynamic action) &#123;</span><br><span class="line">    return _dispatchers[0](action);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>如上，dispatch时，实际上就是从链头开始走NextDispatcher方法，中间经过middleware和reducer的处理</p></li></ol><h4 id="Reducer"><a href="#Reducer" class="headerlink" title="Reducer"></a>Reducer</h4><p>接收当前状态值和Action，处理后返回一个新的State用于替换当前State</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">typedef State Reducer&lt;State&gt;(State state, dynamic action);</span><br></pre></td></tr></table></figure><h4 id="StoreProvider"><a href="#StoreProvider" class="headerlink" title="StoreProvider"></a>StoreProvider</h4><p>主要实现：</p><ol><li>Store的DI注入</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">class StoreProvider&lt;S&gt; extends InheritedWidget &#123;</span><br><span class="line">  final Store&lt;S&gt; _store;</span><br><span class="line">  ...</span><br><span class="line">  static Store&lt;S&gt; of&lt;S&gt;(BuildContext context, &#123;bool listen = true&#125;) &#123;</span><br><span class="line">    final type = _typeOf&lt;StoreProvider&lt;S&gt;&gt;();</span><br><span class="line">    final provider = (listen</span><br><span class="line">        ? context.inheritFromWidgetOfExactType(type)  /// 从树结构获取Widget并且将context加入监听集合</span><br><span class="line">        : context</span><br><span class="line">            .ancestorInheritedElementForWidgetOfExactType(type)/// 只获取 不加入监听</span><br><span class="line">            ?.widget) as StoreProvider&lt;S&gt;;</span><br><span class="line"></span><br><span class="line">    if (provider == null) throw StoreProviderError(type);</span><br><span class="line"></span><br><span class="line">    return provider._store;</span><br><span class="line">  &#125;</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="StoreConnector"><a href="#StoreConnector" class="headerlink" title="StoreConnector"></a>StoreConnector</h4><p>主要实现：</p><ol><li>从父层级获取Store</li><li>利用Stream和StreamBuilder实现局部刷新，可利用Stream丰富的API进行功能的添加</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">class StoreConnector&lt;S, ViewModel&gt; extends StatelessWidget &#123;</span><br><span class="line">...</span><br><span class="line">@override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    return _StoreStreamListener&lt;S, ViewModel&gt;(</span><br><span class="line">      store: StoreProvider.of&lt;S&gt;(context),   /// 沿树往上搜索Store</span><br><span class="line">      builder: builder,/// 利用ViewModel构建Widget</span><br><span class="line">      converter: converter,/// 将获取的State转为ViewModel</span><br><span class="line">      distinct: distinct,/// 性能优化开关，决定当ViewModel改变时是否利用==去比较以决定是否rebuild</span><br><span class="line">      onInit: onInit,</span><br><span class="line">      onDispose: onDispose,</span><br><span class="line">      rebuildOnChange: rebuildOnChange,</span><br><span class="line">      ignoreChange: ignoreChange,</span><br><span class="line">      onWillChange: onWillChange,</span><br><span class="line">      onDidChange: onDidChange,</span><br><span class="line">      onInitialBuild: onInitialBuild,</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">...  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line">class _StoreStreamListener&lt;S, ViewModel&gt; extends StatefulWidget &#123;</span><br><span class="line">...</span><br><span class="line"> @override</span><br><span class="line">  State&lt;StatefulWidget&gt; createState() &#123;</span><br><span class="line">    return _StoreStreamListenerState&lt;S, ViewModel&gt;();</span><br><span class="line">  &#125;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class _StoreStreamListenerState&lt;S, ViewModel&gt;</span><br><span class="line">    extends State&lt;_StoreStreamListener&lt;S, ViewModel&gt;&gt; &#123;</span><br><span class="line">...</span><br><span class="line">    @override</span><br><span class="line">  void initState() &#123;</span><br><span class="line">...</span><br><span class="line">    latestValue = widget.converter(widget.store);  /// 将state转换成ViewModel</span><br><span class="line">    _createStream();/// 创建监听流</span><br><span class="line">...</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">   void _createStream() &#123;</span><br><span class="line">    stream = widget.store.onChange  /// 取出Store的流</span><br><span class="line">        .where(_ignoreChange)</span><br><span class="line">        .map(_mapConverter)/// 这里执行传进来的convert方法进行转换</span><br><span class="line">        .where(_whereDistinct)/// 性能优化，与distinct相关,</span><br><span class="line">        .transform(StreamTransformer.fromHandlers(handleData: _handleChange)); ///生成ViewModel的流</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  /// 这里实际就是将State转为ViewModel流的地方</span><br><span class="line">   void _handleChange(ViewModel vm, EventSink&lt;ViewModel&gt; sink) &#123;</span><br><span class="line">    if (widget.onWillChange != null) &#123;</span><br><span class="line">      widget.onWillChange(latestValue, vm);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    latestValue = vm;</span><br><span class="line"></span><br><span class="line">    if (widget.onDidChange != null) &#123;</span><br><span class="line">      WidgetsBinding.instance.addPostFrameCallback((_) &#123;</span><br><span class="line">        widget.onDidChange(latestValue);</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    sink.add(vm);</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">   @override</span><br><span class="line">  void didUpdateWidget(_StoreStreamListener&lt;S, ViewModel&gt; oldWidget) &#123;</span><br><span class="line">  /// InheritedWidget 的依赖情况改变时会调用，判断是否需要重新关联新的流</span><br><span class="line">    latestValue = widget.converter(widget.store);</span><br><span class="line"></span><br><span class="line">    if (widget.store != oldWidget.store) &#123;</span><br><span class="line">      _createStream();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    super.didUpdateWidget(oldWidget);</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">   @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">  /// 使用官方StreamBuilder组件来监听转换后ViewModel流</span><br><span class="line">    return widget.rebuildOnChange</span><br><span class="line">        ? StreamBuilder&lt;ViewModel&gt;(</span><br><span class="line">            stream: stream,</span><br><span class="line">            builder: (context, snapshot) =&gt; widget.builder(</span><br><span class="line">              context,</span><br><span class="line">              latestValue,</span><br><span class="line">            ),</span><br><span class="line">          )</span><br><span class="line">        : widget.builder(context, latestValue);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意：</p><ol><li>上述Build方法里的rebuildOnChange：true 则会启用流监听，否则只是普通的widget构建，更新只在InheritedWidget依赖改变或者上层build时才触发rebuild</li><li>因为整个State是一个stream出来的，每次store的state改变均会发出事件，widget.distinct，这个开启后，便可实现某个单独的Connector只有在依赖的ViewModel改变时才会重新build</li></ol><p>实际上利用InherityWidget实现Store的DI注入，</p><h3 id="工具方法"><a href="#工具方法" class="headerlink" title="工具方法"></a>工具方法</h3><p>在redux包里有一个工具方法集合 位于：src/utils.dart</p><ol><li>combineReducers，用来合并Reducer</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">/// 利用闭包 包装多个Reducer为一个Reducer</span><br><span class="line">Reducer&lt;State&gt; combineReducers&lt;State&gt;(Iterable&lt;Reducer&lt;State&gt;&gt; reducers) &#123;</span><br><span class="line">  return (State state, dynamic action) &#123;</span><br><span class="line">    for (final reducer in reducers) &#123;</span><br><span class="line">      state = reducer(state, action);</span><br><span class="line">    &#125;</span><br><span class="line">    return state;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li><p>TypedMiddleware&lt;State, Action&gt; ，用来根据action过滤执行指定的中间件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">final todosReducer = combineReducers&lt;List&lt;Todo&gt;&gt;([</span><br><span class="line">  TypedReducer&lt;List&lt;Todo&gt;, AddTodoAction&gt;(_addTodo),</span><br><span class="line">  TypedReducer&lt;List&lt;Todo&gt;, DeleteTodoAction&gt;(_deleteTodo),</span><br><span class="line">  ...</span><br><span class="line">]);</span><br></pre></td></tr></table></figure></li><li><p>TypedReducer&lt;State, Action&gt; ，用来根据action过滤执行指定的reducer</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line">    TypedMiddleware&lt;AppState, LoadTodosAction&gt;(loadTodos),</span><br><span class="line">    TypedMiddleware&lt;AppState, AddTodoAction&gt;(saveTodos),</span><br><span class="line">    ...</span><br><span class="line">  ];</span><br></pre></td></tr></table></figure></li></ol><h3 id="总结-2"><a href="#总结-2" class="headerlink" title="总结"></a>总结</h3><h4 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h4><ol><li>一颗状态树对应用状态进去集权统一管理，方便对状态改变log、序列化、持久化、测试、撤销重做、时光轴回放</li><li>状态均为immutation，每次的改变均生成一个新的，防止副作用产生</li><li>状态的改变不管从view还是网络 均必须通过发出action，在reducer里修改，保证了无竞争问题</li><li>追踪问题方便，可打印某一时刻的状态树，直观分析出异常状态节点</li><li>支持面向切面AOP编程的中间件，中间件定义是可插拔，可叠加，但不会改变事件流程</li></ol><h4 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h4><ol><li>Redux 的集中和 Component 的分治之间的矛盾</li><li>Redux 的 Reducer 需要一层层手动组装，带来的繁琐性和易错性</li><li>代码结构侵入性较大</li><li>如果父组件发生更新，子组件绑定的数据源并未发生变化，仍会导致子的rebuild(可利用缓存child解决)</li></ol><h3 id="辅助库"><a href="#辅助库" class="headerlink" title="辅助库"></a>辅助库</h3><ol><li><p><a href="https://pub.dev/packages/redux_thunk#-example-tab-" target="_blank" rel="noopener">redux_thunk</a></p><p>通过定义中间件拦截 指定的 Function，以实现异步操作然后dispatch改变state的action去刷新</p></li><li><p><a href="https://pub.dev/packages/flutter_redux_dev_tools" target="_blank" rel="noopener">flutter_redux_dev_tools</a> : Time Travel UI，可和React.js一样实现时间旅行</p></li></ol><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ol><li><a href="https://github.com/brianegan/flutter_architecture_samples/tree/master/redux" target="_blank" rel="noopener">Todo app</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;接上一篇 &lt;a href=&quot;https://www.jianshu.com/p/10a1a16dfff7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter状态管理之路（一）&lt;/a&gt;，主要针对一些三方库来继续聊聊解决方案，介绍scope_mode
      
    
    </summary>
    
      <category term="Flutter" scheme="http://yoursite.com/categories/Flutter/"/>
    
    
      <category term="flutter" scheme="http://yoursite.com/tags/flutter/"/>
    
  </entry>
  
  <entry>
    <title>Flutter状态管理之路（五）</title>
    <link href="http://yoursite.com/2022/03/30/Flutter%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E4%B9%8B%E8%B7%AF%EF%BC%88%E4%BA%94%EF%BC%89/"/>
    <id>http://yoursite.com/2022/03/30/Flutter状态管理之路（五）/</id>
    <published>2022-03-30T02:13:30.000Z</published>
    <updated>2022-04-01T15:58:35.413Z</updated>
    
    <content type="html"><![CDATA[<p>接上一篇<a href="https://www.jianshu.com/p/6885b087f872" target="_blank" rel="noopener">Flutter状态管理之路（四）</a><br>此篇主要介绍flutter_mobx</p><h2 id="Fish-Redux"><a href="#Fish-Redux" class="headerlink" title="Fish Redux"></a>Fish Redux</h2><p>版本：0.2.7</p><p>库地址：<a href="https://github.com/alibaba/fish-redux/" target="_blank" rel="noopener">https://github.com/alibaba/fish-redux/</a></p><h3 id="演进过程"><a href="#演进过程" class="headerlink" title="演进过程"></a>演进过程</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-a5a7f289a3cef5bc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/540" alt="闲鱼fish_redux演进过程1.png" title>                </div>                <div class="image-caption">闲鱼fish_redux演进过程1.png</div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-84f8f0fa99a89d59.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/540" alt="闲鱼fish_redux演进过程2.png" title>                </div>                <div class="image-caption">闲鱼fish_redux演进过程2.png</div>            </figure><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><table><thead><tr><th>对象</th><th>说明</th><th>所属库</th></tr></thead><tbody><tr><td>Action</td><td>表示一种意图,包含两个字段<br>type,payload</td><td></td></tr><tr><td>Connector</td><td>表达了如何从一个大数据中读取小数据，<br>同时对小数据的修改如何同步给大数据，这样的数据连接关系</td><td></td></tr><tr><td>Reducer</td><td>一个上下文无关的 pure function</td><td></td></tr><tr><td>State</td><td>状态值</td><td></td></tr><tr><td>Middleware</td><td>中间件，以AOP面向切面形式注入逻辑</td><td></td></tr><tr><td>Component</td><td>对视图展现和逻辑功能的封装</td><td></td></tr><tr><td>Effect</td><td>处理Action的副作用</td><td></td></tr><tr><td>Dependent</td><td>表达了小组件\</td><td>小适配器是如何连接到 大的Component 的</td><td></td></tr><tr><td>Page</td><td>继承Component，针对页面级的抽象，内置一个Store（子Component共享）</td></tr></tbody></table><h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><p>例子来源官方 <a href="https://github.com/alibaba/fish-redux/tree/master/example" target="_blank" rel="noopener">Todos</a></p><ol><li><p>入口路由配置</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line">/// 创建应用的根 Widget</span><br><span class="line">/// 1. 创建一个简单的路由，并注册页面</span><br><span class="line">/// 2. 对所需的页面进行和 AppStore 的连接</span><br><span class="line">/// 3. 对所需的页面进行 AOP 的增强</span><br><span class="line">Widget createApp() &#123;</span><br><span class="line">  final AbstractRoutes routes = PageRoutes(</span><br><span class="line">    pages: &lt;String, Page&lt;Object, dynamic&gt;&gt;&#123;</span><br><span class="line">      /// 注册TodoList主页面</span><br><span class="line">      &apos;todo_list&apos;: ToDoListPage(),</span><br><span class="line"></span><br><span class="line">    &#125;,</span><br><span class="line">    visitor: (String path, Page&lt;Object, dynamic&gt; page) &#123;</span><br><span class="line">      /// 只有特定的范围的 Page 才需要建立和 AppStore 的连接关系</span><br><span class="line">      /// 满足 Page&lt;T&gt; ，T 是 GlobalBaseState 的子类</span><br><span class="line">      if (page.isTypeof&lt;GlobalBaseState&gt;()) &#123;</span><br><span class="line">        /// 建立 AppStore 驱动 PageStore 的单向数据连接</span><br><span class="line">        /// 1. 参数1 AppStore</span><br><span class="line">        /// 2. 参数2 当 AppStore.state 变化时, PageStore.state 该如何变化</span><br><span class="line">        page.connectExtraStore&lt;GlobalState&gt;(GlobalStore.store,</span><br><span class="line">            (Object pagestate, GlobalState appState) &#123;</span><br><span class="line">          /// 根据appState变化pagestate</span><br><span class="line">          return pagestate;</span><br><span class="line">        &#125;);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      /// AOP</span><br><span class="line">      /// 页面可以有一些私有的 AOP 的增强， 但往往会有一些 AOP 是整个应用下，所有页面都会有的。</span><br><span class="line">      /// 这些公共的通用 AOP ，通过遍历路由页面的形式统一加入。</span><br><span class="line">      page.enhancer.append(</span><br><span class="line">        ...</span><br><span class="line"></span><br><span class="line">        /// Store AOP</span><br><span class="line">        middleware: &lt;Middleware&lt;dynamic&gt;&gt;[</span><br><span class="line">          logMiddleware&lt;dynamic&gt;(tag: page.runtimeType.toString()),</span><br><span class="line">        ],</span><br><span class="line">      );</span><br><span class="line">    &#125;,</span><br><span class="line">  );</span><br><span class="line"></span><br><span class="line">  return MaterialApp(</span><br><span class="line">    title: &apos;Fluro&apos;,</span><br><span class="line">    home: routes.buildPage(&apos;todo_list&apos;, null),</span><br><span class="line">    onGenerateRoute: (RouteSettings settings) &#123;</span><br><span class="line">      return MaterialPageRoute&lt;Object&gt;(builder: (BuildContext context) &#123;</span><br><span class="line">        return routes.buildPage(settings.name, settings.arguments);</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;,</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>新建Page</p></li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">class ToDoListPage extends Page&lt;PageState, Map&lt;String, dynamic&gt;&gt; &#123;</span><br><span class="line">  ToDoListPage()</span><br><span class="line">      : super(</span><br><span class="line">          initState: initState,</span><br><span class="line">          effect: buildEffect(),</span><br><span class="line">          reducer: buildReducer(),</span><br><span class="line">          view: buildView,</span><br><span class="line">        );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li>定义state</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">class PageState extends MutableSource</span><br><span class="line">    implements GlobalBaseState, Cloneable&lt;PageState&gt; &#123;</span><br><span class="line">  List&lt;ToDoState&gt; toDos;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  Color themeColor;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  PageState clone() &#123;</span><br><span class="line">    return PageState()</span><br><span class="line">      ..toDos = toDos</span><br><span class="line">      ..themeColor = themeColor;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  Object getItemData(int index) =&gt; toDos[index];</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  String getItemType(int index) =&gt; &apos;toDo&apos;;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  int get itemCount =&gt; toDos?.length ?? 0;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  void setItemData(int index, Object data) =&gt; toDos[index] = data;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">PageState initState(Map&lt;String, dynamic&gt; args) &#123;</span><br><span class="line">  //just demo, do nothing here...</span><br><span class="line">  return PageState();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="4"><li>定义Action</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">enum PageAction &#123; initToDos, onAdd &#125;</span><br><span class="line"></span><br><span class="line">class PageActionCreator &#123;</span><br><span class="line">  static Action initToDosAction(List&lt;ToDoState&gt; toDos) &#123;</span><br><span class="line">    return Action(PageAction.initToDos, payload: toDos);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  static Action onAddAction() &#123;</span><br><span class="line">    return const Action(PageAction.onAdd);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="5"><li>定义Reducer</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">Reducer&lt;PageState&gt; buildReducer() &#123;</span><br><span class="line">  return asReducer(</span><br><span class="line">    &lt;Object, Reducer&lt;PageState&gt;&gt;&#123;PageAction.initToDos: _initToDosReducer&#125;,</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">PageState _initToDosReducer(PageState state, Action action) &#123;</span><br><span class="line">  final List&lt;ToDoState&gt; toDos = action.payload ?? &lt;ToDoState&gt;[];</span><br><span class="line">  final PageState newState = state.clone();</span><br><span class="line">  newState.toDos = toDos;</span><br><span class="line">  return newState;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="6"><li>定义Effect</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">Effect&lt;PageState&gt; buildEffect() &#123;</span><br><span class="line">  return combineEffects(&lt;Object, Effect&lt;PageState&gt;&gt;&#123;</span><br><span class="line">    Lifecycle.initState: _init,</span><br><span class="line">    PageAction.onAdd: _onAdd,</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">void _init(Action action, Context&lt;PageState&gt; ctx) &#123;</span><br><span class="line">  final List&lt;ToDoState&gt; initToDos = &lt;ToDoState&gt;[];</span><br><span class="line">  /// 可作网络/IO等耗时操作</span><br><span class="line">  ctx.dispatch(PageActionCreator.initToDosAction(initToDos));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">void _onAdd(Action action, Context&lt;PageState&gt; ctx) &#123;</span><br><span class="line">  Navigator.of(ctx.context)</span><br><span class="line">      .pushNamed(&apos;todo_edit&apos;, arguments: null)</span><br><span class="line">      .then((dynamic toDo) &#123;</span><br><span class="line">    if (toDo != null &amp;&amp;</span><br><span class="line">        (toDo.title?.isNotEmpty == true || toDo.desc?.isNotEmpty == true)) &#123;</span><br><span class="line">      ctx.dispatch(list_action.ToDoListActionCreator.add(toDo));</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="7"><li>定义View视图</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">Widget buildView(PageState state, Dispatch dispatch, ViewService viewService) &#123;</span><br><span class="line">  return Scaffold(</span><br><span class="line">    appBar: AppBar(</span><br><span class="line">      backgroundColor: state.themeColor,  /// 获取state状态</span><br><span class="line">      title: const Text(&apos;ToDoList&apos;),</span><br><span class="line">    ),</span><br><span class="line">    body: Container(</span><br><span class="line">      child: Column(</span><br><span class="line">        children: &lt;Widget&gt;[</span><br><span class="line">        </span><br><span class="line">        ],</span><br><span class="line">      ),</span><br><span class="line">    ),</span><br><span class="line">    floatingActionButton: FloatingActionButton(</span><br><span class="line">      onPressed: () =&gt; dispatch(PageActionCreator.onAddAction()),  /// 发出意图改变状态</span><br><span class="line">      tooltip: &apos;Add&apos;,</span><br><span class="line">      child: const Icon(Icons.add),</span><br><span class="line">    ),</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>组装子Component</strong></p><ol><li>定义state</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">class ReportState implements Cloneable&lt;ReportState&gt; &#123;</span><br><span class="line">  int total;</span><br><span class="line">  int done;</span><br><span class="line"></span><br><span class="line">  ReportState(&#123;this.total = 0, this.done = 0&#125;);</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  ReportState clone() &#123;</span><br><span class="line">    return ReportState()</span><br><span class="line">      ..total = total</span><br><span class="line">      ..done = done;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>定义Component</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">class ReportComponent extends Component&lt;ReportState&gt; &#123;</span><br><span class="line">  ReportComponent()</span><br><span class="line">      : super(</span><br><span class="line">          view: buildView,</span><br><span class="line">        );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li>定义视图</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">Widget buildView(</span><br><span class="line">  ReportState state,</span><br><span class="line">  Dispatch dispatch,</span><br><span class="line">  ViewService viewService,</span><br><span class="line">) &#123;</span><br><span class="line">  return Container(</span><br><span class="line">      margin: const EdgeInsets.all(8.0),</span><br><span class="line">      padding: const EdgeInsets.all(8.0),</span><br><span class="line">      color: Colors.blue,</span><br><span class="line">      child: Row(</span><br><span class="line">        children: &lt;Widget&gt;[</span><br><span class="line">          Container(</span><br><span class="line">            child: const Icon(Icons.report),</span><br><span class="line">            margin: const EdgeInsets.only(right: 8.0),</span><br><span class="line">          ),</span><br><span class="line">          Text(</span><br><span class="line">            &apos;Total $&#123;state.total&#125; tasks, $&#123;state.done&#125; done.&apos;,</span><br><span class="line">            style: const TextStyle(fontSize: 18.0, color: Colors.white),</span><br><span class="line">          )</span><br><span class="line">        ],</span><br><span class="line">      ));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="4"><li>定义Connector来连接父子Component</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">class ReportConnector extends ConnOp&lt;PageState, ReportState&gt;</span><br><span class="line">    with ReselectMixin&lt;PageState, ReportState&gt; &#123;</span><br><span class="line">  @override</span><br><span class="line">  ReportState computed(PageState state) &#123;</span><br><span class="line">    return ReportState()</span><br><span class="line">      ..done = state.toDos.where((ToDoState tds) =&gt; tds.isDone).length</span><br><span class="line">      ..total = state.toDos.length;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  void set(PageState state, ReportState subState) &#123;</span><br><span class="line">    throw Exception(&apos;Unexcepted to set PageState from ReportState&apos;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="5"><li>page中使用，改造page如下</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">class ToDoListPage extends Page&lt;PageState, Map&lt;String, dynamic&gt;&gt; &#123;</span><br><span class="line">  ToDoListPage()</span><br><span class="line">      : super(</span><br><span class="line">          initState: initState,</span><br><span class="line">          effect: buildEffect(),</span><br><span class="line">          reducer: buildReducer(),</span><br><span class="line">          view: buildView,</span><br><span class="line">          dependencies: Dependencies&lt;PageState&gt;(</span><br><span class="line">              slots: &lt;String, Dependent&lt;PageState&gt;&gt;&#123;</span><br><span class="line">                &apos;report&apos;: ReportConnector() + ReportComponent()</span><br><span class="line">              &#125;),</span><br><span class="line">        );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="6"><li>page的buildView改造如下</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">Widget buildView(PageState state, Dispatch dispatch, ViewService viewService) &#123;</span><br><span class="line">  final ListAdapter adapter = viewService.buildAdapter();</span><br><span class="line">  return Scaffold(</span><br><span class="line">    appBar: AppBar(</span><br><span class="line">      backgroundColor: state.themeColor,</span><br><span class="line">      title: const Text(&apos;ToDoList&apos;),</span><br><span class="line">    ),</span><br><span class="line">    body: Container(</span><br><span class="line">      child: Column(</span><br><span class="line">        children: &lt;Widget&gt;[</span><br><span class="line">          viewService.buildComponent(&apos;report&apos;),   /// 加载子Component</span><br><span class="line">        ],</span><br><span class="line">      ),</span><br><span class="line">    ),</span><br><span class="line">    floatingActionButton: FloatingActionButton(</span><br><span class="line">      onPressed: () =&gt; dispatch(PageActionCreator.onAddAction()),</span><br><span class="line">      tooltip: &apos;Add&apos;,</span><br><span class="line">      child: const Icon(Icons.add),</span><br><span class="line">    ),</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="关键对象"><a href="#关键对象" class="headerlink" title="关键对象"></a>关键对象</h3><h4 id="Middleware"><a href="#Middleware" class="headerlink" title="Middleware"></a>Middleware</h4><p>StoreMiddleware，实际是对Store的dispatch函数进行加强</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-1c16f54633d7a18c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="store_middleware.png" title>                </div>                <div class="image-caption">store_middleware.png</div>            </figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">/// fish-redux-master/lib/src/redux/apply_middleware.dart</span><br><span class="line">StoreEnhancer&lt;T&gt; applyMiddleware&lt;T&gt;(List&lt;Middleware&lt;T&gt;&gt; middleware) &#123;</span><br><span class="line">  return middleware == null || middleware.isEmpty</span><br><span class="line">      ? null</span><br><span class="line">      : (StoreCreator&lt;T&gt; creator) =&gt; (T initState, Reducer&lt;T&gt; reducer) &#123;</span><br><span class="line">            final Store&lt;T&gt; store = creator(initState, reducer);</span><br><span class="line">            final Dispatch initialValue = store.dispatch;   /// 原始的dispatch</span><br><span class="line">            store.dispatch = middleware</span><br><span class="line">                .map((Middleware&lt;T&gt; middleware) =&gt; middleware(   /// 执行middleware最外层函数返回Composable&lt;T&gt;，此函数在这里用于对dispatch包装</span><br><span class="line">                      dispatch: (Action action) =&gt; store.dispatch(action),</span><br><span class="line">                      getState: store.getState,</span><br><span class="line">                    ))</span><br><span class="line">                .fold(</span><br><span class="line">                  initialValue,</span><br><span class="line">                  (Dispatch previousValue,</span><br><span class="line">                          Dispatch Function(Dispatch) element) =&gt;</span><br><span class="line">                      element(previousValue),/// 每次将上一个dispatch传入，返回一个新的dispatch，利用闭包,新的dispatch持有了上一个dispatch的引用</span><br><span class="line">                );</span><br><span class="line"></span><br><span class="line">            return store;</span><br><span class="line">          &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中某一个Middleware示例如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">Middleware&lt;T&gt; logMiddleware&lt;T&gt;(&#123;</span><br><span class="line">  String tag = &apos;redux&apos;,</span><br><span class="line">  String Function(T) monitor,</span><br><span class="line">&#125;) &#123;</span><br><span class="line">  return (&#123;Dispatch dispatch, Get&lt;T&gt; getState&#125;) &#123;</span><br><span class="line">    return (Dispatch next) &#123;   /// 此方法在上一个示意代码段里，是fold方法里的element</span><br><span class="line">      return isDebug()</span><br><span class="line">          ? (Action action) &#123;   /// 返回包装的Dispatch</span><br><span class="line">              print(&apos;---------- [$tag] ----------&apos;);</span><br><span class="line">              print(&apos;[$tag] $&#123;action.type&#125; $&#123;action.payload&#125;&apos;);</span><br><span class="line"></span><br><span class="line">              final T prevState = getState();</span><br><span class="line">              if (monitor != null) &#123;</span><br><span class="line">                print(&apos;[$tag] prev-state: $&#123;monitor(prevState)&#125;&apos;);</span><br><span class="line">              &#125;</span><br><span class="line"></span><br><span class="line">              next(action);</span><br><span class="line"></span><br><span class="line">              final T nextState = getState();</span><br><span class="line">              if (monitor != null) &#123;</span><br><span class="line">                print(&apos;[$tag] next-state: $&#123;monitor(nextState)&#125;&apos;);</span><br><span class="line">              &#125;</span><br><span class="line">            &#125;</span><br><span class="line">          : next;</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="全局Store"><a href="#全局Store" class="headerlink" title="全局Store"></a>全局Store</h4><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-41484d14b9670611.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="fish_redux_全局store时序图.png" title>                </div>                <div class="image-caption">fish_redux_全局store时序图.png</div>            </figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">page.connectExtraStore&lt;GlobalState&gt;(GlobalStore.store,</span><br><span class="line">           (Object pagestate, GlobalState appState) &#123;</span><br><span class="line">         final GlobalBaseState p = pagestate;</span><br><span class="line">         if (p.themeColor != appState.themeColor) &#123;</span><br><span class="line">           if (pagestate is Cloneable) &#123;</span><br><span class="line">             final Object copy = pagestate.clone();</span><br><span class="line">             final GlobalBaseState newState = copy;</span><br><span class="line">             newState.themeColor = appState.themeColor;</span><br><span class="line">             return newState;</span><br><span class="line">           &#125;</span><br><span class="line">         &#125;</span><br><span class="line">         return pagestate;</span><br><span class="line">       &#125;);</span><br></pre></td></tr></table></figure><h4 id="Connector"><a href="#Connector" class="headerlink" title="Connector"></a>Connector</h4><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-f45ee7e46f9e7209.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="fish_redux_connector_reducer.png" title>                </div>                <div class="image-caption">fish_redux_connector_reducer.png</div>            </figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">abstract class MutableConn&lt;T, P&gt; implements AbstractConnector&lt;T, P&gt; &#123;</span><br><span class="line">  const MutableConn();</span><br><span class="line"></span><br><span class="line">  void set(T state, P subState);</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  SubReducer&lt;T&gt; subReducer(Reducer&lt;P&gt; reducer) &#123;</span><br><span class="line">  /// 将本Component的reducer包装成新的reducer给父的store注入</span><br><span class="line">    return (T state, Action action, bool isStateCopied) &#123;</span><br><span class="line">      final P props = get(state);</span><br><span class="line">      if (props == null) &#123;</span><br><span class="line">        return state;</span><br><span class="line">      &#125;</span><br><span class="line">      final P newProps = reducer(props, action);  /// 调用本Component的reducer，返回子的state</span><br><span class="line">      final bool hasChanged = newProps != props;</span><br><span class="line">      final T copy = (hasChanged &amp;&amp; !isStateCopied) ? _clone&lt;T&gt;(state) : state;</span><br><span class="line">      if (hasChanged) &#123;</span><br><span class="line">        set(copy, newProps);  /// 通知父Component同步状态</span><br><span class="line">      &#125;</span><br><span class="line">      return copy;</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其余详见官方文档:<a href="https://github.com/alibaba/fish-redux/blob/master/doc/README-cn.md" target="_blank" rel="noopener">https://github.com/alibaba/fish-redux/blob/master/doc/README-cn.md</a></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>优点：</p><ol><li>每个Page一个Store,子Component共享其Store，单个Component仍拥有redux的特性以实现分治</li><li>子的reducer自动合并，与page的store自动进行数据同步</li><li>利用eventbus 建立page之间的联系，通过broadcast effect来分发page自身不关心的Action给其它page</li><li>可以全局共享状态，定义一个全局Store在用page.connectExtraStore关联</li></ol><p>缺点：</p><ol><li>概念较多，学习曲线较高</li><li>需要定义的各类对象多、文件多</li><li>对项目规模把握不到位容易引入不必要的复杂度</li><li>代码结构侵入性较大</li></ol><h3 id="未完待续"><a href="#未完待续" class="headerlink" title="未完待续"></a>未完待续</h3><p>fish_redux框架定义的概念很多，还需要继续深入…</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ol><li><a href="https://juejin.im/post/5da421b96fb9a04de6513193#heading-0" target="_blank" rel="noopener">手把手入门Fish-Redux开发flutter</a></li><li><a href="https://yq.aliyun.com/articles/703396?spm=a2c4e.11153940.0.0.61e9145dnXsrR5" target="_blank" rel="noopener">Connector的实现原理</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;接上一篇&lt;a href=&quot;https://www.jianshu.com/p/6885b087f872&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter状态管理之路（四）&lt;/a&gt;&lt;br&gt;此篇主要介绍flutter_mobx&lt;/p&gt;
&lt;h2 id
      
    
    </summary>
    
      <category term="Flutter" scheme="http://yoursite.com/categories/Flutter/"/>
    
    
      <category term="flutter" scheme="http://yoursite.com/tags/flutter/"/>
    
  </entry>
  
  <entry>
    <title>Flutter状态管理之路（四）</title>
    <link href="http://yoursite.com/2022/03/30/Flutter%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E4%B9%8B%E8%B7%AF%EF%BC%88%E5%9B%9B%EF%BC%89/"/>
    <id>http://yoursite.com/2022/03/30/Flutter状态管理之路（四）/</id>
    <published>2022-03-30T02:13:30.000Z</published>
    <updated>2022-04-01T15:58:07.600Z</updated>
    
    <content type="html"><![CDATA[<p>接上一篇<a href="https://www.jianshu.com/p/6fae341cb856" target="_blank" rel="noopener">Flutter状态管理之路（三）</a><br>此篇主要介绍flutter_mobx</p><h2 id="flutter-mobx"><a href="#flutter-mobx" class="headerlink" title="flutter_mobx"></a>flutter_mobx</h2><p>版本：</p><p>dependencies:<br>  mobx: ^0.4.0<br>  flutter_mobx: ^0.3.4</p><p>dev_dependencies:<br>  build_runner: ^1.3.1<br>  mobx_codegen: ^0.3.11</p><p>文档：<a href="https://mobx.pub/" target="_blank" rel="noopener">https://mobx.pub/</a></p><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><table><thead><tr><th>对象</th><th>说明</th><th></th></tr></thead><tbody><tr><td>Observables</td><td>代表响应式状态，可以是普通dart对象，<br>也可以是一颗状态树，变化会触发reaction</td><td></td></tr><tr><td>Computed</td><td>计算属性，根据多个Observables来源计算出<br>其应该输出的值，有缓存，不使用会清空，<br>源改变会触发重新计算，变化也会触发reaction</td><td></td></tr><tr><td>Actions</td><td>响应改变Observables的地方</td><td></td></tr><tr><td>Reactions</td><td>对Action、Observable、Computed三元素响应的地方，<br>可以是Widget/函数</td><td></td></tr><tr><td>Observer</td><td>上述Reaction的一个具体实现，用于Flutter中包裹需要响应<br>Observable的子树</td></tr></tbody></table><p>概念图(来自mobx.pub):</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-ff34fa76975d9ee6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="图来自mobx.pub" title>                </div>                <div class="image-caption">图来自mobx.pub</div>            </figure><h3 id="使用例子"><a href="#使用例子" class="headerlink" title="使用例子"></a>使用例子</h3><p>来自官网 计数器Demo</p><ol><li>定义Store，新建counter.dart</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"></span><br><span class="line">// Include generated file</span><br><span class="line">part &apos;counter.g.dart&apos;;   /// 利用注解解析生成代码</span><br><span class="line"></span><br><span class="line">// This is the class used by rest of your codebase</span><br><span class="line">class Counter = _Counter with _$Counter;</span><br><span class="line"></span><br><span class="line">// The store-class</span><br><span class="line">abstract class _Counter with Store &#123;</span><br><span class="line">  @observable</span><br><span class="line">  int value = 0;</span><br><span class="line"></span><br><span class="line">  @action</span><br><span class="line">  void increment() &#123;</span><br><span class="line">    value++;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>main.dart</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line">final counter = Counter(); // 1. 初始化Store</span><br><span class="line"></span><br><span class="line">void main() =&gt; runApp(MyApp());</span><br><span class="line"></span><br><span class="line">class MyApp extends StatelessWidget &#123;</span><br><span class="line">  // This widget is the root of your application.</span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    return MaterialApp(</span><br><span class="line">      title: &apos;MobX&apos;,</span><br><span class="line">      theme: ThemeData(</span><br><span class="line">        primarySwatch: Colors.blue,</span><br><span class="line">      ),</span><br><span class="line">      home: const MyHomePage(),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class MyHomePage extends StatelessWidget &#123;</span><br><span class="line">  const MyHomePage();</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    return Scaffold(</span><br><span class="line">      appBar: AppBar(</span><br><span class="line">        title: Text(&apos;MobX Counter&apos;),</span><br><span class="line">      ),</span><br><span class="line">      body: Center(</span><br><span class="line">        child: Column(</span><br><span class="line">          mainAxisAlignment: MainAxisAlignment.center,</span><br><span class="line">          children: &lt;Widget&gt;[</span><br><span class="line">            Text(</span><br><span class="line">              &apos;You have pushed the button this many times:&apos;,</span><br><span class="line">            ),</span><br><span class="line">            // Wrapping in the Observer will automatically re-render on changes to counter.value</span><br><span class="line">            Observer(   /// 2. 用Observer包裹 使用counter  会自动建立订阅关系</span><br><span class="line">              builder: (_) =&gt; Text(</span><br><span class="line">                &apos;$&#123;counter.value&#125;&apos;,</span><br><span class="line">                style: Theme.of(context).textTheme.display1,</span><br><span class="line">              ),</span><br><span class="line">            ),</span><br><span class="line">          ],</span><br><span class="line">        ),</span><br><span class="line">      ),</span><br><span class="line">      floatingActionButton: FloatingActionButton(</span><br><span class="line">        onPressed: counter.increment,   /// 3. 调用Observer的setter方法 通知更新</span><br><span class="line">        tooltip: &apos;Increment&apos;,</span><br><span class="line">        child: Icon(Icons.add),</span><br><span class="line">      ),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="图示"><a href="#图示" class="headerlink" title="图示"></a>图示</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-30208455f2829385.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="flutter_mobx主流程图1.png" title>                </div>                <div class="image-caption">flutter_mobx主流程图1.png</div>            </figure><h3 id="关键对象"><a href="#关键对象" class="headerlink" title="关键对象"></a>关键对象</h3><p>以上述计数器例子来分析下源码中关键对象</p><h4 id="Counter"><a href="#Counter" class="headerlink" title="_$Counter"></a>_$Counter</h4><p>该对象由代码生成器生成，主要实现扩展自定义的dart object的Observable的能力</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">mixin _$Counter on _Counter, Store &#123; </span><br><span class="line">  /// Store只是起标识作用的mixin</span><br><span class="line">  /// _Counter是我们自定义的状态对象</span><br><span class="line">  </span><br><span class="line">  final _$valueAtom = Atom(name: &apos;_Counter.value&apos;); /// 根据@observable标识的变量，生成的对应的Atom对象，用于与Reactions实现观察者模式</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  int get value &#123;</span><br><span class="line">  /// 属性value的getter  根据@observable生成</span><br><span class="line">    _$valueAtom.context.enforceReadPolicy(_$valueAtom); /// 用于限制此方法是否能在非Actions和Reactions里调用，默认允许，否则抛出assert异常</span><br><span class="line">    _$valueAtom.reportObserved();/// 注册观察者(Reaction)</span><br><span class="line">    return super.value;/// 返回值</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  set value(int value) &#123;</span><br><span class="line">    _$valueAtom.context.conditionallyRunInAction(() &#123;</span><br><span class="line">    /// conditionallyRunInAction 用于判断写入的安全策略和是否包含action的追踪</span><br><span class="line">      super.value = value;  /// 赋值的地方</span><br><span class="line">      _$valueAtom.reportChanged();  /// 通知注册在valueAtom里的Observer刷新数据</span><br><span class="line">    &#125;, _$valueAtom, name: &apos;$&#123;_$valueAtom.name&#125;_set&apos;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  final _$_CounterActionController = ActionController(name: &apos;_Counter&apos;);/// 根据@action生成，用于记录action调用情况</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  void increment() &#123;</span><br><span class="line">    final _$actionInfo = _$_CounterActionController.startAction(); /// 记录开始</span><br><span class="line">    try &#123;</span><br><span class="line">      return super.increment();/// 真正执行定义的increment方法</span><br><span class="line">    &#125; finally &#123;</span><br><span class="line">      _$_CounterActionController.endAction(_$actionInfo); /// 记录完成</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码片段里的<code>_$valueAtom.context</code>是每个Atom里默认取的全局的MainContext，看Atom构造：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">class Atom &#123;</span><br><span class="line">  factory Atom(</span><br><span class="line">          &#123;String name,</span><br><span class="line">          Function() onObserved,</span><br><span class="line">          Function() onUnobserved,</span><br><span class="line">          ReactiveContext context&#125;) =&gt;</span><br><span class="line">      Atom._(context ?? mainContext,   /// 注意此处，参数不传会使用mainContext</span><br><span class="line">          name: name, onObserved: onObserved, onUnobserved: onUnobserved);</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>看下几个重点方法：</p><ol><li><code>_$valueAtom.context.conditionallyRunInAction</code></li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">void conditionallyRunInAction(void Function() fn, Atom atom,</span><br><span class="line">      &#123;String name, ActionController actionController&#125;) &#123;</span><br><span class="line">    if (isWithinBatch) &#123;</span><br><span class="line">    /// 当在action、reaction里执行时，直接进入此处</span><br><span class="line">      enforceWritePolicy(atom);  /// 检查写入权限，如是否可在非action外进行写入等</span><br><span class="line">      fn();/// 执行真正赋值的地方</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">    /// 非 action or transaction 里执行走这</span><br><span class="line">      final controller = actionController ??</span><br><span class="line">          ActionController(</span><br><span class="line">              context: this, name: name ?? nameFor(&apos;conditionallyRunInAction&apos;));</span><br><span class="line">      final runInfo = controller.startAction();  /// 记录action开始</span><br><span class="line"></span><br><span class="line">      try &#123;</span><br><span class="line">        enforceWritePolicy(atom);</span><br><span class="line">        fn();</span><br><span class="line">      &#125; finally &#123;</span><br><span class="line">        controller.endAction(runInfo);   ///  记录action结束</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><ol start="2"><li><code>_$valueAtom.reportObserved()</code></li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">/// Atom</span><br><span class="line">void reportObserved() &#123;</span><br><span class="line"> _context._reportObserved(this);</span><br><span class="line">  &#125;</span><br><span class="line">/// ReactiveContext</span><br><span class="line">  void _reportObserved(Atom atom) &#123;</span><br><span class="line">    final derivation = _state.trackingDerivation;  /// 取出当前正在执行的reactions or computeds</span><br><span class="line"></span><br><span class="line">    if (derivation != null) &#123;</span><br><span class="line">      derivation._newObservables.add(atom);  /// 将当前atom绑进derivation里</span><br><span class="line">      if (!atom._isBeingObserved) &#123;</span><br><span class="line">      /// 如果atom之前并没有被加入观察，则执行此处</span><br><span class="line">        atom</span><br><span class="line">          .._isBeingObserved = true</span><br><span class="line">          .._notifyOnBecomeObserved();/// 通知Observable 的所有listener - 其变为被观察状态</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>上面可以看出，atom被加入到当前reaction(derivation)的监听集合里,即reaction持有了atom，但是atom改变时是需要通知到reaction的，继续看下面</p><ol start="3"><li><code>_$valueAtom.reportChanged()</code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line">/// Atom</span><br><span class="line">void reportChanged() &#123;</span><br><span class="line">   _context</span><br><span class="line">     ..startBatch()   /// batch计数+1 ，记录当前batch的深度，用来追踪如action执行的深度</span><br><span class="line">     ..propagateChanged(this)/// 通知注册在atom里的observer（即Derivation）数据改变</span><br><span class="line">     ..endBatch();  /// 执行完毕，batch计数-1并检查batch执行深度是否归0，此处是做了层优化</span><br><span class="line"> &#125;</span><br><span class="line"> </span><br><span class="line"> /// ReactiveContext</span><br><span class="line"> void propagateChanged(Atom atom) &#123;</span><br><span class="line">  ...</span><br><span class="line">   atom._lowestObserverState = DerivationState.stale;</span><br><span class="line"></span><br><span class="line">   for (final observer in atom._observers) &#123;</span><br><span class="line">     if (observer._dependenciesState == DerivationState.upToDate) &#123;</span><br><span class="line">       observer._onBecomeStale();  /// 通知所有注册的即Derivation数据改变</span><br><span class="line">     &#125;</span><br><span class="line">     observer._dependenciesState = DerivationState.stale;</span><br><span class="line">   &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> </span><br><span class="line"> void endBatch() &#123;</span><br><span class="line">   if (--_state.batch == 0) &#123; /// 优化：当前执行改变的层次没回归0时，跳过最终的reaction响应，只有全部执行完毕才走下面的逻辑 (个人理解:因为是单线程，此处考虑的应该是递归情况，如action里再调用action)</span><br><span class="line">     runReactions(); </span><br><span class="line">     /// 通知挂起的reactions 数据改变</span><br><span class="line">     ///  List&lt;Reaction&gt; pendingReactions = [];</span><br><span class="line">       /// The reactions that must be triggered at the end of a `transaction` or an `action`</span><br><span class="line"></span><br><span class="line">     for (var i = 0; i &lt; _state.pendingUnobservations.length; i++) &#123;</span><br><span class="line">     /// 这里处理断开连接的observations 如dispose掉</span><br><span class="line">       final ob = _state.pendingUnobservations[i]</span><br><span class="line">         .._isPendingUnobservation = false;</span><br><span class="line"></span><br><span class="line">       if (ob._observers.isEmpty) &#123;</span><br><span class="line">         if (ob._isBeingObserved) &#123;</span><br><span class="line">           // if this observable had reactive observers, trigger the hooks</span><br><span class="line">           ob</span><br><span class="line">             .._isBeingObserved = false</span><br><span class="line">             .._notifyOnBecomeUnobserved();</span><br><span class="line">         &#125;</span><br><span class="line"></span><br><span class="line">         if (ob is Computed) &#123;</span><br><span class="line">           ob._suspend();</span><br><span class="line">         &#125;</span><br><span class="line">       &#125;</span><br><span class="line">     &#125;</span><br><span class="line"></span><br><span class="line">     _state.pendingUnobservations = [];</span><br><span class="line">   &#125;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure></li></ol><p>基本上，<code>_$Counter</code>就是对<code>@observable</code>注解的变量扩展getter、setter方法，getter里将变量对应的atom绑进当前执行的<code>derivation</code>里去；在setter里去通知atom里的<code>_observers</code>集合。</p><p><code>@action</code>注解的方法，则会被包含进<code>_$_CounterActionController</code>控制里，记录action执行情况</p><p>但是atom._observers里的元素是什么时候注册的，按照mobx的理念是在reaction里引用过Observable，则自动tracking，所以接下来看<code>Observer</code></p><h4 id="Observer"><a href="#Observer" class="headerlink" title="Observer"></a>Observer</h4><p>flutter中作为UI的响应式组件，简单看下类图</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-9bb65e00ee506002.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="mobx_observer.png" title>                </div>                <div class="image-caption">mobx_observer.png</div>            </figure><p>如上图，StatelessObserverWidget extends StatelessWidget，框架主要通过<code>ObserverWidgetMixin</code>和<code>ObserverElementMixin</code>来扩展功能</p><ol><li><code>ObserverWidgetMixin</code></li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">mixin ObserverWidgetMixin on Widget &#123;</span><br><span class="line">  String getName();</span><br><span class="line"></span><br><span class="line">  ReactiveContext getContext() =&gt; mainContext;</span><br><span class="line"></span><br><span class="line">  Reaction createReaction(</span><br><span class="line">    Function() onInvalidate, &#123;</span><br><span class="line">    Function(Object, Reaction) onError,</span><br><span class="line">  &#125;) =&gt;</span><br><span class="line">      ReactionImpl(</span><br><span class="line">        getContext(),</span><br><span class="line">        onInvalidate,</span><br><span class="line">        name: getName(),</span><br><span class="line">        onError: onError,</span><br><span class="line">      );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>基本上就是扩展了 1) 创建Reaction  2) 获取mainContext 全局响应式上下文</p><ol start="2"><li><code>ObserverElementMixin</code></li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">mixin ObserverElementMixin on ComponentElement &#123;</span><br><span class="line">  ReactionImpl get reaction =&gt; _reaction;</span><br><span class="line">  ReactionImpl _reaction;  /// 包裹的响应类</span><br><span class="line"></span><br><span class="line">  ObserverWidgetMixin get _widget =&gt; widget as ObserverWidgetMixin;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  void mount(Element parent, dynamic newSlot) &#123;</span><br><span class="line">  /// 挂载Element 时 创建Reaction</span><br><span class="line">    _reaction = _widget.createReaction(invalidate, onError: (e, _) &#123;</span><br><span class="line">      FlutterError.reportError(FlutterErrorDetails(</span><br><span class="line">        library: &apos;flutter_mobx&apos;,</span><br><span class="line">        exception: e,</span><br><span class="line">        stack: e is Error ? e.stackTrace : null,</span><br><span class="line">      ));</span><br><span class="line">    &#125;) as ReactionImpl;</span><br><span class="line">    super.mount(parent, newSlot);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  void invalidate() =&gt; markNeedsBuild();/// Observable改变时会通知到这里 标脏</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  Widget build() &#123;</span><br><span class="line">    Widget built;</span><br><span class="line"></span><br><span class="line">    reaction.track(() &#123;  /// 每次挂载上Element树上会启动reaction的track，在这里面建立在传入的build方法里(即Observer的build属性) 获取过的Observable的关联</span><br><span class="line">      built = super.build();/// 调用外部传入的build方法 建立Widget子树</span><br><span class="line">    &#125;);</span><br><span class="line">...</span><br><span class="line">    return built;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  void unmount() &#123;</span><br><span class="line">  /// 卸载Element 时 卸载Reaction</span><br><span class="line">    reaction.dispose();</span><br><span class="line">    super.unmount();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接下来重点看<code>reaction.track</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">/// ReactionImpl</span><br><span class="line">  void track(void Function() fn) &#123;</span><br><span class="line">    _context.startBatch();  /// batch次数+1</span><br><span class="line"></span><br><span class="line">    _isRunning = true;</span><br><span class="line">    _context.trackDerivation(this, fn);  /// 开始追踪这个derivation即此时的reaction</span><br><span class="line">    _isRunning = false;</span><br><span class="line"></span><br><span class="line">    if (_isDisposed) &#123;</span><br><span class="line">      _context._clearObservables(this);   /// dispose的话 清理</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if (_context._hasCaughtException(this)) &#123;</span><br><span class="line">      _reportException(_errorValue._exception);  </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    _context.endBatch();  /// 此处理操作完成</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>进入<code>_context.trackDerivation</code>方法</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">/// ReactiveContext </span><br><span class="line">  T trackDerivation&lt;T&gt;(Derivation d, T Function() fn) &#123;</span><br><span class="line">    final prevDerivation = _startTracking(d);  /// 让mainContext开始追踪传入的derivation</span><br><span class="line">    T result;</span><br><span class="line"></span><br><span class="line">    if (config.disableErrorBoundaries == true) &#123;</span><br><span class="line">      result = fn();</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      try &#123;</span><br><span class="line">        result = fn();  /// 这里调用Observer里传入的build函数，里面会调用Observable的getter方法，上面提到的derivation就是这个d，所以atom会注册到这个d里面去</span><br><span class="line">        d._errorValue = null;</span><br><span class="line">      &#125; on Object catch (e) &#123;</span><br><span class="line">        d._errorValue = MobXCaughtException(e);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    _endTracking(d, prevDerivation);  /// 结束追踪</span><br><span class="line">    return result;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>进入<code>_startTracking(d)</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">/// ReactiveContext </span><br><span class="line">Derivation _startTracking(Derivation derivation) &#123;</span><br><span class="line">  final prevDerivation = _state.trackingDerivation;  </span><br><span class="line">  _state.trackingDerivation = derivation;  /// 将传入的derivation赋值为当前正在追踪的，所以从这之后调用的Observable的getter方法里拿到的都是它</span><br><span class="line"></span><br><span class="line">  _resetDerivationState(derivation); /// 重置derivation状态</span><br><span class="line">  derivation._newObservables = &#123;&#125;;/// 清空，方便之后的atom加入</span><br><span class="line"></span><br><span class="line">  return prevDerivation;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>进入<code>_endTracking(d, prevDerivation)</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">void _endTracking(Derivation currentDerivation, Derivation prevDerivation) &#123;</span><br><span class="line">  _state.trackingDerivation = prevDerivation;</span><br><span class="line">  _bindDependencies(currentDerivation);  /// 绑定derivation依赖的Observables</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>进入<code>_bindDependencies(currentDerivation)</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">void _bindDependencies(Derivation derivation) &#123;</span><br><span class="line">   final staleObservables =</span><br><span class="line">       derivation._observables.difference(derivation._newObservables);  /// 取出不一致的observable集合</span><br><span class="line">   final newObservables =</span><br><span class="line">       derivation._newObservables.difference(derivation._observables); /// 取出新的observable集合</span><br><span class="line">   var lowestNewDerivationState = DerivationState.upToDate;</span><br><span class="line"></span><br><span class="line">   // Add newly found observables</span><br><span class="line">   for (final observable in newObservables) &#123;</span><br><span class="line">     observable._addObserver(derivation);   /// 关键点1 这里将此derivation添加到Observable的_observers集合里，即在这里实现了atom持有derivation</span><br><span class="line"></span><br><span class="line">     // Computed = Observable + Derivation</span><br><span class="line">     if (observable is Computed) &#123;</span><br><span class="line">       if (observable._dependenciesState.index &gt;</span><br><span class="line">           lowestNewDerivationState.index) &#123;</span><br><span class="line">         lowestNewDerivationState = observable._dependenciesState;</span><br><span class="line">       &#125;</span><br><span class="line">     &#125;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   // Remove previous observables</span><br><span class="line">   for (final ob in staleObservables) &#123;</span><br><span class="line">     ob._removeObserver(derivation);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   if (lowestNewDerivationState != DerivationState.upToDate) &#123;</span><br><span class="line">     derivation</span><br><span class="line">       .._dependenciesState = lowestNewDerivationState</span><br><span class="line">       .._onBecomeStale();</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   derivation</span><br><span class="line">     .._observables = derivation._newObservables</span><br><span class="line">     .._newObservables = &#123;&#125;; // No need for newObservables beyond this point</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>如上关键点1，将derivation里关联的observable拿到，并将derivation注入到每个observable里，这里为止实现了observable和derivation的双向绑定</p><h5 id="Computed"><a href="#Computed" class="headerlink" title="Computed"></a>Computed</h5><p>该对象由@computed生成，充当Atom和Derivation的双重身份，即作为Atom给Observer等Reaction来观察，作为Derivation其方法里调用了其他的Atom，会监听其他的Atom的变化来触发自身的改变</p><p>看下自己定义的类</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">abstract class _Counter with Store &#123;</span><br><span class="line">  @observable</span><br><span class="line">  int value = 0;</span><br><span class="line"></span><br><span class="line">  @action</span><br><span class="line">  void increment() &#123;</span><br><span class="line">    value++;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  @computed</span><br><span class="line">  int get testCmp =&gt; value + 1;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>生成的<code>counter.g.dart</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">mixin _$Counter on _Counter, Store &#123;</span><br><span class="line">  Computed&lt;int&gt; _$testCmpComputed;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  int get testCmp =&gt;</span><br><span class="line">      (_$testCmpComputed ??= Computed&lt;int&gt;(() =&gt; super.testCmp)).value;</span><br><span class="line">  ...    </span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>可以看出，实际就是生成了一个Computed来包裹我们定义的testCmp getter方法，下面看Computed的实现</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">class Computed&lt;T&gt; extends Atom implements Derivation, ObservableValue&lt;T&gt; &#123;</span><br><span class="line">factory Computed(T Function() fn, &#123;String name, ReactiveContext context&#125;) =&gt;</span><br><span class="line">      Computed._(context ?? mainContext, fn, name: name);</span><br><span class="line"></span><br><span class="line">  Computed._(ReactiveContext context, this._fn, &#123;String name&#125;)</span><br><span class="line">      : super._(context, name: name ?? context.nameFor(&apos;Computed&apos;));</span><br><span class="line">      ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看出，具备了Atom和Derivation的特性，构造函数里比Atom多持有了外部的传入的(即我们自己定义的)方法</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">class Computed&lt;T&gt; extends Atom implements Derivation, ObservableValue&lt;T&gt; &#123;</span><br><span class="line">...</span><br><span class="line">  T _value;  /// 缓存的value</span><br><span class="line">  </span><br><span class="line"> @override</span><br><span class="line">  T get value &#123;</span><br><span class="line">...</span><br><span class="line">    if (!_context.isWithinBatch &amp;&amp; _observers.isEmpty) &#123;</span><br><span class="line">    /// 如果没在action or transaction里执行 并且 被其作为atom _observers内无观察者时</span><br><span class="line">      if (_context._shouldCompute(this)) &#123;  /// 判断其是否需要重新计算新的值，因为涉及到以及缓存</span><br><span class="line">        _context.startBatch();</span><br><span class="line">        _value = computeValue(track: false);  /// 计算新的值 且不将自己作为derivation利用mainContext进行追踪</span><br><span class="line">        _context.endBatch();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      reportObserved();/// 自己作为atom 被reaction调用时，上报自己给derivation监听</span><br><span class="line">      if (_context._shouldCompute(this)) &#123;</span><br><span class="line">        if (_trackAndCompute()) &#123;/// 开启reaction的追踪并计算新值</span><br><span class="line">          _context._propagateChangeConfirmed(this);/// 标记其持有的_observers 的依赖状态为脏</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">    return _value;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">   @override</span><br><span class="line">  void _suspend() &#123;</span><br><span class="line">    _context._clearObservables(this);</span><br><span class="line">    _value = null;  /// 挂起时清除_value缓存值</span><br><span class="line">  &#125;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以上代码片段重点三个方法：<code>_context._shouldCompute</code>、<code>computeValue(track: false)</code>、<code>_trackAndCompute()</code></p><p><strong><code>_context._shouldCompute</code></strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">bool _shouldCompute(Derivation derivation) &#123;</span><br><span class="line">    switch (derivation._dependenciesState) &#123;</span><br><span class="line">      case DerivationState.upToDate:</span><br><span class="line">        return false;</span><br><span class="line"></span><br><span class="line">      case DerivationState.notTracking:</span><br><span class="line">      case DerivationState.stale:</span><br><span class="line">        return true;</span><br><span class="line"></span><br><span class="line">      case DerivationState.possiblyStale:</span><br><span class="line">        return untracked(() &#123;</span><br><span class="line">          for (final obs in derivation._observables) &#123;  /// 遍历其使用过的Atom</span><br><span class="line">            if (obs is Computed) &#123;  /// 判断依赖的atom是否也是Computed，是的话需要处罚依赖去计算新的值</span><br><span class="line">              // Force a computation</span><br><span class="line">              if (config.disableErrorBoundaries == true) &#123;</span><br><span class="line">                obs.value;  /// 触发计算新的值</span><br><span class="line">              &#125; else &#123;</span><br><span class="line">                try &#123;</span><br><span class="line">                  obs.value;</span><br><span class="line">                &#125; on Object catch (_) &#123;</span><br><span class="line">                  return true;</span><br><span class="line">                &#125;</span><br><span class="line">              &#125;</span><br><span class="line"></span><br><span class="line">              if (derivation._dependenciesState == DerivationState.stale) &#123;</span><br><span class="line">                return true;</span><br><span class="line">              &#125;</span><br><span class="line">            &#125;</span><br><span class="line">          &#125;</span><br><span class="line"></span><br><span class="line">          _resetDerivationState(derivation);   </span><br><span class="line">          return false;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    return false;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p><strong><code>computeValue(track: false)</code></strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">T computeValue(&#123;bool track&#125;) &#123;</span><br><span class="line">    _isComputing = true;</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">    T value;</span><br><span class="line">    if (track) &#123;</span><br><span class="line">      value = _context.trackDerivation(this, _fn); /// 让computed作为derivation身份，调用_fn前利用ReactiveContext开启tracking模式</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">...</span><br><span class="line">        value = _fn();  /// 计算获取新的值</span><br><span class="line">...</span><br><span class="line">    &#125;</span><br><span class="line">...</span><br><span class="line">    _isComputing = false;</span><br><span class="line"></span><br><span class="line">    return value;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p><strong><code>_trackAndCompute()</code></strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">bool _trackAndCompute() &#123;</span><br><span class="line">    final oldValue = _value;</span><br><span class="line">    final wasSuspended = _dependenciesState == DerivationState.notTracking;</span><br><span class="line"></span><br><span class="line">    final newValue = computeValue(track: true);  /// 计算新值通知开启ReactiveContext的追踪</span><br><span class="line"></span><br><span class="line">    final changed = wasSuspended ||</span><br><span class="line">        _context._hasCaughtException(this) ||</span><br><span class="line">        !_isEqual(oldValue, newValue);</span><br><span class="line"></span><br><span class="line">    if (changed) &#123;</span><br><span class="line">      _value = newValue;  /// 赋新值</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    return changed;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>优点：</p><ol><li>observer的组件真正实现按需更新，只有监听的数据发生变化，它才会re-render</li><li>具备Computer计算属性机制，无引用时会自动回收</li><li>使用注解省去了notify等模板代码，简洁</li><li>mobx耦合性更低</li></ol><p>缺点：</p><ol><li>store过多导致无法统一数据源，管理是个问题</li><li>没有时间回溯能力，因为数据只有一份引用</li><li>缺乏中间件机制有效支持</li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;接上一篇&lt;a href=&quot;https://www.jianshu.com/p/6fae341cb856&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter状态管理之路（三）&lt;/a&gt;&lt;br&gt;此篇主要介绍flutter_mobx&lt;/p&gt;
&lt;h2 id
      
    
    </summary>
    
      <category term="Flutter" scheme="http://yoursite.com/categories/Flutter/"/>
    
    
      <category term="flutter" scheme="http://yoursite.com/tags/flutter/"/>
    
  </entry>
  
  <entry>
    <title>Flutter状态管理之路（一）</title>
    <link href="http://yoursite.com/2022/03/28/Flutter%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E4%B9%8B%E8%B7%AF%EF%BC%88%E4%B8%80%EF%BC%89/"/>
    <id>http://yoursite.com/2022/03/28/Flutter状态管理之路（一）/</id>
    <published>2022-03-28T02:13:30.000Z</published>
    <updated>2022-04-01T15:53:30.164Z</updated>
    
    <content type="html"><![CDATA[<p>博客地址：<a href="https://www.jianshu.com/p/10a1a16dfff7" target="_blank" rel="noopener">https://www.jianshu.com/p/10a1a16dfff7</a></p><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>原生提供了StatefulWidget这个有状态组件来管理状态，对于多组件的状态交互可以选择由父组件进行统一管理分发，但是当业务一旦复杂，组件树的分支足够多，会出现状态下沉过深入，状态传递复杂的问题。</p><p>简单情况是这样的：<br> <img src="https://upload-images.jianshu.io/upload_images/13098410-06bd3ac95510d408.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="状态管理背景1.png"></p><p>随着功能的增加，你的应用程序将会有几十个甚至上百个状态。这个时候你的应用应该会是这样：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-df28c64466d9a14b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="状态管理背景2.png" title>                </div>                <div class="image-caption">状态管理背景2.png</div>            </figure><p>上述实际就是多个页面需要共享状态和传递信息场景下出现的，直接的做法是：</p><ol><li><p>通过父widget来分发通知，有嵌套层级深的问题，父层级的setState导致不必要build问题</p></li><li><p>通过回调传递，同样存在传递深的问题 ，回调也会出现漏调用的问题</p></li></ol><h2 id="面临的问题"><a href="#面临的问题" class="headerlink" title="面临的问题"></a>面临的问题</h2><ol><li><p>如何获取数据源</p></li><li><p>如何更新数据源</p></li><li><p>如何通知组件数据源更新</p></li><li><p>跨组件数据源如何共享</p></li></ol><h2 id="数据流概念"><a href="#数据流概念" class="headerlink" title="数据流概念"></a>数据流概念</h2><p>状态管理里会出现基于单向数据流的情况，这里先介绍下数据流的概念</p><h3 id="单向数据流"><a href="#单向数据流" class="headerlink" title="单向数据流"></a>单向数据流</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-dec08e0c51eca527.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/200" alt="单向数据流.png" title>                </div>                <div class="image-caption">单向数据流.png</div>            </figure><ul><li><p>state：驱动应用的数据源。</p></li><li><p>view：以声明方式将 state 映射到视图 。</p></li><li><p>actions：响应在 view 上的用户输入导致的状态变化</p></li></ul><p>单向数据流的状态管理：通过定义和隔离状态管理中的各种概念并强制遵守一定的规则，我们的代码将会变得更结构化且易维护</p><p><strong>特点：</strong><br>（1） 所有状态的改变可记录、可跟踪，源头易追溯;<br>（2） 所有数据只有一份，组件数据只有唯一的入口和出口，使得程序更直观更容易理解，有利于应用的可维护性;<br>（3） 一旦数据变化，就去更新页面(data-&gt;页面)，但是没有(页面-&gt;data);<br>（4） 如果用户在页面上做了变动，那么就手动收集起来(双向是自动)，合并到原有的数据中</p><h3 id="双向数据流"><a href="#双向数据流" class="headerlink" title="双向数据流"></a>双向数据流</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-268e724f9ba08c2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/800" alt="双向数据流.png" title>                </div>                <div class="image-caption">双向数据流.png</div>            </figure><p>双向数据绑定，带来双向数据流，数据（state）和视图（View）之间的双向绑定。</p><blockquote><p>ng 里的 ng-model 和 vue 里的 v-model，以及Android的DataBinding</p></blockquote><p>说到底就是 （value 的单向绑定 + onChange 事件侦听）的一个语法糖</p><p><strong>特点：</strong><br>（1）无论数据改变，或是用户操作，都能带来互相的变动，自动更新。适用于项目细节，如：UI控件中(通常是类表单操作)。<br>（2）状态的改变不可控</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><h3 id="StatefulWidget"><a href="#StatefulWidget" class="headerlink" title="StatefulWidget"></a>StatefulWidget</h3><p>官方自带有状态组件，其组件里的各种状态可以由自身管理，也可由父组件管理，哪个管理合适，一般遵循以下原则：</p><ul><li>如果状态是用户数据，如复选框的选中状态、滑块的位置，则该状态最好由父Widget管理。</li><li>如果状态是有关界面外观效果的，例如颜色、动画，那么状态最好由Widget本身来管理。</li><li>如果某一个状态是不同Widget共享的则最好由它们共同的父Widget管理。</li></ul><p>一般来说，在Widget内部管理状态封装性会好一些，而在父Widget中管理会比较灵活，如果不知道是不是该由widget自身管理，则优先设计为由父widget管理并将其设计为StatelessWidget</p><p><strong>问题：</strong></p><p>功能单一，复杂多页面情况下，状态下沉过于深入，状态传递复杂，rebuild的范围过大等</p><h3 id="InherityWidget"><a href="#InherityWidget" class="headerlink" title="InherityWidget"></a>InherityWidget</h3><p>功能型组件，提供了一种数据在widget树中从上到下传递、共享的机制</p><p>比如在根Widget通过一个InherityWidget共享了一个状态，那么便可以在任意子widget中获取它，Theme,Navigator等都是通过这种机制来共享给整个应用的，它省去了逐级传递的麻烦</p><h4 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h4><ol><li><p>用于存储共享数据的父Widget，该widget继承InheritedWidget</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">class FatherWidget extends InheritedWidget &#123;</span><br><span class="line">  final int data;</span><br><span class="line"></span><br><span class="line">  FatherWidget(&#123;@required this.data, Widget child&#125;) : super(child: child);</span><br><span class="line"></span><br><span class="line">  //子树通过该方法获取共享数据</span><br><span class="line">  static FatherWidget of(BuildContext context) &#123;</span><br><span class="line">    return context.inheritFromWidgetOfExactType(FatherWidget);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  //该回调决定当data发生变化时，是否通知子树中依赖data的widget</span><br><span class="line">  @override</span><br><span class="line">  bool updateShouldNotify(FatherWidget oldWidget) &#123;</span><br><span class="line">    return oldWidget.data != data;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>子widget，获取状态和处理依赖发生变化时的响应</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">class ChildWidget extends StatefulWidget &#123;</span><br><span class="line">  @override</span><br><span class="line">  _ChildWidgetState createState() =&gt; _ChildWidgetState();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class _ChildWidgetState extends State&lt;ChildWidget&gt; &#123;</span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    return new Text(FatherWidget.of(context).data.toString());</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  void didChangeDependencies() &#123;</span><br><span class="line">    super.didChangeDependencies();</span><br><span class="line">    //父或祖先widget中的InheritedWidget改变（updateShouldNotify返回true）时会被调用</span><br><span class="line">    //如果build中没有依赖InheritedWidget,则此回调不会被调用</span><br><span class="line">    print(&quot;didChangeDependencies = &quot; +</span><br><span class="line">        FatherWidget.of(context).data.toString());</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>整合</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">class ContainerWidget extends StatefulWidget &#123;</span><br><span class="line">  @override</span><br><span class="line">  _ContainerWidgetState createState() =&gt; _ContainerWidgetState();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class _ContainerWidgetState extends State&lt;ContainerWidget&gt; &#123;</span><br><span class="line">  int _data = 0;</span><br><span class="line">  </span><br><span class="line">  void _incrementCounter() &#123;</span><br><span class="line">    setState(() &#123;               </span><br><span class="line">      _data++;                  /// 改变状态</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    return FatherWidget(</span><br><span class="line">      data: widget.data,</span><br><span class="line">      child: ChildWidget(),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h4 id="图示"><a href="#图示" class="headerlink" title="图示"></a>图示</h4><ol><li>如何实现子树获取InheritedWidget</li></ol><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-50a586a17c7b2b77.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="InheritedWidget机制1.png" title>                </div>                <div class="image-caption">InheritedWidget机制1.png</div>            </figure><ol start="2"><li>InheritedWidget和用of获取过它的子Widget如何建立联系的<figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-b80a6a0a8f7a3c2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="InheritedWidget机制2.png" title>                </div>                <div class="image-caption">InheritedWidget机制2.png</div>            </figure></li></ol><h4 id="关键点"><a href="#关键点" class="headerlink" title="关键点"></a>关键点</h4><p>“didChangeDependencies”：</p><p>State里的生命周期函数之一，表示依赖发生变化时由Framework调用通知，这里的依赖指的是子widget是否使用了InherityWidget的数据</p><p>另外，此回调紧跟initState执行，这里可以直接.context获取来使用</p><h4 id="源码"><a href="#源码" class="headerlink" title="源码"></a>源码</h4><p><strong>如何实现子树直接获取InheritedWidget</strong></p><p>新建build InheritedWidget时，对应的Element会调用如下_updateInheritance方法，从父Element复制 _inheritedWidgets并将此InheritedElement注册进 _inheritedWidgets里</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">class InheritedElement extends ProxyElement &#123;</span><br><span class="line">final Map&lt;Element, Object&gt; _dependents = HashMap&lt;Element, Object&gt;();</span><br><span class="line">...</span><br><span class="line">  @override</span><br><span class="line">  void _updateInheritance() &#123;</span><br><span class="line">  ...</span><br><span class="line">    final Map&lt;Type, InheritedElement&gt; incomingWidgets = _parent?._inheritedWidgets;</span><br><span class="line">    if (incomingWidgets != null)</span><br><span class="line">      _inheritedWidgets = HashMap&lt;Type, InheritedElement&gt;.from(incomingWidgets);</span><br><span class="line">    else</span><br><span class="line">      _inheritedWidgets = HashMap&lt;Type, InheritedElement&gt;();</span><br><span class="line">    _inheritedWidgets[widget.runtimeType] = this;</span><br><span class="line">  &#125;</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>获取过程，如下</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">/// 调用of方法</span><br><span class="line">static ThemeData of(BuildContext context, &#123; bool shadowThemeOnly = false &#125;) &#123;</span><br><span class="line">    final _InheritedTheme inheritedTheme = context.inheritFromWidgetOfExactType(_InheritedTheme);</span><br><span class="line">   ...</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">///  Element class</span><br><span class="line"> @override</span><br><span class="line">  InheritedWidget inheritFromWidgetOfExactType(Type targetType, &#123; Object aspect &#125;) &#123;</span><br><span class="line">  ...</span><br><span class="line">    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];   /// 根据类型 取出InheritedElement</span><br><span class="line">    if (ancestor != null) &#123;</span><br><span class="line">      assert(ancestor is InheritedElement);</span><br><span class="line">      return inheritFromElement(ancestor, aspect: aspect);  /// 此处建立子Element和InheritedElement的关系并返回InheritedWidget</span><br><span class="line">    &#125;</span><br><span class="line">    _hadUnsatisfiedDependencies = true;</span><br><span class="line">    return null;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  @override</span><br><span class="line">  InheritedWidget inheritFromElement(InheritedElement ancestor, &#123; Object aspect &#125;) &#123;</span><br><span class="line">...</span><br><span class="line">    _dependencies ??= HashSet&lt;InheritedElement&gt;();</span><br><span class="line">    _dependencies.add(ancestor);</span><br><span class="line">    ancestor.updateDependencies(this, aspect);</span><br><span class="line">    return ancestor.widget;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>注意：还有个方法是只取InheritedElement而不注册依赖关系的</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">/// Element class</span><br><span class="line">@override</span><br><span class="line">  InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) &#123;</span><br><span class="line">...</span><br><span class="line">    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];</span><br><span class="line">    return ancestor;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>优点：</p><ol><li><p>自动订阅</p></li><li><p>可跨组件获取状态</p></li></ol><p>缺点：</p><ol><li><p>每次促使inheritedWidget build重建 事实上都会触发所有子树的build，所以需要封装一个StatefulWidget来配合实现缓存加载</p></li><li><p>没有有效分离视图逻辑和业务逻辑。</p></li><li><p>无法定向通知/指向性通知。 事实上依赖InheriteWidget的子Widget，在调用State的didChangeDependencies前，在Element这一级会调用markNeedsBuild，所以都会rebuild一下</p></li></ol><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ol><li><a href="https://www.jianshu.com/p/810464f1a576" target="_blank" rel="noopener">单向数据流和双向数据流</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;博客地址：&lt;a href=&quot;https://www.jianshu.com/p/10a1a16dfff7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.jianshu.com/p/10a1a16dfff7&lt;/a&gt;&lt;/p&gt;
&lt;h2 i
      
    
    </summary>
    
      <category term="Flutter" scheme="http://yoursite.com/categories/Flutter/"/>
    
    
      <category term="flutter" scheme="http://yoursite.com/tags/flutter/"/>
    
  </entry>
  
  <entry>
    <title>Android轻量级网页风格分页器</title>
    <link href="http://yoursite.com/2019/05/15/Android%E8%BD%BB%E9%87%8F%E7%BA%A7%E7%BD%91%E9%A1%B5%E9%A3%8E%E6%A0%BC%E5%88%86%E9%A1%B5%E5%99%A8/"/>
    <id>http://yoursite.com/2019/05/15/Android轻量级网页风格分页器/</id>
    <published>2019-05-15T09:17:30.000Z</published>
    <updated>2020-09-30T13:33:34.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Android轻量级网页风格分页器"><a href="#Android轻量级网页风格分页器" class="headerlink" title="Android轻量级网页风格分页器"></a>Android轻量级网页风格分页器</h1><p>轻量级仿网页风格分页器，和RecycleView封装一起配合使用，也可单独使用，喜欢就star、fork下吧～谢谢</p><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ul><li><a href="#功能介绍">功能介绍</a></li><li><a href="#效果图">效果图</a></li><li><a href="#如何引入">如何引入</a></li><li><a href="#简单使用">简单使用</a></li><li><a href="#依赖">依赖</a></li><li><a href="#github地址">github地址</a></li></ul><h2 id="功能介绍"><a href="#功能介绍" class="headerlink" title="功能介绍"></a>功能介绍</h2><ul><li style="list-style: none"><input type="checkbox" checked> 支持延迟加载分页</li><li style="list-style: none"><input type="checkbox" checked> 支持单独分页器组件使用；同时封装了RecycleView，可以配合使用</li><li style="list-style: none"><input type="checkbox" checked> 支持加载状态改变提示</li><li style="list-style: none"><input type="checkbox" checked> 支持自定义数字指示器数量、选中和未选中等样式</li></ul><h2 id="效果图"><a href="#效果图" class="headerlink" title="效果图"></a>效果图</h2><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-ee753b0c2bae04f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/320" alt="pic1.png" title>                </div>                <div class="image-caption">pic1.png</div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-41e48de284e19b06.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/320" alt="pic2.png" title>                </div>                <div class="image-caption">pic2.png</div>            </figure><h2 id="Screenshots"><a href="#Screenshots" class="headerlink" title="Screenshots"></a>Screenshots</h2><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://upload-images.jianshu.io/upload_images/13098410-66d80a72834fea8e.gif?imageMogr2/auto-orient/strip" alt="pagination.gif" title>                </div>                <div class="image-caption">pagination.gif</div>            </figure><h2 id="如何引入"><a href="#如何引入" class="headerlink" title="如何引入"></a>如何引入</h2><h3 id="Gradle引入"><a href="#Gradle引入" class="headerlink" title="Gradle引入"></a>Gradle引入</h3><h3 id="step-1"><a href="#step-1" class="headerlink" title="step 1"></a>step 1</h3><p>Add the JitPack repository to your build file</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">allprojects &#123;</span><br><span class="line">repositories &#123;</span><br><span class="line">...</span><br><span class="line">maven &#123; url &apos;https://jitpack.io&apos; &#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Step-2"><a href="#Step-2" class="headerlink" title="Step 2"></a>Step 2</h3><p>Add the dependency</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">dependencies &#123;</span><br><span class="line">         implementation &apos;com.github.itlwy:PaginationExample:0.0.20&apos;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="简单使用"><a href="#简单使用" class="headerlink" title="简单使用"></a>简单使用</h2><h3 id="组合RecycleView使用"><a href="#组合RecycleView使用" class="headerlink" title="组合RecycleView使用"></a>组合RecycleView使用</h3><p>此时使用的是PaginationRecycleView类</p><ol><li><p>activity_main.xml</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="tag">&lt;<span class="name">com.lwy.paginationlib.PaginationRecycleView</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:id</span>=<span class="string">"@+id/pagination_rcv"</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:layout_width</span>=<span class="string">"match_parent"</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:layout_height</span>=<span class="string">"match_parent"</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:layout_marginBottom</span>=<span class="string">"8dp"</span></span></span><br><span class="line"><span class="tag">        <span class="attr">app:number_tip_count</span>=<span class="string">"5"</span></span></span><br><span class="line"><span class="tag">        <span class="attr">app:rect_size</span>=<span class="string">"30dp"</span></span></span><br><span class="line"><span class="tag">        <span class="attr">app:selected_color</span>=<span class="string">"@color/indicator_rect_selected"</span></span></span><br><span class="line"><span class="tag">        <span class="attr">app:text_size</span>=<span class="string">"14sp"</span></span></span><br><span class="line"><span class="tag">        <span class="attr">app:unselected_color</span>=<span class="string">"@color/indicator_rect_unselected"</span></span></span><br><span class="line"><span class="tag">        /&gt;</span></span><br><span class="line">...</span><br></pre></td></tr></table></figure></li><li><p>MainActivity.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"> ...</span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(Bundle savedInstanceState)</span> </span>&#123;</span><br><span class="line">mPaginationRcv = findViewById(R.id.pagination_rcv);</span><br><span class="line">        mAdapter = <span class="keyword">new</span> CustomAdapter(<span class="keyword">this</span>, <span class="number">99</span>);</span><br><span class="line">        mPaginationRcv.setAdapter(mAdapter);</span><br><span class="line"><span class="comment">//        mPaginationRcv.setPerPageCountChoices(perPageCountChoices);</span></span><br><span class="line">        GridLayoutManager layoutManager = <span class="keyword">new</span> GridLayoutManager(<span class="keyword">this</span>, <span class="number">3</span>);</span><br><span class="line">        mPaginationRcv.setLayoutManager(layoutManager);</span><br><span class="line">        mPaginationRcv.setListener(<span class="keyword">new</span> PaginationRecycleView.Listener() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">loadMore</span><span class="params">(<span class="keyword">int</span> currentPagePosition, <span class="keyword">int</span> nextPagePosition, <span class="keyword">int</span> perPageCount, <span class="keyword">int</span> dataTotalCount)</span> </span>&#123;</span><br><span class="line">         <span class="comment">// nextPagePosition为将要加载的页码，即需要加载数据的页</span></span><br><span class="line">         <span class="comment">// perPageCount 每页展示的数量</span></span><br><span class="line">               <span class="comment">//TODO : 此处进行异步数据加载</span></span><br><span class="line">               <span class="comment">//TODO : 完成加载后通知分页控件(注意此处应该是在主线程运行)，如下</span></span><br><span class="line">                mAdapter.setDatas(nextPagePosition, data);</span><br><span class="line">                mPaginationRcv.setState(PaginationRecycleView.SUCCESS);</span><br><span class="line">                </span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onPerPageCountChanged</span><span class="params">(<span class="keyword">int</span> perPageCount)</span> </span>&#123;</span><br><span class="line"><span class="comment">// "x条/每页"Spinner选中值改变时触发</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        mAdapter.setOnItemClickListener(<span class="keyword">this</span>);</span><br><span class="line"> &#125;</span><br><span class="line">         <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onItemClick</span><span class="params">(View view, RecyclerView.ViewHolder holder, <span class="keyword">int</span> position)</span> </span>&#123;</span><br><span class="line">        JSONObject item = mAdapter.getCurrentPageItem(position);  <span class="comment">// 此处position返回的是recycleview的位置，所以取当前页显示列表的项</span></span><br><span class="line">        Toast.makeText(<span class="keyword">this</span>, item.optString(<span class="string">"name"</span>), Toast.LENGTH_LONG).show();</span><br><span class="line">    &#125;</span><br><span class="line">...</span><br></pre></td></tr></table></figure></li><li><p>CustomAdapter</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CustomAdapter</span> <span class="keyword">extends</span> <span class="title">PaginationRecycleView</span>.<span class="title">Adapter</span>&lt;<span class="title">JSONObject</span>, <span class="title">ViewHolder</span>&gt; </span>&#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> Context mContext;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">CustomAdapter</span><span class="params">(Context context, <span class="keyword">int</span> dataTotalCount)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">super</span>(dataTotalCount);</span><br><span class="line">            mContext = context;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">bindViewHolder</span><span class="params">(ViewHolder viewholder, JSONObject data)</span> </span>&#123;</span><br><span class="line">            viewholder.setText(R.id.text, data.optString(<span class="string">"name"</span>));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> ViewHolder <span class="title">createViewHolder</span><span class="params">(@NonNull ViewGroup parent, <span class="keyword">int</span> viewTypea)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> ViewHolder.createViewHolder(mContext, parent, R.layout.item_list);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>布局文件中的属性说明:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">app:number_tip_count=&quot;5&quot;   // 数字指示器显示的数量，默认是5</span><br><span class="line">app:rect_size=&quot;30dp&quot;// 圆角矩形的大小(正方形)</span><br><span class="line">app:selected_color=&quot;@color/indicator_rect_selected&quot;  // 选中的颜色(包含框和字体)</span><br><span class="line">app:text_size=&quot;14sp&quot;  // 字体大小</span><br><span class="line">app:unselected_color=&quot;@color/indicator_rect_unselected&quot; 未选中的颜色(包含框和字体)</span><br></pre></td></tr></table></figure></li></ol><h3 id="单独使用"><a href="#单独使用" class="headerlink" title="单独使用"></a>单独使用</h3><pre><code>此时使用的是PaginationIndicator类，布局如下：</code></pre><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="tag">&lt;<span class="name">com.lwy.paginationlib.PaginationIndicator</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:id</span>=<span class="string">"@+id/indicator"</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:layout_width</span>=<span class="string">"match_parent"</span></span></span><br><span class="line"><span class="tag">    <span class="attr">app:number_tip_count</span>=<span class="string">"5"</span></span></span><br><span class="line"><span class="tag">    <span class="attr">app:rect_size</span>=<span class="string">"30dp"</span></span></span><br><span class="line"><span class="tag">    <span class="attr">app:selected_color</span>=<span class="string">"@color/indicator_rect_selected"</span></span></span><br><span class="line"><span class="tag">    <span class="attr">app:text_size</span>=<span class="string">"14sp"</span></span></span><br><span class="line"><span class="tag">    <span class="attr">app:unselected_color</span>=<span class="string">"@color/indicator_rect_unselected"</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:layout_height</span>=<span class="string">"wrap_content"</span>&gt;</span></span><br><span class="line">...</span><br></pre></td></tr></table></figure><pre><code>说明如上述代码如下：</code></pre><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">... </span><br><span class="line"><span class="keyword">private</span> <span class="keyword">int</span>[] perPageCountChoices = &#123;<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>, <span class="number">50</span>&#125;;</span><br><span class="line">... </span><br><span class="line">mIndicatorView = (PaginationIndicator) findViewById(R.id.indicator);</span><br><span class="line">        mIndicatorView.setTotalCount(<span class="number">99</span>);  <span class="comment">// 设置数据源总数量即可</span></span><br><span class="line">        mIndicatorView.setPerPageCountChoices(perPageCountChoices); <span class="comment">// 选填</span></span><br><span class="line">        mIndicatorView.setListener(<span class="keyword">new</span> PaginationIndicator.OnChangedListener() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onPageSelectedChanged</span><span class="params">(<span class="keyword">int</span> currentPapePos, <span class="keyword">int</span> lastPagePos, <span class="keyword">int</span> totalPageCount, <span class="keyword">int</span> total)</span> </span>&#123;</span><br><span class="line">                Toast.makeText(MainActivity.<span class="keyword">this</span>, <span class="string">"选中"</span> + currentPapePos + <span class="string">"页"</span>, Toast.LENGTH_LONG).show();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onPerPageCountChanged</span><span class="params">(<span class="keyword">int</span> perPageCount)</span> </span>&#123;</span><br><span class="line"><span class="comment">// x条/页 选项改变时触发</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">...</span><br></pre></td></tr></table></figure><pre><code>相关说明已在代码里注释，详细可参考demo，谢谢</code></pre><h2 id="依赖"><a href="#依赖" class="headerlink" title="依赖"></a>依赖</h2><ul><li>recyclerview : com.android.support:recyclerview-v7:28.0.0</li></ul><h2 id="github地址"><a href="#github地址" class="headerlink" title="github地址"></a>github地址</h2><p><a href="https://github.com/itlwy/PaginationExample" target="_blank" rel="noopener">源码github</a><br> 如果觉得对你有所帮助，就喜欢star一下表示下支持呗，谢啦各位看官～</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;Android轻量级网页风格分页器&quot;&gt;&lt;a href=&quot;#Android轻量级网页风格分页器&quot; class=&quot;headerlink&quot; title=&quot;Android轻量级网页风格分页器&quot;&gt;&lt;/a&gt;Android轻量级网页风格分页器&lt;/h1&gt;&lt;p&gt;轻量级仿网页风格分页
      
    
    </summary>
    
      <category term="Android" scheme="http://yoursite.com/categories/Android/"/>
    
    
      <category term="Android" scheme="http://yoursite.com/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>Android自动更新,支持增量更新</title>
    <link href="http://yoursite.com/2018/09/04/Android%E5%A2%9E%E9%87%8F%E8%87%AA%E5%8A%A8%E6%9B%B4%E6%96%B0/"/>
    <id>http://yoursite.com/2018/09/04/Android增量自动更新/</id>
    <published>2018-09-04T12:00:30.000Z</published>
    <updated>2020-09-30T13:33:34.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="APP自动增量更新"><a href="#APP自动增量更新" class="headerlink" title="APP自动增量更新"></a>APP自动增量更新</h2><p><a href="https://github.com/itlwy/AppSmartUpdate" target="_blank" rel="noopener">github：https://github.com/itlwy/AppSmartUpdate</a></p><p>抽取的Android自动更新库,目的是几行代码引入更新功能,含服务端代码,欢迎Star，欢迎Fork，谢谢～</p><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ul><li><a href="#功能介绍">功能介绍</a></li><li><a href="#流程图">流程图</a></li><li><a href="#效果图与示例apk">效果图与示例apk</a></li><li><a href="#如何引入">如何引入</a></li><li><a href="#更新清单文件">更新清单文件</a></li><li><a href="#简单使用">简单使用</a></li><li><a href="#详细说明">详细说明</a></li><li><a href="#差分包生成">差分包生成(服务端)</a></li><li><a href="#依赖">依赖</a></li><li><a href="#License">License</a></li></ul><h2 id="功能介绍"><a href="#功能介绍" class="headerlink" title="功能介绍"></a>功能介绍</h2><ul><li style="list-style: none"><input type="checkbox" checked> 支持全量更新apk,直接升级到最新版本</li><li style="list-style: none"><input type="checkbox" checked> 支持增量更新,只下载补丁包升级</li><li style="list-style: none"><input type="checkbox" checked> 设置仅在wifi环境下更新</li><li style="list-style: none"><input type="checkbox" checked> 支持外部注入网络框架(库默认使用okhttp)</li><li style="list-style: none"><input type="checkbox" checked> 支持前台和后台自动更新</li><li style="list-style: none"><input type="checkbox" checked> 支持强制更新</li><li style="list-style: none"><input type="checkbox" checked> 支持对外定制更新提示和更新进度界面</li><li style="list-style: none"><input type="checkbox"> 记忆下载</li><li style="list-style: none"><input type="checkbox" checked> 含发布功能后台服务端<a href="https://github.com/itlwy/App-Update-Server" target="_blank" rel="noopener">github</a> (Node.js实现)</li></ul><h2 id="流程图"><a href="#流程图" class="headerlink" title="流程图"></a>流程图</h2><p><img src="https://raw.githubusercontent.com/itlwy/AppSmartUpdate/master/resources/flowchart.jpg" width="80%" height="50%"></p><h2 id="效果图与示例apk"><a href="#效果图与示例apk" class="headerlink" title="效果图与示例apk"></a>效果图与示例apk</h2><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://raw.githubusercontent.com/itlwy/AppSmartUpdate/master/resources/update_1.png" alt="示例1" title>                </div>                <div class="image-caption">示例1</div>            </figure> <figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://raw.githubusercontent.com/itlwy/AppSmartUpdate/master/resources/update_2.png" alt="示例2" title>                </div>                <div class="image-caption">示例2</div>            </figure><p><a href="https://raw.githubusercontent.com/itlwy/AppSmartUpdate/master/resources/app/v100/smart-updatev100.apk" target="_blank" rel="noopener">点击下载 smart-update.apk</a> </p><h2 id="如何引入"><a href="#如何引入" class="headerlink" title="如何引入"></a>如何引入</h2><h3 id="Gradle引入"><a href="#Gradle引入" class="headerlink" title="Gradle引入"></a>Gradle引入</h3><h3 id="step-1"><a href="#step-1" class="headerlink" title="step 1"></a>step 1</h3><p>Add the JitPack repository to your build file</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">allprojects &#123;</span><br><span class="line">repositories &#123;</span><br><span class="line">...</span><br><span class="line">maven &#123; url &apos;https://jitpack.io&apos; &#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Step-2"><a href="#Step-2" class="headerlink" title="Step 2"></a>Step 2</h3><p>Add the dependency</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">dependencies &#123;</span><br><span class="line">          implementation &apos;com.github.itlwy:AppSmartUpdate:v1.0.6&apos;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="更新清单文件"><a href="#更新清单文件" class="headerlink" title="更新清单文件"></a>更新清单文件</h2><p>该清单放置在静态服务器以供App访问，主要用于判断最新的版本，及要更新的版本资源信息等(示例见仓库根目录下的resources目录或直接访问后台代码 <a href="https://github.com/itlwy/App-Update-Server" target="_blank" rel="noopener">github</a>)，清单由服务端程序发布apk时生成，详见后台示例:<a href="https://github.com/itlwy/App-Update-Server" target="_blank" rel="noopener">github</a></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"minVersion"</span>: <span class="number">100</span>, <span class="comment">// app最低支持的版本代码(包含),低于此数值的app将强制更新</span></span><br><span class="line">  <span class="attr">"minAllowPatchVersion"</span>: <span class="number">100</span>, <span class="comment">// 最低支持的差分版本(包含),低于此数值的app将采取全量更新,否则采用差量</span></span><br><span class="line">  <span class="attr">"newVersion"</span>: <span class="number">101</span>, <span class="comment">// 当前最新版本代码</span></span><br><span class="line">  <span class="attr">"tip"</span>: <span class="string">"test update"</span>,<span class="comment">// 更新提示</span></span><br><span class="line">  <span class="attr">"size"</span>: <span class="number">1956631</span>,<span class="comment">// 最新apk文件大小</span></span><br><span class="line">  <span class="attr">"apkURL"</span>: <span class="string">"https://raw.githubusercontent.com/itlwy/AppSmartUpdate/master/resources/app/smart-update.apk"</span>, <span class="comment">// 最新apk 绝对url地址，也可用相对地址，如下方的"patchURL"字段</span></span><br><span class="line">  <span class="attr">"hash"</span>: <span class="string">"ea97c8efa490a2eaf7d10b37e63dab0e"</span>, <span class="comment">// 最新apk文件的md5值</span></span><br><span class="line">  <span class="attr">"patchInfo"</span>: &#123;  <span class="comment">// 差分包信息</span></span><br><span class="line">    <span class="attr">"v100"</span>: &#123; <span class="comment">// v100表示-版本代码100的apk需要下载的差分包</span></span><br><span class="line">      <span class="attr">"patchURL"</span>: <span class="string">"v100/100to101.patch"</span>, <span class="comment">//差分包地址，相对此UpdateManifest.json文件的地址,也可用绝对地址</span></span><br><span class="line">      <span class="attr">"tip"</span>: <span class="string">"101 version"</span>, <span class="comment">// 提示</span></span><br><span class="line">      <span class="attr">"hash"</span>: <span class="string">"ea97c8efa490a2eaf7d10b37e63dab0e"</span>, <span class="comment">// 合成后apk(即版本代码101)的文件md5值</span></span><br><span class="line">      <span class="attr">"size"</span>: <span class="number">1114810</span> <span class="comment">// 差分包大小</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="简单使用"><a href="#简单使用" class="headerlink" title="简单使用"></a>简单使用</h2><h3 id="1-初始化"><a href="#1-初始化" class="headerlink" title="1.初始化"></a>1.初始化</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyApplication</span> <span class="keyword">extends</span> <span class="title">Application</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>.onCreate();</span><br><span class="line">        <span class="comment">//推荐在Application中初始化</span></span><br><span class="line">        Config config = <span class="keyword">new</span> Config.Builder()</span><br><span class="line">                .isDebug(<span class="keyword">true</span>)</span><br><span class="line">                .build(<span class="keyword">this</span>);</span><br><span class="line">        UpdateManager.getInstance().init(config);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-调用"><a href="#2-调用" class="headerlink" title="2.调用"></a>2.调用</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MainActivity</span> <span class="keyword">extends</span> <span class="title">AppCompatActivity</span> <span class="keyword">implements</span> <span class="title">View</span>.<span class="title">OnClickListener</span> </span>&#123;</span><br><span class="line"> <span class="keyword">private</span> Button mUpdateBtn;</span><br><span class="line">    <span class="keyword">private</span> String manifestJsonUrl = <span class="string">"https://raw.githubusercontent.com/itlwy/AppSmartUpdate/master/resources/UpdateManifest.json"</span>;</span><br><span class="line">    <span class="keyword">private</span> IUpdateCallback mCallback;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(Bundle savedInstanceState)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>.onCreate(savedInstanceState);</span><br><span class="line">        setContentView(R.layout.activity_main);</span><br><span class="line">        mUpdateBtn = (Button) findViewById(R.id.update_btn);</span><br><span class="line">        mUpdateBtn.setOnClickListener(<span class="keyword">this</span>);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onClick</span><span class="params">(View v)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">switch</span> (v.getId()) &#123;</span><br><span class="line">            <span class="keyword">case</span> R.id.update_btn:</span><br><span class="line">                UpdateManager.getInstance().update(<span class="keyword">this</span>, manifestJsonUrl, <span class="keyword">null</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="详细说明"><a href="#详细说明" class="headerlink" title="详细说明"></a>详细说明</h2><h3 id="注册通知回调"><a href="#注册通知回调" class="headerlink" title="注册通知回调"></a>注册通知回调</h3><ul><li>其他activity界面需要获知后台更新情况</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">register</span><span class="params">(IUpdateCallback callback)</span> </span>&#123;...&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">unRegister</span><span class="params">(IUpdateCallback callback)</span> </span>&#123;...&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">IUpdateCallback</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 通知无新版本需要更新,运行在主线程</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">noNewApp</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 自动更新准备开始时回调,运行在主线程，可做一些提示等</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">beforeUpdate</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 自动更新的进度回调（分增量和全量更新）,运行在主线程</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> percent     当前总进度百分比</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> totalLength 更新总大小(全量为apk大小,增量为全部补丁大小和)</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> patchIndex  当前更新的补丁索引(从1开始)</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> patchCount  需要更新的总补丁数(当为0时表示是增量更新)</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">onProgress</span><span class="params">(<span class="keyword">int</span> percent, <span class="keyword">long</span> totalLength, <span class="keyword">int</span> patchIndex, <span class="keyword">int</span> patchCount)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 下载完成，准备更新,运行在主线程</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">onCompleted</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 异常回调,运行在主线程</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> error 异常信息</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">onError</span><span class="params">(String error)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 用户取消了询问更新对话框</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">onCancelUpdate</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 取消了更新进度对话框,压入后台自动更新,此时由通知栏通知进度</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">onBackgroundTrigger</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="网络框架注入"><a href="#网络框架注入" class="headerlink" title="网络框架注入"></a>网络框架注入</h3><p>默认使用okhttp，也可由外部注入,只需实现如下的IHttpManager接口,然后通过new Config.Builder().httpManager(new OkhttpManager())注入即可</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">IHttpManager</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="function">IResponse <span class="title">syncGet</span><span class="params">(@NonNull String url, @NonNull Map&lt;String, String&gt; params)</span> <span class="keyword">throws</span> IOException</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 异步get</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> url      get请求地址</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> params   get参数</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> callBack 回调</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">asyncGet</span><span class="params">(@NonNull String url, @NonNull Map&lt;String, String&gt; params, @NonNull Callback callBack)</span></span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 异步post</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> url      post请求地址</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> params   post请求参数</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> callBack 回调</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">asyncPost</span><span class="params">(@NonNull String url, @NonNull Map&lt;String, String&gt; params, @NonNull Callback callBack)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 下载</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> url      下载地址</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> path     文件保存路径</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> fileName 文件名称</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> callback 回调</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">download</span><span class="params">(@NonNull String url, @NonNull String path, @NonNull String fileName, @NonNull FileCallback callback)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="定制更新交互界面"><a href="#定制更新交互界面" class="headerlink" title="定制更新交互界面"></a>定制更新交互界面</h3><p>每个应用的风格都可能是不一样的，因此这里也支持自定义弹出的提示框和进度框，详细见如下代码示例：</p><ol><li><p>初始化config时需要将内部默认的弹框屏蔽掉</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyApplication</span> <span class="keyword">extends</span> <span class="title">Application</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>.onCreate();</span><br><span class="line">         Config config = <span class="keyword">new</span> Config.Builder()</span><br><span class="line">                .isShowInternalDialog(<span class="keyword">false</span>)</span><br><span class="line">                .build(<span class="keyword">this</span>);</span><br><span class="line">        UpdateManager.getInstance().init(config);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>自定义对话框，如下(详细代码在MainActivity.java里)：</p></li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">registerUpdateCallbak</span><span class="params">()</span> </span>&#123;</span><br><span class="line">       mCallback = <span class="keyword">new</span> IUpdateCallback() &#123;</span><br><span class="line">           <span class="meta">@Override</span></span><br><span class="line">           <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">noNewApp</span><span class="params">()</span> </span>&#123;</span><br><span class="line">               Toast.makeText(MainActivity.<span class="keyword">this</span>, <span class="string">"当前已是最新版本!"</span>, Toast.LENGTH_LONG).show();</span><br><span class="line">           &#125;</span><br><span class="line"></span><br><span class="line">           <span class="meta">@Override</span></span><br><span class="line">           <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">hasNewApp</span><span class="params">(AppUpdateModel appUpdateModel, UpdateManager updateManager, <span class="keyword">final</span> <span class="keyword">int</span> updateMethod)</span> </span>&#123;</span><br><span class="line">               AlertDialog.Builder builder = <span class="keyword">new</span> AlertDialog.Builder(MainActivity.<span class="keyword">this</span>);</span><br><span class="line">               mDialog = builder.setTitle(<span class="string">"自动更新提示"</span>)</span><br><span class="line">                       .setMessage(appUpdateModel.getTip())</span><br><span class="line">                       .setPositiveButton(<span class="string">"更新"</span>, <span class="keyword">new</span> DialogInterface.OnClickListener() &#123;</span><br><span class="line">                           <span class="meta">@Override</span></span><br><span class="line">                           <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onClick</span><span class="params">(DialogInterface dialog, <span class="keyword">int</span> which)</span> </span>&#123;</span><br><span class="line">                               UpdateManager.getInstance().startUpdate(updateMethod);</span><br><span class="line">                           &#125;</span><br><span class="line">                       &#125;)</span><br><span class="line">                       .setNegativeButton(<span class="string">"取消"</span>, <span class="keyword">new</span> DialogInterface.OnClickListener() &#123;</span><br><span class="line">                           <span class="meta">@Override</span></span><br><span class="line">                           <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onClick</span><span class="params">(DialogInterface dialog, <span class="keyword">int</span> which)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">                           &#125;</span><br><span class="line">                       &#125;).create();</span><br><span class="line">               mDialog.show();</span><br><span class="line">           &#125;</span><br><span class="line"></span><br><span class="line">           <span class="meta">@Override</span></span><br><span class="line">           <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">beforeUpdate</span><span class="params">()</span> </span>&#123;</span><br><span class="line">               <span class="comment">// 更新开始</span></span><br><span class="line">               mProgressDialog = <span class="keyword">new</span> ProgressDialog(MainActivity.<span class="keyword">this</span>);</span><br><span class="line">               mProgressDialog.setTitle(<span class="string">"更新中..."</span>);</span><br><span class="line">               mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);</span><br><span class="line">               mProgressDialog.setMessage(<span class="string">"正在玩命更新中..."</span>);</span><br><span class="line">               mProgressDialog.setMax(<span class="number">100</span>);</span><br><span class="line">               mProgressDialog.setProgress(<span class="number">0</span>);</span><br><span class="line">               mProgressDialog.setOnCancelListener(<span class="keyword">new</span> DialogInterface.OnCancelListener() &#123;</span><br><span class="line">                   <span class="meta">@Override</span></span><br><span class="line">                   <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCancel</span><span class="params">(DialogInterface dialog)</span> </span>&#123;</span><br><span class="line">                       <span class="comment">// 退到后台自动更新，进度由通知栏显示</span></span><br><span class="line">                       <span class="keyword">if</span> (UpdateManager.getInstance().isRunning()) &#123;</span><br><span class="line">                           UpdateManager.getInstance().onBackgroundTrigger();</span><br><span class="line">                       &#125;</span><br><span class="line">                   &#125;</span><br><span class="line">               &#125;);</span><br><span class="line">               mProgressDialog.show();</span><br><span class="line">           &#125;</span><br><span class="line"></span><br><span class="line">           <span class="meta">@Override</span></span><br><span class="line">           <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onProgress</span><span class="params">(<span class="keyword">int</span> percent, <span class="keyword">long</span> totalLength, <span class="keyword">int</span> patchIndex, <span class="keyword">int</span> patchCount)</span> </span>&#123;</span><br><span class="line">               String tip;</span><br><span class="line">               <span class="keyword">if</span> (patchCount &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                   tip = String.format(<span class="string">"正在下载补丁%d/%d"</span>, patchIndex, patchCount);</span><br><span class="line">               &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                   tip = <span class="string">"正在下载更新中..."</span>;</span><br><span class="line">               &#125;</span><br><span class="line">               mProgressDialog.setProgress(percent);</span><br><span class="line">               mProgressDialog.setMessage(tip);</span><br><span class="line">           &#125;</span><br><span class="line"></span><br><span class="line">           <span class="meta">@Override</span></span><br><span class="line">           <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCompleted</span><span class="params">()</span> </span>&#123;</span><br><span class="line">               mProgressDialog.dismiss();</span><br><span class="line">           &#125;</span><br><span class="line"></span><br><span class="line">           <span class="meta">@Override</span></span><br><span class="line">           <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onError</span><span class="params">(String error)</span> </span>&#123;</span><br><span class="line">               Toast.makeText(MainActivity.<span class="keyword">this</span>, error, Toast.LENGTH_LONG).show();</span><br><span class="line">               mProgressDialog.dismiss();</span><br><span class="line">           &#125;</span><br><span class="line"></span><br><span class="line">           <span class="meta">@Override</span></span><br><span class="line">           <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCancelUpdate</span><span class="params">()</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">           &#125;</span><br><span class="line"></span><br><span class="line">           <span class="meta">@Override</span></span><br><span class="line">           <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onBackgroundTrigger</span><span class="params">()</span> </span>&#123;</span><br><span class="line">               Toast.makeText(MainActivity.<span class="keyword">this</span>, <span class="string">"转为后台更新，进度由通知栏提示!"</span>, Toast.LENGTH_LONG).show();</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;;</span><br><span class="line">       UpdateManager.getInstance().register(mCallback);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><h3 id="差分包合成-jni"><a href="#差分包合成-jni" class="headerlink" title="差分包合成(jni)"></a>差分包合成(jni)</h3><p>​    此部分采用的差分工具为开源<a href="http://www.daemonology.net/bsdiff/" target="_blank" rel="noopener">bsdiff</a>，用于生成.patch补丁文件，采用jni方式封装一个.so库供java调用，详见”smartupdate”库里的main/cpp目录源码，过程比较简单，就是写个jni的方法来直接调用bsdiff库，目录结构如下：</p><p>main</p><pre><code>-cpp    -bzip2    -CMakeLists.txt    -patchUtils.c    -patchUtils.h    -update-lib.cpp</code></pre><p>​    因为bsdiff还依赖了bzip2，所以这里涉及多个源文件编译链接问题，需要在CMakeLists.txt稍作修改：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"># 将当前 &quot;./src/main/cpp&quot; 目录下的所有源文件保存到 &quot;NATIVE_SRC&quot; 中，然后在 add_library 方法调用。</span><br><span class="line">aux_source_directory( . NATIVE_SRC )</span><br><span class="line"># 将 &quot;./src/main/cpp/bzip2&quot; 目录下的子目录bzip2保存到 &quot;BZIP2_BASE&quot; 中，然后在 add_library 方法调用。</span><br><span class="line">aux_source_directory( ./bzip2 BZIP2_BASE )</span><br><span class="line"># 将 BZIP2_BASE 增加到 NATIVE_SRC 中，这样目录的源文件也加入了编译列表中，当然也可以不加到 NATIVE_SRC，直接调用add_library。</span><br><span class="line">list(APPEND NATIVE_SRC $&#123;BZIP2_BASE&#125;)</span><br><span class="line"></span><br><span class="line">add_library( # Sets the name of the library.</span><br><span class="line">        update-lib</span><br><span class="line">        # Sets the library as a shared library.</span><br><span class="line">        SHARED</span><br><span class="line">        # Provides a relative path to your source file(s).</span><br><span class="line">        $&#123;NATIVE_SRC&#125;)</span><br></pre></td></tr></table></figure><h2 id="差分包生成"><a href="#差分包生成" class="headerlink" title="差分包生成"></a>差分包生成</h2><p>​    服务端见<a href="https://github.com/itlwy/App-Update-Server" target="_blank" rel="noopener">github</a> ，使用时将manifestJsonUrl改成部署的服务器地址即可，如下示例代码片段的注释处</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MainActivity</span> <span class="keyword">extends</span> <span class="title">AppCompatActivity</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> String manifestJsonUrl = <span class="string">"https://raw.githubusercontent.com/itlwy/AppSmartUpdate/master/resources/UpdateManifest.json"</span>;</span><br><span class="line"><span class="comment">//    private String manifestJsonUrl = "http://192.168.2.107:8000/app/UpdateManifest.json";</span></span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="依赖"><a href="#依赖" class="headerlink" title="依赖"></a>依赖</h2><ul><li>okhttp : com.squareup.okhttp3:okhttp:3.11.0</li><li>gson : com.google.code.gson:gson:2.8.0</li><li>numberprogressbar : com.daimajia.numberprogressbar:library:1.4@aar</li></ul><h2 id="License"><a href="#License" class="headerlink" title="License"></a>License</h2><pre><code>   Copyright 2018 lwyLicensed under the Apache License, Version 2.0 (the &quot;License&quot;);you may not use this file except in compliance with the License.You may obtain a copy of the License at   http://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an &quot;AS IS&quot; BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions andlimitations under the License.</code></pre>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;APP自动增量更新&quot;&gt;&lt;a href=&quot;#APP自动增量更新&quot; class=&quot;headerlink&quot; title=&quot;APP自动增量更新&quot;&gt;&lt;/a&gt;APP自动增量更新&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/itlwy/AppSmart
      
    
    </summary>
    
      <category term="Android" scheme="http://yoursite.com/categories/Android/"/>
    
    
      <category term="Android" scheme="http://yoursite.com/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>基于DataBinding的基础View绑定库</title>
    <link href="http://yoursite.com/2018/08/24/DataBinding_View%E7%BB%91%E5%AE%9A%E5%BA%93/"/>
    <id>http://yoursite.com/2018/08/24/DataBinding_View绑定库/</id>
    <published>2018-08-24T12:00:30.000Z</published>
    <updated>2020-09-30T13:33:34.000Z</updated>
    
    <content type="html"><![CDATA[<p>github地址：<a href="https://github.com/itlwy/DBindingView" target="_blank" rel="noopener">https://github.com/itlwy/DBindingView</a></p><div id="table-of-contents"><br><h2>Table of Contents</h2><br><div id="text-table-of-contents"><br><ul><br><li><a href="#sec-1">1. 写在前面</a></li><br><li><a href="#sec-2">2. 缩略图</a></li><br><li><a href="#sec-3">3. 如何引入</a><br><ul><br><li><a href="#sec-3-1">3.1. step1</a></li><br><li><a href="#sec-3-2">3.2. step2</a></li><br></ul><br></li><br><li><a href="#sec-4">4. 如何使用</a><br><ul><br><li><a href="#sec-4-1">4.1. 一般控件</a><br><ul><br><li><a href="#sec-4-1-1">4.1.1. 涉及到的类</a></li><br><li><a href="#sec-4-1-2">4.1.2. 属性</a></li><br></ul><br></li><br><li><a href="#sec-4-2">4.2. RecycleView</a><br><ul><br><li><a href="#sec-4-2-1">4.2.1. 属性</a></li><br><li><a href="#sec-4-2-2">4.2.2. 代码说明</a></li><br></ul><br></li><br></ul><br></li><br><li><a href="#sec-5">5. 参考</a></li><br><li><a href="#sec-6">6. License</a></li><br></ul><br></div><br></div><h1 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面<a id="sec-1" name="sec-1"></a></h1><p>一个对databinding的常用封装库,包含基础控件及recycleview等。不了解databinding的可以先阅读相关网上的资料,这里推荐<a href="https://blog.csdn.net/tianjf0514/article/details/75195108" target="_blank" rel="noopener"><br>Android DataBinding介绍</a></p><h1 id="缩略图"><a href="#缩略图" class="headerlink" title="缩略图"></a>缩略图<a id="sec-2" name="sec-2"></a></h1><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://github.com/itlwy/DBindingView/blob/master/pic/dbindingview.gif" alt="normalWidget" title>                </div>                <div class="image-caption">normalWidget</div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://github.com/itlwy/DBindingView/blob/master/pic/dbindingview1.gif" alt="recycleview" title>                </div>                <div class="image-caption">recycleview</div>            </figure><h1 id="如何引入"><a href="#如何引入" class="headerlink" title="如何引入"></a>如何引入<a id="sec-3" name="sec-3"></a></h1><h2 id="step1"><a href="#step1" class="headerlink" title="step1"></a>step1<a id="sec-3-1" name="sec-3-1"></a></h2><p>Add the JitPack repository to your build file</p><pre><code>allprojects {                        repositories {                                ...                                maven { url &apos;https://jitpack.io&apos; }                        }                }</code></pre><h2 id="step2"><a href="#step2" class="headerlink" title="step2"></a>step2<a id="sec-3-2" name="sec-3-2"></a></h2><p>Add the dependency</p><pre><code>dependencies {                compile &apos;com.github.itlwy:DBindingView:v1.1.14&apos;        }</code></pre><h1 id="如何使用"><a href="#如何使用" class="headerlink" title="如何使用"></a>如何使用<a id="sec-4" name="sec-4"></a></h1><h2 id="一般控件"><a href="#一般控件" class="headerlink" title="一般控件"></a>一般控件<a id="sec-4-1" name="sec-4-1"></a></h2><p>如上面的动态图,目前展示了基本控件的进行一些初始化的封装,目的是将一些繁琐的初始化操作自动化掉。注意,这里用到的属性均是自定义属性,前缀请用app:</p><h3 id="涉及到的类"><a href="#涉及到的类" class="headerlink" title="涉及到的类"></a>涉及到的类<a id="sec-4-1-1" name="sec-4-1-1"></a></h3><table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides"><br><br><br><colgroup><br><col class="left"><br><br><col class="left"><br><br><col class="left"><br></colgroup><br><thead><br><tr><br><th scope="col" class="left">类名</th><br><th scope="col" class="left">包名</th><br><th scope="col" class="left">说明</th><br></tr><br></thead><br><br><tbody><br><tr><br><td class="left">KeyValue</td><br><td class="left">com.lwy.dbindingview.data</td><br><td class="left">存储key(Integer)-value(String),主要用于spinner、checkbox、radiogroup等,实现数据源的key-label模式</td><br></tr><br></tbody><br><br><tbody><br><tr><br><td class="left">BindingSpinner</td><br><td class="left">com.lwy.dbindingview.bindingadapter.spinner</td><br><td class="left">封装的双向绑定自定义Spinner</td><br></tr><br><br><br><tr><br><td class="left">DataBindingRadioGroup</td><br><td class="left">com.lwy.dbindingview.bindingadapter.radiogroup</td><br><td class="left">封装的双向绑定自定义RadioGroup</td><br></tr><br><br><br><tr><br><td class="left">DataBindingRadioButton</td><br><td class="left">com.lwy.dbindingview.bindingadapter.radiogroup</td><br><td class="left">封装的双向绑定自定义RadioButton</td><br></tr><br><br><br><tr><br><td class="left">BindingCheckGroup</td><br><td class="left">com.lwy.dbindingview.bindingadapter.checkbox</td><br><td class="left">封装的双向绑定自定义LinearLayout,用作BindingCheckBox容器</td><br></tr><br><br><br><tr><br><td class="left">BindingCheckBox</td><br><td class="left">com.lwy.dbindingview.bindingadapter.checkbox</td><br><td class="left">封装的双向绑定自定义CheckBox</td><br></tr><br><br><br><tr><br><td class="left">BindingEditText</td><br><td class="left">com.lwy.dbindingview.bindingadapter.edittext.BindingEditText</td><br><td class="left">让EditText支持绑定数值类型,eg:Integer、Double&#x2026;</td><br></tr><br></tbody><br></table><h3 id="属性"><a href="#属性" class="headerlink" title="属性"></a>属性<a id="sec-4-1-2" name="sec-4-1-2"></a></h3><table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides"><br><br><br><colgroup><br><col class="left"><br><br><col class="left"><br><br><col class="left"><br><br><col class="left"><br><br><col class="left"><br></colgroup><br><thead><br><tr><br><th scope="col" class="left">控件</th><br><th scope="col" class="left">自定义属性</th><br><th scope="col" class="left">值类型</th><br><th scope="col" class="left">值</th><br><th scope="col" class="left">说明</th><br></tr><br></thead><br><br><tbody><br><tr><br><td class="left">BindingSpinner</td><br><td class="left">selectedValue</td><br><td class="left">KeyValue</td><br><td class="left">&#xa0;</td><br><td class="left">绑定选中的值</td><br></tr><br><br><br><tr><br><td class="left">&#xa0;</td><br><td class="left">spinneritems</td><br><td class="left">List&lt;KeyValue&gt;</td><br><td class="left">&#xa0;</td><br><td class="left">spinner的适配器数据源</td><br></tr><br></tbody><br><br><tbody><br><tr><br><td class="left">DataBindingRadioGroup</td><br><td class="left">selectedValue</td><br><td class="left">KeyValue</td><br><td class="left">&#xa0;</td><br><td class="left">绑定RadioGroup选中的值</td><br></tr><br><br><br><tr><br><td class="left">&#xa0;</td><br><td class="left">items</td><br><td class="left">List&lt;KeyValue&gt;</td><br><td class="left">&#xa0;</td><br><td class="left">设置该属性可动态渲染子view</td><br></tr><br><br><br><tr><br><td class="left">&#xa0;</td><br><td class="left">childViewFactory</td><br><td class="left">DBCustomViewFactory&lt;DataBindingRadioButton&gt;</td><br><td class="left">&#xa0;</td><br><td class="left">当设置了items属性时,可通过此属性传入继承自DataBindingRadioButton的自定义view,不设则用默认类</td><br></tr><br><br><br><tr><br><td class="left">DataBindingRadioButton</td><br><td class="left">value</td><br><td class="left">KeyValue</td><br><td class="left">&#xa0;</td><br><td class="left">初始化RadioButton的值</td><br></tr><br></tbody><br><br><tbody><br><tr><br><td class="left">BindingCheckGroup</td><br><td class="left">selectedValues</td><br><td class="left">List&lt;KeyValue&gt;</td><br><td class="left">&#xa0;</td><br><td class="left">存储checkbox选中的值,默认用,分割</td><br></tr><br><br><br><tr><br><td class="left">&#xa0;</td><br><td class="left">items</td><br><td class="left">List&lt;KeyValue&gt;</td><br><td class="left">&#xa0;</td><br><td class="left">设置该属性可动态渲染子view</td><br></tr><br><br><br><tr><br><td class="left">&#xa0;</td><br><td class="left">childViewFactory</td><br><td class="left">DBCustomViewFactory&lt;BindingCheckBox&gt;</td><br><td class="left">&#xa0;</td><br><td class="left">当设置了items属性时,可通过此属性传入继承自BindingCheckBox的自定义view,不设则用默认类</td><br></tr><br></tbody><br><br><tbody><br><tr><br><td class="left">BindingCheckBox</td><br><td class="left">value</td><br><td class="left">KeyValue</td><br><td class="left">&#xa0;</td><br><td class="left">设置值</td><br></tr><br><br><br><tr><br><td class="left">ImageView</td><br><td class="left">uri</td><br><td class="left">String or ObservableField<string></string></td><br><td class="left">&#xa0;</td><br><td class="left">图片的url,用的加载框架是glide</td><br></tr><br><br><br><tr><br><td class="left">&#xa0;</td><br><td class="left">placeholderImageRes</td><br><td class="left">int</td><br><td class="left">eg:R.mipmap.ic_launcher</td><br><td class="left">占位图</td><br></tr><br><br><br><tr><br><td class="left">&#xa0;</td><br><td class="left">request_width、request_width</td><br><td class="left">int</td><br><td class="left">&#xa0;</td><br><td class="left">设置图片的大小,不设置默认用view的大小,2个属性必修同时设置才有效</td><br></tr><br></tbody><br><br><tbody><br><tr><br><td class="left">View</td><br><td class="left">clickCommand</td><br><td class="left">ReplyCommand</td><br><td class="left">&#xa0;</td><br><td class="left">点击事件触发的命令</td><br></tr><br><br><br><tr><br><td class="left">&#xa0;</td><br><td class="left">display</td><br><td class="left">boolean</td><br><td class="left">&#xa0;</td><br><td class="left">控制view的Visibility</td><br></tr><br></tbody><br><br><tbody><br><tr><br><td class="left">BindingEdittext</td><br><td class="left">textDouble、textInt、textFloat、textLong</td><br><td class="left">Double、Float、Integer、Long</td><br><td class="left">&#xa0;</td><br><td class="left">绑定数值类型</td><br></tr><br><br><br><tr><br><td class="left">&#xa0;</td><br><td class="left">regularExpression</td><br><td class="left">&#xa0;</td><br><td class="left"></td><br><td class="left">正则表达式校验输入值&#xa0;</td><br></tr><br></tbody><br></table><p>具体使用很简单,直接看demo&#x2013;&gt;com.lwy.dbindingview.base_widget.WidgeActivity</p><p>这里说明下DataBindingRadioGroup和BindingCheckGroup,</p><pre><code>&lt;com.lwy.dbindingview.bindingadapter.radiogroup.DataBindingRadioGroup                      android:layout_width=&quot;0dp&quot;                      android:layout_height=&quot;wrap_content&quot;                      android:layout_marginLeft=&quot;@dimen/margin_medium&quot;                      android:layout_marginRight=&quot;@dimen/margin_medium&quot;                      android:layout_weight=&quot;10&quot;                      android:orientation=&quot;horizontal&quot;                      app:items=&quot;@{viewmodel.sexList}&quot;                      app:childViewFactory=&quot;@{ViewFactory.createDBRadioButton()}&quot;                      app:selectedValue=&quot;@={viewmodel.sex}&quot;/&gt;</code></pre><p>这里的app:items属性,让其根据sexList动态渲染子view(DataBindingRadioButton),也可以选择在布局里直接写子View，如：</p><pre><code>&lt;com.lwy.dbindingview.bindingadapter.radiogroup.DataBindingRadioGroup                        android:layout_width=&quot;0dp&quot;                        android:layout_height=&quot;wrap_content&quot;                        android:layout_marginLeft=&quot;@dimen/margin_medium&quot;                        android:layout_marginRight=&quot;@dimen/margin_medium&quot;                        android:layout_weight=&quot;10&quot;                        android:orientation=&quot;horizontal&quot;                        app:selectedValue=&quot;@={viewmodel.sex}&quot;&gt;                        &lt;com.lwy.dbindingview.bindingadapter.radiogroup.DataBindingRadioButton                            android:layout_width=&quot;wrap_content&quot;                            app:value=&quot;@{viewmodel.sex_male}&quot;                            android:layout_height=&quot;wrap_content&quot;/&gt;                        &lt;com.lwy.dbindingview.bindingadapter.radiogroup.DataBindingRadioButton                            android:layout_width=&quot;wrap_content&quot;                            app:value=&quot;@{viewmodel.sex_female}&quot;                            android:layout_height=&quot;wrap_content&quot;/&gt;                    &lt;/com.lwy.dbindingview.bindingadapter.radiogroup.DataBindingRadioGroup&gt;</code></pre><hr><p>BindingCheckGroup同理</p><h2 id="RecycleView"><a href="#RecycleView" class="headerlink" title="RecycleView"></a>RecycleView<a id="sec-4-2" name="sec-4-2"></a></h2><h3 id="属性-1"><a href="#属性-1" class="headerlink" title="属性"></a>属性<a id="sec-4-2-1" name="sec-4-2-1"></a></h3><table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides"><br><br><br><colgroup><br><col class="left"><br><br><col class="left"><br><br><col class="left"><br><br><col class="left"><br></colgroup><br><thead><br><tr><br><th scope="col" class="left">属性</th><br><th scope="col" class="left">值类型</th><br><th scope="col" class="left">值</th><br><th scope="col" class="left">说明</th><br></tr><br></thead><br><br><tbody><br><tr><br><td class="left">itemBinding</td><br><td class="left">ItemBinding</td><br><td class="left">&#xa0;</td><br><td class="left">必填,item的布局和变量的绑定关系包装类</td><br></tr><br><br><br><tr><br><td class="left">items</td><br><td class="left">List&lt;T&gt;</td><br><td class="left">&#xa0;</td><br><td class="left">必填,数据源</td><br></tr><br><br><br><tr><br><td class="left">adapter</td><br><td class="left">BindingRecyclerViewAdapter&lt;T&gt;</td><br><td class="left">&#xa0;</td><br><td class="left">选填,可继承BindingRecyclerViewAdapter自定义适配器</td><br></tr><br><br><br><tr><br><td class="left">itemIds</td><br><td class="left">BindingRecyclerViewAdapter.ItemIds&lt;? super T&gt;</td><br><td class="left">&#xa0;</td><br><td class="left">选填,不设置则默认使用position</td><br></tr><br><br><br><tr><br><td class="left">viewHolder</td><br><td class="left">BindingRecyclerViewAdapter.ViewHolderFactory</td><br><td class="left">&#xa0;</td><br><td class="left">选填,可继承以实现自定义ViewHolder</td><br></tr><br><br><br><tr><br><td class="left">onItemClick</td><br><td class="left">BindingRecyclerViewAdapter.OnItemClickListener</td><br><td class="left">&#xa0;</td><br><td class="left">item点击事件</td><br></tr><br><br><br><tr><br><td class="left">layoutManager</td><br><td class="left">LayoutManagers.LayoutManagerFactory</td><br><td class="left">&#xa0;</td><br><td class="left">必填,布局方式,如线性:LayoutManagers.linear()</td><br></tr><br><br><br><tr><br><td class="left">&#xa0;</td><br><td class="left">&#xa0;</td><br><td class="left">&#xa0;</td><br><td class="left">&#xa0;</td><br></tr><br></tbody><br></table><h3 id="代码说明"><a href="#代码说明" class="headerlink" title="代码说明"></a>代码说明<a id="sec-4-2-2" name="sec-4-2-2"></a></h3><ol><li><p>单个布局的简单列表代码片段</p><p>1、定义viewmodel</p><pre><code>public class ItemVM extends BaseObservable {    public final boolean checkable; // for now,it&apos;s useless    @Bindable    private int index;    @Bindable    private boolean checked;    public ItemVM(int index, boolean checkable) {        this.index = index;        this.checkable = checkable;    }    public int getIndex() {        return index;    }    public boolean isChecked() {        return checked;    }    public boolean onToggleChecked(View v) {        if (!checkable) {            return false;        }        checked = !checked;//        notifyPropertyChanged(com.lwy.dbindingview.BR.checked);        return true;    }}</code></pre><p>2、定义Item布局文件R.layout.item</p><pre><code>&lt;layout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;    xmlns:tools=&quot;http://schemas.android.com/tools&quot;&gt;    &lt;data&gt;        &lt;variable            name=&quot;item&quot;            type=&quot;com.lwy.dbindingview.recycleview.vm.ItemVM&quot; /&gt;        &lt;import type=&quot;android.view.View&quot; /&gt;    &lt;/data&gt;    &lt;LinearLayout        android:layout_width=&quot;match_parent&quot;        android:layout_height=&quot;wrap_content&quot;        android:background=&quot;?selectableItemBackground&quot;        android:onLongClickListener=&quot;@{item::onToggleChecked}&quot;        android:longClickable=&quot;@{item.checkable}&quot;        android:orientation=&quot;horizontal&quot;&gt;        &lt;TextView            style=&quot;@style/TextAppearance.AppCompat.Body1&quot;            android:layout_width=&quot;0dp&quot;            android:layout_height=&quot;wrap_content&quot;            android:layout_weight=&quot;1&quot;            android:padding=&quot;16dp&quot;            android:text=&apos;@{&quot;Item &quot; + (item.index + 1)}&apos;            tools:text=&quot;Item 1&quot; /&gt;        &lt;ImageView            android:layout_width=&quot;48dp&quot;            android:layout_height=&quot;48dp&quot;            android:src=&quot;@mipmap/ic_action_check&quot;            android:visibility=&quot;@{item.checked ? View.VISIBLE : View.GONE}&quot; /&gt;    &lt;/LinearLayout&gt;&lt;/layout&gt;</code></pre><p>3、创建layout和viewmodel变量的绑定关系包装类</p><pre><code>public final ItemBinding&lt;ItemVM&gt; singleItem = ItemBinding.of(com.lwy.dbindingview.BR.item, R.layout.item);</code></pre><p>4、创建数据源</p><pre><code>public final ObservableList&lt;ItemVM&gt; items = new ObservableArrayList&lt;&gt;();</code></pre><p>5、设置RecycleView的属性</p><pre><code>... &lt;android.support.v7.widget.RecyclerView               android:id=&quot;@+id/list&quot;               android:layout_width=&quot;match_parent&quot;               android:layout_height=&quot;match_parent&quot;               app:itemBinding=&quot;@{viewmodel.singleItem}&quot;               app:items=&quot;@{viewmodel.items}&quot;               app:layoutManager=&quot;@{LayoutManagers.linear()}&quot;/&gt; ...</code></pre></li><li><p>复杂布局代码片段</p><p>1、定义viewmodel(同上)</p><p>2、定义Item布局文件R.layout.item(同上)</p><p>3、创建footer的viewmodel</p><pre><code>public class FooterVM extends RcVFooterVM {    public final ReplyCommand clickCommand = new ReplyCommand(new Action0() {        @Override        public void call() {            if (!getIsFooterLoading().get()) {                switchLoading(true);                callback.execute();            }        }    });    private ReplyCommand callback;</code></pre></li></ol><pre><code>        public final ObservableField&lt;String&gt; noMoreTip = new ObservableField&lt;&gt;();        /*            state : 0 loading            state : 1 idle         */        public final ObservableField&lt;Integer&gt; state = new ObservableField&lt;&gt;();        public FooterVM(ReplyCommand callback) {            super();            this.callback = callback;            switchLoading(false);            noMoreTip.set(&quot;暂无更多&quot;);        }        @Override        protected ReplyCommand&lt;Integer&gt; geneOnLoadMoreCommand() {            return new ReplyCommand&lt;&gt;(new Action1&lt;Integer&gt;() {                @Override                public void call(Integer integer) {                    FooterVM.this.callback.execute();    //                switchLoading(true);                }            });        }        @Override        public void switchLoading(boolean flag) {            if (flag) {                state.set(0);            } else {                state.set(1);            }            super.switchLoading(flag);        }    }4、创建footer的布局文件R.layout.default_loading    &lt;layout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;            xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;            xmlns:tools=&quot;http://schemas.android.com/tools&quot;&gt;        &lt;data&gt;            &lt;variable                name=&quot;footerVM&quot;                type=&quot;com.lwy.dbindingview.recycleview.vm.FooterVM&quot;/&gt;        &lt;/data&gt;    &lt;LinearLayout            android:layout_width=&quot;match_parent&quot;            android:layout_height=&quot;wrap_content&quot;            android:orientation=&quot;vertical&quot;            app:clickCommand=&quot;@{footerVM.clickCommand}&quot;&gt;            &lt;LinearLayout                android:layout_width=&quot;match_parent&quot;                android:layout_height=&quot;45dp&quot;                android:gravity=&quot;center&quot;                android:orientation=&quot;horizontal&quot;                android:padding=&quot;8dp&quot;                app:display=&quot;@{footerVM.state==0?true:false}&quot;&gt;                &lt;ProgressBar                    android:layout_width=&quot;32dp&quot;                    android:layout_height=&quot;32dp&quot;                    /&gt;                &lt;TextView                    android:layout_width=&quot;wrap_content&quot;                    android:layout_height=&quot;wrap_content&quot;                    android:layout_marginLeft=&quot;8dp&quot;                    android:text=&quot;正在加载...&quot;                    android:textSize=&quot;14sp&quot;/&gt;            &lt;/LinearLayout&gt;            &lt;LinearLayout                android:layout_width=&quot;match_parent&quot;                android:layout_height=&quot;45dp&quot;                android:gravity=&quot;center&quot;                android:orientation=&quot;horizontal&quot;                android:padding=&quot;8dp&quot;                app:display=&quot;@{footerVM.state==1?true:false}&quot;&gt;                &lt;TextView                    android:layout_width=&quot;wrap_content&quot;                    android:layout_height=&quot;wrap_content&quot;                    android:layout_gravity=&quot;center&quot;                    android:layout_marginLeft=&quot;8dp&quot;                    android:text=&quot;@{footerVM.noMoreTip}&quot;                    android:textSize=&quot;14sp&quot;/&gt;            &lt;/LinearLayout&gt;        &lt;/LinearLayout&gt;    &lt;/layout&gt;5、创建layout和viewmodel变量的绑定关系包装类    // 这里根据class类型来控制不同的item    public final ItemBinding&lt;Object&gt; multipleItems = ItemBinding.of(new OnItemBindClass&lt;&gt;()                .map(FooterVM.class, BR.footerVM, R.layout.default_loading)                .map(String.class, com.lwy.dbindingview.BR.item, R.layout.item_header_footer)                .map(ItemVM.class, com.lwy.dbindingview.BR.item, R.layout.item));6、创建数据源    public final FooterVM footerVM = new FooterVM(new ReplyCommand&lt;Integer&gt;(new Action1&lt;Integer&gt;() {            @Override            public void call(Integer integer) {                // 异步执行加载数据 完了需要调用 &quot;footerVM.switchLoading(false)&quot; 取消加载状态            }        }));    public final ObservableList&lt;ItemVM&gt; items = new ObservableArrayList&lt;&gt;();    public final MergeObservableList&lt;Object&gt; headerFooterItems = new MergeObservableList&lt;&gt;()                .insertItem(&quot;Header&quot;)                .insertList(items)                .insertItem(footerVM);5、设置RecycleView的属性    ...    &lt;android.support.v7.widget.RecyclerView                   android:id=&quot;@+id/list&quot;                   android:layout_width=&quot;match_parent&quot;                   android:layout_height=&quot;match_parent&quot;                   app:itemBinding=&quot;@{viewmodel.multipleItems}&quot;                   app:items=&quot;@{viewmodel.headerFooterItems}&quot;                   app:layoutManager=&quot;@{LayoutManagers.linear()}&quot;/&gt;    ...</code></pre><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考<a id="sec-5" name="sec-5"></a></h1><ol><li><a href="https://github.com/evant/binding-collection-adapter" target="_blank" rel="noopener">binding-collection-adapter</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;github地址：&lt;a href=&quot;https://github.com/itlwy/DBindingView&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/itlwy/DBindingView&lt;/a&gt;&lt;/p&gt;
&lt;di
      
    
    </summary>
    
      <category term="Android" scheme="http://yoursite.com/categories/Android/"/>
    
    
      <category term="Android" scheme="http://yoursite.com/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>nginx折腾记</title>
    <link href="http://yoursite.com/2018/07/15/nginx%E6%8A%98%E8%85%BE%E8%AE%B0/"/>
    <id>http://yoursite.com/2018/07/15/nginx折腾记/</id>
    <published>2018-07-15T02:13:30.000Z</published>
    <updated>2020-09-30T13:33:34.000Z</updated>
    
    <content type="html"><![CDATA[<p>博客地址：<a href="https://blog.csdn.net/Weiye__Lee/article/details/79700730" target="_blank" rel="noopener">https://blog.csdn.net/Weiye__Lee/article/details/79700730</a></p><h1 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面<a id="sec-1" name="sec-1"></a></h1><p>　　对nginx的折腾做个汇总记录,部分内容参考出处见文章末尾.</p><h1 id="nginx概述"><a href="#nginx概述" class="headerlink" title="nginx概述"></a>nginx概述<a id="sec-2" name="sec-2"></a></h1><p>nginx是一款自由的、开源的、高性能的HTTP服务器和反向代理服务器；同时也是一个IMAP、POP3、SMTP代理服务器；nginx可以作为一个HTTP服务器进行网站的发布处理，另外nginx可以作为反向代理进行负载均衡的实现。</p><h2 id="正向代理"><a href="#正向代理" class="headerlink" title="正向代理"></a>正向代理<a id="sec-2-1" name="sec-2-1"></a></h2><p>正向代理是一般意义上的代理。在如今的网络环境下，我们如果由于技术需要要去访问国外的某些网站，此时你会发现位于国外的某网站我们通过浏览器是没有办法访问的，此时大家可能都会用一个操作FQ进行访问，FQ的方式主要是找到一个可以访问国外网站的代理服务器，我们将请求发送给代理服务器，代理服务器去访问国外的网站，然后将访问到的数据传递给我们<br>上述这样的代理模式称为正向代理，正向代理最大的特点是客户端非常明确要访问的服务器地址；服务器只清楚请求来自哪个代理服务器，而不清楚来自哪个具体的客户端；正向代理模式屏蔽或者隐藏了真实客户端信息。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://s8.postimg.cc/qcxh11eg5/nginx_1.png" alt="这里写图片描述" title>                </div>                <div class="image-caption">这里写图片描述</div>            </figure><h2 id="反向代理"><a href="#反向代理" class="headerlink" title="反向代理"></a>反向代理<a id="sec-2-2" name="sec-2-2"></a></h2><p>反向代理多用于分布式部署；也就是通过部署多台服务器来解决访问并发问题。<br><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://s8.postimg.cc/cw0iik9dx/nginx_2.png" alt="这里写图片描述" title>                </div>                <div class="image-caption">这里写图片描述</div>            </figure></p><p>通过上述的图解大家就可以看清楚了，多个客户端给服务器发送的请求，nginx服务器接收到之后，按照一定的规则分发给了后端的业务处理服务器进行处理了。此时，请求的来源也就是客户端是明确的，但是请求具体由哪台服务器处理的并不明确了，nginx扮演的就是一个反向代理角色<br>反向代理，主要用于服务器集群分布式部署的情况下，反向代理隐藏了服务器的信息。对于反向这个词，刚开始接触的时候没理解为啥叫反向,这里的抽取出来注明下：</p><ul><li><p>正向代理<br>　　代理服务器实现对被代理服务端隐藏客户端信息,对客户端的请求进行转发处理.此时,客户端是明确知道被代理的服务端信息的,而被代理服务端是不知道客户端信息的</p></li><li><p>反向代理<br>　　代理服务器实现对客户端的被代理服务器信息的隐藏,对客户端的请求进行转发处理.此时,客户端是不知道被代理服务端的信息的,而被代理服务端是知道客户端信息的，注意到了吧,后半句两边整好反过来</p></li></ul><h1 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装<a id="sec-3" name="sec-3"></a></h1><h2 id="ubantu包管理器安装"><a href="#ubantu包管理器安装" class="headerlink" title="ubantu包管理器安装"></a>ubantu包管理器安装<a id="sec-3-1" name="sec-3-1"></a></h2><p>使用包管理器直接安装是最快捷方便的,但是缺点也明显,想自由装对应的版本就困难了。anyway,在terminal里敲如下命令:</p><pre><code>$ sudo apt-get install nginx</code></pre><p>安装完成即可，在/usr/sbin/目录下是nginx命令所在目录，在/etc/nginx/目录下是nginx所有的配置文件，用于配置nginx服务器以及负载均衡等信息</p><ul><li><p>查看nginx进程是否启动:</p><pre><code>$ ps -ef | grep nginx</code></pre></li><li><p>启动nginx服务器,直接执行nginx会按照默认的配置文件进行服务器的启动</p><pre><code>$ nginx # 启动$ nginx -s stop  停止$ nginx -s quit  停止</code></pre></li></ul><h2 id="ubantu源码安装"><a href="#ubantu源码安装" class="headerlink" title="ubantu源码安装"></a>ubantu源码安装<a id="sec-3-2" name="sec-3-2"></a></h2><pre><code> #安装gcc g++的依赖库$ apt-get install build-essential                                     $ apt-get install libtool         # 安装pcre依赖库$ sudo apt-get install libpcre3 libpcre3-dev #安装 zlib依赖库$ sudo apt-get install zlib1g-dev # 安装 ssl依赖库$ sudo apt-get install openssl #下载nginx最新版本：参考用nginx-1.10.3/$ wget http://nginx.org/download/nginx-1.11.3.tar.gz #解压：$ tar -zxvf nginx-1.11.3.tar.gz#进入解压目录：$ cd nginx-1.11.3#配置：$ ./configure --prefix=/usr/local/nginx #编辑nginx：$ make#安装nginx：$ sudo make install#启动nginx：$ sudo /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf注意：-c 指定配置文件的路径，不加的话，nginx会自动加载默认路径的配置文件，可以通过 -h查看帮助命令。#查看nginx进程：$ ps -ef|grep nginx#检查配置文件是否正确$ sudo /usr/local/nginx/sbin/nginx -t-----$ /usr/local/nginx/sbin/nginx -s reload # Nginx重新加载配置$ /usr/local/nginx/sbin/nginx -s stop #停止</code></pre><h1 id="nginx配置"><a href="#nginx配置" class="headerlink" title="nginx配置"></a>nginx配置<a id="sec-4" name="sec-4"></a></h1><h2 id="nginx-conf"><a href="#nginx-conf" class="headerlink" title="nginx.conf"></a>nginx.conf<a id="sec-4-1" name="sec-4-1"></a></h2><p>修改部署目录下conf子目录的nginx.conf文件（如上面源码安装的/usr/local/nginx/conf/nginx.conf）内容</p><pre><code>main                                # 全局配置events {                            # nginx工作模式配置}http {                                # http设置    ....    server {                        # 服务器主机配置        ....        location {                    # 路由配置            ....        }        location path {            ....        }        location otherpath {            ....        }    }    server {        ....        location {            ....        }    }    upstream name {                    # 负载均衡配置        ....    }}</code></pre><p>如上述配置文件所示，主要由6个部分组成：</p><ul><li>main：用于进行nginx全局信息的配置</li><li>events：用于nginx工作模式的配置</li><li>http：用于进行http协议信息的一些配置</li><li>server：用于进行服务器访问信息的配置</li><li>location：用于进行访问路由的配置</li><li>upstream：用于进行负载均衡的配置</li></ul><h3 id="main模块"><a href="#main模块" class="headerlink" title="main模块"></a>main模块<a id="sec-4-1-1" name="sec-4-1-1"></a></h3><pre><code># user nobody nobody;worker_processes 2;# error_log logs/error.log# error_log logs/error.log notice# error_log logs/error.log info# pid logs/nginx.pidworker_rlimit_nofile 1024;</code></pre><p>上述配置都是放在main全局配置模块中的配置项</p><ul><li>user 用来指定进程运行的用户及用户组,默认为nobody</li><li>work_processes 用来指定nginx开启的子进程数量,数量通常是cpu内核的2倍,官方宣称一般开1个就可以了</li><li>error_log 定义错误日志文件的位置及输出级别[debug/info/notice/warn/error/crit]</li><li>pid 用来指定进程pid的存储文件的位置</li><li>worker_rlimit_nofile 指定一个进程可以打开的最多文件描述符数量</li></ul><h3 id="event-模块"><a href="#event-模块" class="headerlink" title="event 模块"></a>event 模块<a id="sec-4-1-2" name="sec-4-1-2"></a></h3><pre><code>event {    worker_connections 1024;    # 并发总数是 worker_processes 和 worker_connections 的乘积    # 即 max_clients = worker_processes * worker_connections    # 在设置了反向代理的情况下，max_clients = worker_processes * worker_connections / 4  为什么    # 为什么上面反向代理要除以4，应该说是一个经验值    # 根据以上条件，正常情况下的Nginx Server可以应付的最大连接数为：4 * 8000 = 32000    # worker_connections 值的设置跟物理内存大小有关    # 因为并发受IO约束，max_clients的值须小于系统可以打开的最大文件数    # 而系统可以打开的最大文件数和内存大小成正比，一般1GB内存的机器上可以打开的文件数大约是10万左右    # 我们来看看360M内存的VPS可以打开的文件句柄数是多少：    # $ cat /proc/sys/fs/file-max    # 输出 34336    # 32000 &lt; 34336，即并发连接总数小于系统可以打开的文件句柄总数，这样就在操作系统可以承受的范围之内    # 所以，worker_connections 的值需根据 worker_processes 进程数目和系统可以打开的最大文件总数进行适当地进行设置    # 使得并发总数小于操作系统可以打开的最大文件数目    # 其实质也就是根据主机的物理CPU和内存进行配置    # 当然，理论上的并发总数可能会和实际有所偏差，因为主机还有其他的工作进程需要消耗系统资源。    # ulimit -SHn 65535    multi_accept on;    use epoll;}</code></pre><ul><li>worker_connections 指定单个进程最大可以接收的连接数量.注意,nginx最大连接数和worker_processes、worker_connections、能打开的文件描述符数量、系统资源等因素共同决定</li><li>multi_accept 指定nginx在收到一个新连接通知后,尽可能多的接收更多的l连接</li><li>use epoll 指定线程轮询的方法,如果是linux2.6+,使用epoll。如果是BSD如mac,请使用Kqueue</li></ul><h3 id="http-模块"><a href="#http-模块" class="headerlink" title="http 模块"></a>http 模块<a id="sec-4-1-3" name="sec-4-1-3"></a></h3><p>作为web服务器，http模块是nginx最核心的一个模块，配置项也是比较多的，项目中会设置到很多的实际业务场景，需要根据硬件信息进行适当的配置，常规情况下，使用默认配置即可</p><h3 id="server模块"><a href="#server模块" class="headerlink" title="server模块"></a>server模块<a id="sec-4-1-4" name="sec-4-1-4"></a></h3><p>srever模块配置是http模块中的一个子模块，用来定义一个虚拟访问主机，也就是一个虚拟服务器的配置信息</p><pre><code>server {    listen        80;    server_name localhost    192.168.1.100;    root        /nginx/www;    index        index.php index.html index.html;    charset        utf-8;    access_log    logs/access.log;    error_log    logs/error.log;    ......}</code></pre><p>核心配置信息如下：</p><ul><li>server：一个虚拟主机的配置，一个http中可以配置多个server</li><li>server_name：指定ip地址或者域名，多个配置之间用空格分隔</li><li>root：表示整个server虚拟主机内的根目录，所有当前主机中web项目的根目录</li><li>index：用户访问web网站时的全局首页</li><li>charset：用于设置www/路径中配置的网页的默认编码格式</li><li>access_log：用于指定该虚拟主机服务器中的访问记录日志存放路径</li><li>error_log：用于指定该虚拟主机服务器中访问错误日志的存放路径</li></ul><h3 id="location-模块"><a href="#location-模块" class="headerlink" title="location 模块"></a>location 模块<a id="sec-4-1-5" name="sec-4-1-5"></a></h3><p>location模块是nginx配置中出现最多的一个配置，主要用于配置路由访问信息<br>在路由访问信息配置中关联到负载均衡、url重写等等各项功能，所以location模块也是一个非常重要的配置模块,而其中匹配模式则是玩转转发的秘籍<br>匹配语法</p><pre><code>location [ = | ~ | ~* | ^~ ] uri { ... }location @name { ... }</code></pre><p>没错，就这么多，实际写在loacation中大概是这样的</p><pre><code>location = / {}-----location  [指令模式] url匹配模式 {}</code></pre><p>指令模式指用于匹配的方式，即精确匹配,前缀匹配还是正则匹配，当然这个是可选的，如果不写，则退化成正常匹配或者全匹配。url匹配模式则需要匹配的url，可以看成是web开发中的路由</p><h4 id="匹配模式"><a href="#匹配模式" class="headerlink" title="匹配模式"></a>匹配模式<a id="sec-4-1-5-1" name="sec-4-1-5-1"></a></h4><ol><li><p>精确匹配</p><p>= 指令用于精确字符匹配(模式),不能使用正则,区分大小写,为了直观的观察匹配命中的location，使用rewrite指令，用于转发。目前只要理解命中了就重定向到rewrite后面的url即可</p><pre><code>location = /demo {        rewrite ^ http://google.com;}</code></pre><p>上述的配置表示只有访问 <a href="http://host:port/demo" target="_blank" rel="noopener">http://host:port/demo</a> 这样的url，才能跳转到google的页面。除此之外的任何地址都无法访问，那怕是访问<a href="http://host:port/demo/" target="_blank" rel="noopener">http://host:port/demo/</a> 这个地址也不行。因为url匹配模式是/demo</p></li><li><p>前缀匹配</p><p>^~指令用于字符前缀匹配，和=精确匹配一样，也是用于字符确定的匹配，不能使用正则且区分大小写。和=不同的在于，^~指令下，访问的url无需url匹配模式一模一样，只需要其开头前缀和url匹配模式一样即可</p><pre><code>location ^~ /demo {        rewrite ^ http://google.com;}</code></pre><p>对于该模式（/demo）,访问下列的地址都能匹配:</p><ul><li><a href="http://127.0.0.1/demo" target="_blank" rel="noopener">http://127.0.0.1/demo</a></li><li><a href="http://127.0.0.1/demo/" target="_blank" rel="noopener">http://127.0.0.1/demo/</a></li><li><a href="http://127.0.0.1/demo/aaa" target="_blank" rel="noopener">http://127.0.0.1/demo/aaa</a></li><li><a href="http://127.0.0.1/demo/aaa/bbb" target="_blank" rel="noopener">http://127.0.0.1/demo/aaa/bbb</a></li><li><a href="http://127.0.0.1/demo/AAA" target="_blank" rel="noopener">http://127.0.0.1/demo/AAA</a></li><li><a href="http://127.0.0.1/demoaaa" target="_blank" rel="noopener">http://127.0.0.1/demoaaa</a></li><li><a href="http://127.0.0.1/demo.aaa" target="_blank" rel="noopener">http://127.0.0.1/demo.aaa</a></li></ul><p>只需要以/demo为前缀开头的url都能匹配。与该模式后的是否大小写无关。<br>^~不支持正则。模式/demo$中的$并不代表字符模式结束，而是一个是实实在在的$，只有访问/demo$开头的url才能匹配，<a href="http://host:port/demo" target="_blank" rel="noopener">http://host:port/demo</a> 则不再匹配。前缀匹配通常用于匹配文件夹，如配置静态文件。</p></li><li><p>正则匹配</p><p>众所周知，nginx的url功能强大，配置灵活。字符匹配中，支持正则和不支持正则完全是两个境界。前面的两种方式都不能使用正则，未免让人觉得nginx有点虚夸。</p><p>实际上，nginx支持正则匹配。所使用的指令是~和~*，前者表示使用正则，区分大小写，后者表示使用正则，不区分大小写。与前缀匹配一样，正则匹配也是只需匹配以url模式开头的即可。</p><pre><code>location ~ /[0-9]emo {        rewrite ^ http://google.com;}</code></pre><p>对于上述的模式，可以匹配的url如下：</p><ul><li><a href="http://127.0.0.1/5emo" target="_blank" rel="noopener">http://127.0.0.1/5emo</a></li><li><a href="http://127.0.0.1/9emo" target="_blank" rel="noopener">http://127.0.0.1/9emo</a></li><li><a href="http://127.0.0.1/5emo/aaa" target="_blank" rel="noopener">http://127.0.0.1/5emo/aaa</a></li><li><a href="http://127.0.0.1/5emo/AAA" target="_blank" rel="noopener">http://127.0.0.1/5emo/AAA</a></li><li><a href="http://127.0.0.1/5emoaaa" target="_blank" rel="noopener">http://127.0.0.1/5emoaaa</a></li></ul><p>使用~*则不区分大小写</p><pre><code>location ~ /[0-9]EmO {        rewrite ^ http://google.com;}</code></pre><p>下面的都能匹配</p><ul><li><a href="http://127.0.0.1/5emo" target="_blank" rel="noopener">http://127.0.0.1/5emo</a></li><li><a href="http://127.0.0.1/9Emo" target="_blank" rel="noopener">http://127.0.0.1/9Emo</a></li><li><a href="http://127.0.0.1/5emo/Aaa" target="_blank" rel="noopener">http://127.0.0.1/5emo/Aaa</a></li><li><a href="http://127.0.0.1/5eMoEaaa" target="_blank" rel="noopener">http://127.0.0.1/5eMoEaaa</a></li></ul></li><li><p>正常匹配</p><p>正常匹配的指令为空，即没有指定匹配指令的即为正常匹配。其形式类似 /XXX/YYY.ZZZ正常匹配中的url匹配模式可以使用正则，不区分大小写。</p><pre><code>location /demo {        rewrite ^ http://google.com;}</code></pre><p>上述模式指的是匹配/demo的url，下面的都能匹配</p><ul><li><a href="http://192.168.33.10/demo" target="_blank" rel="noopener">http://192.168.33.10/demo</a></li><li><a href="http://192.168.33.10/demo/" target="_blank" rel="noopener">http://192.168.33.10/demo/</a></li><li><a href="http://192.168.33.10/demo/aaa" target="_blank" rel="noopener">http://192.168.33.10/demo/aaa</a></li><li><a href="http://192.168.33.10/demo/aaa/bbb" target="_blank" rel="noopener">http://192.168.33.10/demo/aaa/bbb</a></li><li><a href="http://192.168.33.10/demo/AAA" target="_blank" rel="noopener">http://192.168.33.10/demo/AAA</a></li><li><a href="http://192.168.33.10/demoaaa" target="_blank" rel="noopener">http://192.168.33.10/demoaaa</a></li><li><a href="http://192.168.33.10/demo.aaa" target="_blank" rel="noopener">http://192.168.33.10/demo.aaa</a></li></ul><p>正常匹配和前缀匹配的差别在于优先级。前缀的优先级高于正常匹配</p></li><li><p>全匹配</p><p>全匹配与正常匹配一样，没有匹配指令，匹配的url模式仅一个斜杠/</p><pre><code>location / {        rewrite ^ http://google.com;}</code></pre><p>全匹配也可以配合 精确匹配和正则匹配一些指令，只不过这样的设定意义不大。通过都会有一个默认的location，这个就是全匹配</p></li><li><p>命名匹配</p><p>命名匹配指的是使用@绑定一个模式，类似变量替换的用法。</p><pre><code>error_page 404 = @not_foundlocation @not_found {          rewrite http://google.com;}</code></pre><p>上述的作用是如果访问没有匹配的url会触发404指令，然后就匹配到@not_found 这个 location上。</p><p>反向代理配置示例:<br>主要修改的节点为http节点,</p><pre><code>http {    omitted...    upstream backend_6001{             #ip_hash;                                                                                       server 192.168.1.207:6001;             server 192.168.1.207:6002;    }server {    listen       5000;    server_name  lwy.com;</code></pre></li></ol><pre><code>}}</code></pre><h4 id="匹配优先级"><a href="#匹配优先级" class="headerlink" title="匹配优先级"></a>匹配优先级<a id="sec-4-1-5-2" name="sec-4-1-5-2"></a></h4><p>nginx的匹配优先级遵循一个大原则和两个小细节。<br>大原则是关于匹配模式的优先级：</p><pre><code>精确匹配  &gt;  前缀匹配  &gt;  正则匹配  &gt; 正常匹配  &gt; 全匹配</code></pre><p>小细节则是同一优先级中：</p><ul><li>细节一：正则匹配成功之后停止匹配，非正则匹配成功还会接着匹配。</li><li>细节二：在所有匹配成功的url中，选取匹配度最大的url字符地址。</li></ul><h4 id="反向代理配置"><a href="#反向代理配置" class="headerlink" title="反向代理配置"></a>反向代理配置<a id="sec-4-1-5-3" name="sec-4-1-5-3"></a></h4><pre><code>location / {        #设置主机头和客户端真实地址，以便服务器获取客户端真实IP             proxy_set_header Host $host;             proxy_set_header X-Real-IP $remote_addr;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;              #禁用缓存             proxy_buffering off;             #设置反向代理的地址 可设为下面介绍的upstream模块             proxy_pass http://192.168.1.1;             }</code></pre><h4 id="proxy-redirect"><a href="#proxy-redirect" class="headerlink" title="proxy_redirect"></a>proxy_redirect<a id="sec-4-1-5-4" name="sec-4-1-5-4"></a></h4><p>proxy_redirect指定修改被代理服务器返回的响应头中的location头域</p><pre><code>location / {            proxy_pass http://192.168.1.207:6001;            proxy_redirect http://192.168.1.207:([1-9][0-9]*)/   http://lwy.cn:5000/lwy$1/;                                                                       }</code></pre><p>上面proxy_redirect配置将被代理服务器的重定向端口全部映射为/lwy$1/路由上了。然后,可以根据浏览器重定向进来的特殊路由url进行相应的配置,即配置多个location</p><pre><code>location ~ /conwin([1-9][0-9]*)/ {                 remainder omitted...        }</code></pre><p>在指令中可以使用一些变量：</p><p>proxy_redirect   <a href="http://localhost:8000/" target="_blank" rel="noopener">http://localhost:8000/</a>    <a href="http://$host:$server_port/" target="_blank" rel="noopener">http://$host:$server_port/</a>;</p><h4 id="proxy-pass"><a href="#proxy-pass" class="headerlink" title="proxy_pass"></a>proxy_pass<a id="sec-4-1-5-5" name="sec-4-1-5-5"></a></h4><p>在nginx中配置proxy_pass代理转发时，如果在proxy_pass后面的url加/，表示绝对根路径；如果没有/，表示相对路径，把匹配的路径部分也给代理走。</p><p>假设下面四种情况分别用 <a href="http://192.168.1.1/proxy/test.html" target="_blank" rel="noopener">http://192.168.1.1/proxy/test.html</a> 进行访问。</p><p>第一种：<br>location <em>proxy</em> {<br>    proxy_pass <a href="http://127.0.0.1/" target="_blank" rel="noopener">http://127.0.0.1/</a>;<br>}<br>代理到URL：<a href="http://127.0.0.1/test.html" target="_blank" rel="noopener">http://127.0.0.1/test.html</a></p><p>第二种（相对于第一种，最后少一个 / ）<br>location <em>proxy</em> {<br>    proxy_pass <a href="http://127.0.0.1" target="_blank" rel="noopener">http://127.0.0.1</a>;<br>}<br>代理到URL：<a href="http://127.0.0.1/proxy/test.html" target="_blank" rel="noopener">http://127.0.0.1/proxy/test.html</a></p><p>第三种：<br>location <em>proxy</em> {<br>    proxy_pass <a href="http://127.0.0.1/aaa/" target="_blank" rel="noopener">http://127.0.0.1/aaa/</a>;<br>}<br>代理到URL：<a href="http://127.0.0.1/aaa/test.html" target="_blank" rel="noopener">http://127.0.0.1/aaa/test.html</a></p><p>第四种（相对于第三种，最后少一个 / ）<br>location <em>proxy</em> {<br>    proxy_pass <a href="http://127.0.0.1/aaa" target="_blank" rel="noopener">http://127.0.0.1/aaa</a>;<br>}<br>代理到URL：<a href="http://127.0.0.1/aaatest.html" target="_blank" rel="noopener">http://127.0.0.1/aaatest.html</a></p><h3 id="upstream模块"><a href="#upstream模块" class="headerlink" title="upstream模块"></a>upstream模块<a id="sec-4-1-6" name="sec-4-1-6"></a></h3><p>upstream模块主要负责负载均衡的配置，通过默认的轮询调度方式来分发请求到后端服务器</p><pre><code>upstream backend {    ip_hash;    server 192.168.1.100:8000;    server 192.168.1.100:8001 down;    server 192.168.1.100:8002 max_fails=3;    server 192.168.1.100:8003 fail_timeout=20s;    server 192.168.1.100:8004 max_fails=3 fail_timeout=20s;} location / {             #反向代理的地址             proxy_pass http://backend;             }</code></pre><p>核心配置信息如下</p><ul><li>ip_hash：指定请求调度算法，默认是weight权重轮询调度，可以指定</li><li>server host:port：分发服务器的列表配置</li><li>&#x2013; down：表示该主机暂停服务</li><li>&#x2013; max_fails：表示失败最大次数，超过失败最大次数暂停服务</li><li>&#x2013; fail_timeout：表示如果请求受理失败，暂停指定的时间之后重新发起请求</li></ul><p>其中,nginx支持的负载均衡调度算法方式如下:</p><ul><li>weight轮询（默认）：接收到的请求按照顺序逐一分配到不同的后端服务器，即使在使用过程中，某一台后端服务器宕机，nginx会自动将该服务器剔除出队列，请求受理情况不会受到任何影响。 这种方式下，可以给不同的后端服务器设置一个权重值（weight），用于调整不同的服务器上请求的分配率；权重数据越大，被分配到请求的几率越大；该权重值，主要是针对实际工作环境中不同的后端服务器硬件配置进行调整的。</li><li>ip_hash：每个请求按照发起客户端的ip的hash结果进行匹配，这样的算法下一个固定ip地址的客户端总会访问到同一个后端服务器，这也在一定程度上解决了集群部署环境下session共享的问题。</li><li>fair：智能调整调度算法，动态的根据后端服务器的请求处理到响应的时间进行均衡分配，响应时间短处理效率高的服务器分配到请求的概率高，响应时间长处理效率低的服务器分配到的请求少；结合了前两者的优点的一种调度算法。但是需要注意的是nginx默认不支持fair算法，如果要使用这种调度算法，请安装upstream_fair模块</li><li>url_hash：按照访问的url的hash结果分配请求，每个请求的url会指向后端固定的某个服务器，可以在nginx作为静态服务器的情况下提高缓存效率。同样要注意nginx默认不支持这种调度算法，要使用的话需要安装nginx的hash软件包</li></ul><h1 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题<a id="sec-5" name="sec-5"></a></h1><h2 id="遇到301、302这种重定向的http状态码-浏览器被重定向到其他服务器地址或端口-如内网-。"><a href="#遇到301、302这种重定向的http状态码-浏览器被重定向到其他服务器地址或端口-如内网-。" class="headerlink" title="遇到301、302这种重定向的http状态码,浏览器被重定向到其他服务器地址或端口(如内网)。"></a>遇到301、302这种重定向的http状态码,浏览器被重定向到其他服务器地址或端口(如内网)。<a id="sec-5-1" name="sec-5-1"></a></h2><p>　　这种问题通常出现在被代理的服务端不知道代理服务器的存在，直接返回302重定向到另外一个服务器或端口(server2),nginx如果不做处理则浏览器会直接取访问server2，这样便绕过了nginx,如果是内网地址,呵呵,就访问失败了.这时候可以使用location匹配下的proxy_redirect来拦截节点被代理服务器的重定向信息</p><h2 id><a href="#" class="headerlink" title="　　"></a>　　</h2><p>本文由Weiye Lee原创，转载请注明来源,谢谢:<br><a href="http://blog.csdn.net/Weiye__Lee/article/details/79472815" target="_blank" rel="noopener">http://blog.csdn.net/Weiye__Lee/article/details/79472815</a></p><hr><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考<a id="sec-6" name="sec-6"></a></h1><p>Nginx安装及配置详解:<a href="https://www.cnblogs.com/zhouxinfei/p/7862285.html" target="_blank" rel="noopener">https://www.cnblogs.com/zhouxinfei/p/7862285.html</a></p><p>Nginx高级应用之Location Url 配置:<a href="https://www.linuxidc.com/Linux/2017-03/141910.htm" target="_blank" rel="noopener">https://www.linuxidc.com/Linux/2017-03/141910.htm</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;博客地址：&lt;a href=&quot;https://blog.csdn.net/Weiye__Lee/article/details/79700730&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://blog.csdn.net/Weiye__Lee/
      
    
    </summary>
    
      <category term="Linux" scheme="http://yoursite.com/categories/Linux/"/>
    
    
      <category term="ubantu" scheme="http://yoursite.com/tags/ubantu/"/>
    
  </entry>
  
  <entry>
    <title>Android离线身份证等图片文字识别</title>
    <link href="http://yoursite.com/2018/07/10/Android%E5%9B%BE%E5%83%8F%E6%89%AB%E6%8F%8F%E6%96%87%E5%AD%97%E8%AF%86%E5%88%AB/"/>
    <id>http://yoursite.com/2018/07/10/Android图像扫描文字识别/</id>
    <published>2018-07-10T02:13:30.000Z</published>
    <updated>2020-09-30T13:33:34.000Z</updated>
    
    <content type="html"><![CDATA[<p>博客：<a href="https://blog.csdn.net/Weiye__Lee/article/details/80952724" target="_blank" rel="noopener">https://blog.csdn.net/Weiye__Lee/article/details/80952724</a></p><p>个人主页：<a href="http://liweiye.top" target="_blank" rel="noopener">http://liweiye.top</a></p><h1 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h1><p>　　最近因为业务需要，要在Android端实现个扫描身份证识别其中文字的功能，网上溜达了一圈。<br><br>　　Android比较推荐的是: <br><br>　　GitHub：<a href="https://github.com/rmtheis/tess-two" target="_blank" rel="noopener">https://github.com/rmtheis/tess-two</a><br><br>　  当然也有第三方提供的解决方案,比如百度提供的文字识别：<a href="http://ai.baidu.com/tech/ocr。" target="_blank" rel="noopener">http://ai.baidu.com/tech/ocr。</a><br>　　咱做技术的还是先折腾折腾，特此记录下过程，也希望能帮助到同样折腾的人</p><h1 id="效果图-名字打了马赛克"><a href="#效果图-名字打了马赛克" class="headerlink" title="效果图(名字打了马赛克)"></a>效果图(名字打了马赛克)</h1><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://github.com/itlwy/TextOcrExample/blob/master/pic/recScan.png?raw=true" alt="拍照](https://github.com/itlwy/TextOcrExample/blob/master/pic/recResult.png?raw=true)  ![识别" title>                </div>                <div class="image-caption">拍照](https://github.com/itlwy/TextOcrExample/blob/master/pic/recResult.png?raw=true)  ![识别</div>            </figure><h1 id="过程"><a href="#过程" class="headerlink" title="过程"></a>过程</h1><p>先理下要实现这样的效果，我们需要做些什么</p><ol><li>在Android端需要自定义个含取景框的自定义相机</li><li>对拍摄的图片进行灰度化、二值化处理</li><li>引入tess-two 库,调用相应api将步骤2处理后的图片传入处理</li></ol><h3 id="1、自定义相机"><a href="#1、自定义相机" class="headerlink" title="1、自定义相机"></a>1、自定义相机</h3><p>　　这一步主要是写个布局，使用Camera和SurfaceView做个自定义相机，具体实现可看demo代码,里面有个取景框是专门用来截取出我们关心的区域的</p><h3 id="2、图片处理"><a href="#2、图片处理" class="headerlink" title="2、图片处理"></a>2、图片处理</h3><p>　　机器视觉分为三个阶段 : 图像转化、图像分析、图像理解。若要将一幅图像转化为方便分析理解的格式，有一个很关键的过程就是“图像二值化”。一幅图像能否分析理解的准确很大程度上来说取决于二值化(非黑即白)效果的好坏。而在二值化之前，有一个重要步骤，那便是“图像灰度化”</p><p>　　所以，先将图片灰度化，这里有个公式：f(x) = R<em>0.3+G</em>0.51+B*0.11，实际需要做的就是将图片的每个像素(这里假定android里用ARGB表示一个像素点)的red、green、blue取出并代入此公式算出每个点的灰度值，这样便实现了灰度化<br><br>　　<img src="https://github.com/itlwy/TextOcrExample/blob/master/pic/girl_normal.jpeg?raw=true" alt="灰度化之前"> <img src="https://github.com/itlwy/TextOcrExample/blob/master/pic/girl_gray.jpeg?raw=true" alt="这里写图片描述"><br><br>　　再来看看二值化，二值化的原理就是取一个阈值，然后将每个像素点的灰度值和这个阈值进行比较，如果大于阈值则定为白色，反之为黑色(这里假定要识别的图像是白底黑字)，如此一来便实现了二值化。可以看到，最重要的是这个阈值，该怎么取值才合理，最简单的阈值取定便是取整幅图画的均值了：<br><br>　　<br>　　<img src="https://github.com/itlwy/TextOcrExample/blob/master/pic/girl_gray.jpeg?raw=true" alt="这里写图片描述"> <img src="https://github.com/itlwy/TextOcrExample/blob/master/pic/girl_binary.jpeg?raw=true" alt="这里写图片描述"><br><br>　　效果看上去还不错，实际上用到身份证识别或文字识别上，受阴影等因素的影响，效果就差很多了，因此，优化算法还是很有必要的，网上流传着多种二值化算阈值的算法，这里目前尝试了以下几种：　　　</p><ol><li>阈值迭代算法(效果不理想，字体的笔画容易站在一起，阴影影响大)<br>　　<a href="https://www.cnblogs.com/gxclmx/p/6916515.html" target="_blank" rel="noopener">https://www.cnblogs.com/gxclmx/p/6916515.html</a>　　</li><li>基于Otsu阈值二值化(跟上面的迭代算法效果差不多，阴影影响大)<br>　　<a href="https://blog.csdn.net/mao0514/article/details/47041597" target="_blank" rel="noopener">https://blog.csdn.net/mao0514/article/details/47041597</a>　　</li><li>矩阵二值化算法<br>　　<a href="https://blog.csdn.net/lj501886285/article/details/52425157" target="_blank" rel="noopener">https://blog.csdn.net/lj501886285/article/details/52425157</a><br>　　这个算法的阈值是自适应的，对于每个像素点都构造了一个小矩阵来衡量阈值的取值，也就是说每个点都跟它周围的细节相关，这样比起用单一的整体阈值去衡量每个点，显然更具说服力。而事实证明，这个算法应用后的二值化测试效果确实杠杠的　　<h3 id="3、tess-two-api识别"><a href="#3、tess-two-api识别" class="headerlink" title="3、tess-two api识别"></a>3、tess-two api识别</h3>　　这一步就比较简单了，直接上步骤：</li><li><p>引入依赖</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">dependencies &#123;</span><br><span class="line">    implementation &apos;com.rmtheis:tess-two:9.0.0&apos;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>调用api识别</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">TessBaseAPI tessBaseAPI = new TessBaseAPI();</span><br><span class="line">tessBaseAPI.init(DATAPATH, &quot;chi_sim&quot;);//传入训练文件目录和训练文件</span><br><span class="line">tessBaseAPI.setImage(bitmap);</span><br><span class="line">String text = tessBaseAPI.getUTF8Text();</span><br></pre></td></tr></table></figure></li></ol><p>　　就这样短短4行代码便可识别出文字了，这里有个坑要注意，看下tessBaseAPI.init这个方法源码:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">public boolean init(String datapath, String language) &#123;</span><br><span class="line">        return init(datapath, language, OEM_DEFAULT);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure></p><p>可以看到又调用了另一个init方法<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">public boolean init(String datapath, String language, @OcrEngineMode int ocrEngineMode) &#123;</span><br><span class="line">       if (datapath == null)</span><br><span class="line">           throw new IllegalArgumentException(&quot;Data path must not be null!&quot;);</span><br><span class="line">       if (!datapath.endsWith(File.separator))</span><br><span class="line">           datapath += File.separator;</span><br><span class="line"></span><br><span class="line">       File datapathFile = new File(datapath);</span><br><span class="line">       if (!datapathFile.exists())</span><br><span class="line">           throw new IllegalArgumentException(&quot;Data path does not exist!&quot;);</span><br><span class="line"></span><br><span class="line">       File tessdata = new File(datapath + &quot;tessdata&quot;);</span><br><span class="line">       if (!tessdata.exists() || !tessdata.isDirectory())</span><br><span class="line">           throw new IllegalArgumentException(&quot;Data path must contain subfolder tessdata!&quot;);</span><br><span class="line"></span><br><span class="line">       //noinspection deprecation</span><br><span class="line">       if (ocrEngineMode != OEM_CUBE_ONLY) &#123;</span><br><span class="line">           for (String languageCode : language.split(&quot;\\+&quot;)) &#123;</span><br><span class="line">               if (!languageCode.startsWith(&quot;~&quot;)) &#123;</span><br><span class="line">                   File datafile = new File(tessdata + File.separator + </span><br><span class="line">                           languageCode + &quot;.traineddata&quot;);</span><br><span class="line">                   if (!datafile.exists())</span><br><span class="line">                       throw new IllegalArgumentException(&quot;Data file not found at &quot; + datafile);</span><br><span class="line"></span><br><span class="line">                   // Catch some common problematic initialization cases.</span><br><span class="line">                   if (languageCode.equals(&quot;ara&quot;) || (languageCode.equals(&quot;hin&quot;) &amp;&amp;</span><br><span class="line">                           ocrEngineMode == OEM_DEFAULT)) &#123;</span><br><span class="line">                       boolean sampleCubeFileExists = new File(tessdata +</span><br><span class="line">                               File.separator + languageCode + &quot;.cube.params&quot;).exists();</span><br><span class="line">                       if (!sampleCubeFileExists) &#123;</span><br><span class="line">                           throw new IllegalArgumentException(&quot;Cube data files not found.&quot; +</span><br><span class="line">                                   &quot; See https://github.com/rmtheis/tess-two/issues/239&quot;);</span><br><span class="line">                       &#125;</span><br><span class="line">                   &#125;</span><br><span class="line">               &#125;</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;</span><br><span class="line"></span><br><span class="line">       boolean success = nativeInitOem(mNativeData, datapath, language, ocrEngineMode);</span><br><span class="line"></span><br><span class="line">       if (success) &#123;</span><br><span class="line">           mRecycled = false;</span><br><span class="line">       &#125;</span><br><span class="line"></span><br><span class="line">       return success;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure></p><p>这里注意下面这一段代码，api要求DATAPATH目录下，必须有tessdata这样一个子目录，也就是说，训练文件必须放在这个子目录下<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">File tessdata = new File(datapath + &quot;tessdata&quot;);</span><br><span class="line">        if (!tessdata.exists() || !tessdata.isDirectory())</span><br><span class="line">            throw new IllegalArgumentException(&quot;Data path must contain subfolder tessdata!&quot;);</span><br></pre></td></tr></table></figure></p><p>　　一个文字识别的demo就此完成了，稍后会传上demo到github，demo做的是身份证，所以对图片的截取处理是针对身份证的，当然也可应用到其他营业执照啥的了。<br>　　最后，在查资料的时候发现一个身份证识别demo：<a href="https://github.com/dreamkid/IdCardReconition" target="_blank" rel="noopener">IdCardReconition</a>，处理效果挺快，效果也蛮不错的，但是查看代码后发现处理都是在so文件里并且处理貌似只针对身份证，也不清楚是怎么做的，有了解的望告知</p><p>附上github：<a href="https://github.com/itlwy/TextOcrExample" target="_blank" rel="noopener">TextOcrExample</a></p><h1 id="待完善"><a href="#待完善" class="headerlink" title="待完善"></a>待完善</h1><ol><li>图像处理的算法是有待完善的，特别是速度上</li><li>tess-two api识别的速度上，也可考虑针对特定场景定制训练文件，这样速度上也会有所提升</li></ol><p>PS：对于速度上的要求，个人觉得可以在网络畅通的情况下，采用上传图片到服务器去处理，然后再反馈回给客户端。在服务端可做的事就多了，单台服务器计算资源就好过手机太多，况且我们还可以做分布式并发处理呢？速度相信是杠杠的，像百度这些第三方一般也是提供服务上传图片来识别的，速度那是相当快。最后感谢阅读，如果有什么不对的望大神指正，喜欢就star一下呗，谢谢！</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ol><li><a href="https://blog.csdn.net/qq_25806863/article/details/67637567" target="_blank" rel="noopener">tesseract ocr训练样本库 识别字库</a></li><li><a href="https://github.com/rmtheis/tess-two" target="_blank" rel="noopener">tess-two</a></li></ol><h1 id="License"><a href="#License" class="headerlink" title="License"></a>License<a id="sec-6" name="sec-6"></a></h1><pre><code>Copyright 2018 lwyLicensed under the Apache License, Version 2.0 (the &quot;License&quot;);you may not use this file except in compliance with the License.You may obtain a copy of the License at   http://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an &quot;AS IS&quot; BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions andlimitations under the License.</code></pre>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;博客：&lt;a href=&quot;https://blog.csdn.net/Weiye__Lee/article/details/80952724&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://blog.csdn.net/Weiye__Lee/ar
      
    
    </summary>
    
      <category term="Android" scheme="http://yoursite.com/categories/Android/"/>
    
    
      <category term="Android" scheme="http://yoursite.com/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>仿QQ右上角的弹出菜单框</title>
    <link href="http://yoursite.com/2018/03/07/%E4%BB%BFQQ%E5%8F%B3%E4%B8%8A%E8%A7%92%E7%9A%84%E5%BC%B9%E5%87%BA%E8%8F%9C%E5%8D%95%E6%A1%86/"/>
    <id>http://yoursite.com/2018/03/07/仿QQ右上角的弹出菜单框/</id>
    <published>2018-03-07T08:30:30.000Z</published>
    <updated>2020-09-30T13:33:34.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Screenshots"><a href="#Screenshots" class="headerlink" title="Screenshots"></a>Screenshots</h1><p>#<figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://img.blog.csdn.net/20180307160817971?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvV2VpeWVfX0xlZQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="Alt text" title>                </div>                <div class="image-caption">Alt text</div>            </figure></p><h1 id="How-to-Use"><a href="#How-to-Use" class="headerlink" title="How to Use"></a>How to Use</h1><h2 id="step-1"><a href="#step-1" class="headerlink" title="step 1"></a>step 1</h2><p>Add the JitPack repository to your build file</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">allprojects &#123;</span><br><span class="line">repositories &#123;</span><br><span class="line">...</span><br><span class="line">maven &#123; url &apos;https://jitpack.io&apos; &#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Step-2"><a href="#Step-2" class="headerlink" title="Step 2"></a>Step 2</h2><p>Add the dependency</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">dependencies &#123;</span><br><span class="line">        compile &apos;com.github.itlwy:LPopupMenu:v1.1&apos;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="step-3"><a href="#step-3" class="headerlink" title="step 3"></a>step 3</h2><p>　　使用方法见如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">  List&lt;MenuItem&gt; menuItems = new ArrayList&lt;&gt;();</span><br><span class="line">                menuItems.add(new MenuItem(R.mipmap.multichat, &quot;发起多人聊天&quot;, 100));</span><br><span class="line">                menuItems.add(new MenuItem(R.mipmap.addmember, &quot;加好友&quot;, 3));</span><br><span class="line">                menuItems.add(new MenuItem(R.mipmap.qr_scan, &quot;扫一扫&quot;));</span><br><span class="line">                menuItems.add(new MenuItem(R.mipmap.facetoface, &quot;面对面快传&quot;));</span><br><span class="line">                menuItems.add(new MenuItem(R.mipmap.pay, &quot;付款&quot;));</span><br><span class="line">                if (mRightTopMenu == null) &#123;</span><br><span class="line">                    mRightTopMenu = new RightTopMenu.Builder(MainActivity.this)</span><br><span class="line">//                            .windowHeight(480)     //当菜单数量大于3个时，为wrap_content,反之取默认高度320</span><br><span class="line">//                        .windowWidth(320)      //默认宽度wrap_content</span><br><span class="line">                            .dimBackground(true)           //背景变暗，默认为true</span><br><span class="line">                            .needAnimationStyle(true)   //显示动画，默认为true</span><br><span class="line">                            .animationStyle(R.style.RTM_ANIM_STYLE)  //默认为R.style.RTM_ANIM_STYLE</span><br><span class="line">                            .menuItems(menuItems)</span><br><span class="line">                            .onMenuItemClickListener(new RightTopMenu.OnMenuItemClickListener() &#123;</span><br><span class="line">                                @Override</span><br><span class="line">                                public void onMenuItemClick(int position) &#123;</span><br><span class="line">                                    Toast.makeText(MainActivity.this, &quot;点击菜单:&quot; + position, Toast.LENGTH_SHORT).show();</span><br><span class="line">                                &#125;</span><br><span class="line">                            &#125;).build();</span><br><span class="line">                &#125;</span><br><span class="line">                mRightTopMenu.showAsDropDown(mMenuIV, 0, 0);</span><br></pre></td></tr></table></figure><p>　　觉得合适就拿去用吧~</p><h1 id="Otherthing"><a href="#Otherthing" class="headerlink" title="Otherthing"></a>Otherthing</h1><p>　　项目依赖了</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">com.android.support:recyclerview-v7:26.1.0</span><br><span class="line">com.android.support:appcompat-v7:26.1.0</span><br></pre></td></tr></table></figure><p>  当你的项目里已有这些依赖时，按照gradle的编译特性，会取最新版本的。所以，可能会报错咯，解决办法可参考<a href="http://blog.csdn.net/Weiye__Lee/article/details/79472501" target="_blank" rel="noopener">gradle提供的依赖冲突解决方案</a>.下面是给出示例解决方案(build.gradle文件)：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">apply plugin: &apos;com.android.application&apos;</span><br><span class="line"></span><br><span class="line">android &#123;</span><br><span class="line">    compileSdkVersion 23</span><br><span class="line">    buildToolsVersion &quot;26.0.2&quot;</span><br><span class="line"></span><br><span class="line">  ......</span><br><span class="line">  </span><br><span class="line">//configurations.all &#123;</span><br><span class="line">//    resolutionStrategy &#123;</span><br><span class="line">//        force &apos;com.android.support:appcompat-v7:23.2.1&apos;  // 这里可以采用设置强制版本</span><br><span class="line">//    &#125;</span><br><span class="line">//&#125;</span><br><span class="line">dependencies &#123;</span><br><span class="line">    compile fileTree(include: [&apos;*.jar&apos;], dir: &apos;libs&apos;)</span><br><span class="line">    androidTestCompile(&apos;com.android.support.test.espresso:espresso-core:2.2.2&apos;, &#123;</span><br><span class="line">        exclude group: &apos;com.android.support&apos;, module: &apos;support-annotations&apos;</span><br><span class="line">    &#125;)</span><br><span class="line">    compile &apos;com.android.support:appcompat-v7:23.2.1&apos;</span><br><span class="line">    compile &quot;com.android.support:recyclerview-v7:23.4.0&quot;</span><br><span class="line">    compile &apos;com.android.support:design:23.2.1&apos;</span><br><span class="line">    compile (&quot;com.github.itlwy:LPopupMenu:v1.0&quot;)&#123;</span><br><span class="line">        exclude group: &apos;com.android.support&apos;,module: &apos;appcompat-v7&apos;</span><br><span class="line">        exclude group: &apos;com.android.support&apos;,module: &apos;recyclerview-v7&apos;</span><br><span class="line">    &#125;</span><br><span class="line">    testCompile &apos;junit:junit:4.12&apos;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="By-the-way"><a href="#By-the-way" class="headerlink" title="By the way"></a>By the way</h1><p>　　关于数量的提示角标，是用shape画的圆(要改颜色的话方便~)。但是当大于99时，直接撑大显示不好看，改成和qq那边的效果类似（两边半圆，中间矩形）；由于水平有限，暂时没找到代码的简单实现，直接用图片代替了，这方面有待改进（查了下网上的实现，大部分是用.9图代替的，但是要动态改变圈的颜色效果什么的就不好弄了）</p><p>本文由Weiye Lee原创，转载请注明来源：<a href="http://blog.csdn.net/Weiye__Lee/article/details/79472815" target="_blank" rel="noopener">http://blog.csdn.net/Weiye__Lee/article/details/79472815</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;Screenshots&quot;&gt;&lt;a href=&quot;#Screenshots&quot; class=&quot;headerlink&quot; title=&quot;Screenshots&quot;&gt;&lt;/a&gt;Screenshots&lt;/h1&gt;&lt;p&gt;#&lt;figure class=&quot;image-bubble&quot;&gt;
   
      
    
    </summary>
    
      <category term="Android" scheme="http://yoursite.com/categories/Android/"/>
    
    
      <category term="Android" scheme="http://yoursite.com/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>linux用ssh做反向代理</title>
    <link href="http://yoursite.com/2018/02/20/linux%E7%94%A8ssh%E5%81%9A%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86/"/>
    <id>http://yoursite.com/2018/02/20/linux用ssh做反向代理/</id>
    <published>2018-02-20T05:31:57.000Z</published>
    <updated>2020-09-30T13:33:34.000Z</updated>
    
    <content type="html"><![CDATA[<p>　　在部署服务器的时候，经常会是这样一种情况:生产的服务器部署在一个安全的局域网环境中，不对外暴露端口的话，无法从外网访问到它。这时候，我们可以通过一台外网可访问的服务器做跳转，间接访问到服务器A。<br><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="http://img.blog.csdn.net/20180219195159811?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvV2VpeWVfX0xlZQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="ssh反向代理示意图" title>                </div>                <div class="image-caption">ssh反向代理示意图</div>            </figure><br>　　如上图，客户端C无法直接访问到服务器A，那么我们先在局域网内访问服务器A,在服务器A上建立与服务器B的反向代理通道。此时，客户端C可通过访问服务器B，再经由服务器B这个代理，访问到服务器</p><h2 id="step1-设置免密码登录"><a href="#step1-设置免密码登录" class="headerlink" title="step1.设置免密码登录"></a>step1.设置免密码登录</h2><ul><li>在内网服务器A上生产公钥和私钥，并把公钥内容拷到服务器B的~/.ssh/authorized_keys里<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ ssh-keygen -t rsa</span><br><span class="line">...(一直按Enter，最后在~/.ssh/下生成密钥)</span><br><span class="line">$ ssh-copy-id -i ~/.ssh/id_rsa.pub user@hostB</span><br><span class="line">把本机的公钥追到hostB的 ~/.ssh/authorized_keys 里</span><br></pre></td></tr></table></figure></li></ul><p>于是服务器 A上的用户就可以用ssh以用户B的身份无需密码登陆到服务器 B上了。如:<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -l userB serverB。</span><br></pre></td></tr></table></figure></p><h2 id="step2-用ssh建立反向链接"><a href="#step2-用ssh建立反向链接" class="headerlink" title="step2.用ssh建立反向链接"></a>step2.用ssh建立反向链接</h2><p>在内网服务器A上执行<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh -NfR 1234:localhost:22 userB@123.123.123.123 -p 22</span><br></pre></td></tr></table></figure></p><p>　　上面命令中,-N表示不执行远程命令,-f表示后台运行,-R则针对后面的绑定参数进行端口映射。整体意思是：将本机（服务器A）的22与远程服务器的1234端口进行绑定，相当于远程端口映射(Remote Port Forwarding)。<br>　　这时在服务器B上sshd会listen本地1234端口<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ ss -ant</span><br><span class="line"></span><br><span class="line">State      Recv-Q Send-Q        Local Address:Port          Peer Address:Port</span><br><span class="line">LISTEN     0      128               127.0.0.1:1234                     *:*</span><br></pre></td></tr></table></figure></p><pre><code>客户端C通过如下步骤可访问到服务器A:</code></pre><p>1.通过ssh连接到服务器B<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh userB@服务器B地址</span><br></pre></td></tr></table></figure></p><p>2.通过刚刚建立的反向通道，连接到服务器A<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh localhost -p 1234</span><br></pre></td></tr></table></figure></p><p>　　搞定完上面的步骤，已经可以从外网连接到服务器A了，但是这种方式还存在问题，服务器A-&gt;服务器B的链路是不稳定的，随时可能会断开，这时候可以用autossh这个命令，它可以帮助监听链路的连接情况，断开时可帮我们自动重连。<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">autossh -M 5678 -NR 1234:localhost:22 userB@123.123.123.123 -p 22</span><br></pre></td></tr></table></figure></p><p>　　比之前的命令添加的一个-M 5678参数，负责通过5678端口监视连接状态，连接有问题时就会自动重连，去掉了一个-f参数，因为autossh本身就会在background运行<br>　　最后，还有一种情况就是机器重启了，这时候需要将该命令加入到自启动命令列表里。有多种方式，这里采用在/etc/rc.local文件里追加内容，将以下内容加入到文件里即可<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/usr/bin/sudo -u conwin /usr/bin/autossh -M 5678 -NR 1234:localhost:22 userB@123.123.123.123 -p 22</span><br></pre></td></tr></table></figure></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;　　在部署服务器的时候，经常会是这样一种情况:生产的服务器部署在一个安全的局域网环境中，不对外暴露端口的话，无法从外网访问到它。这时候，我们可以通过一台外网可访问的服务器做跳转，间接访问到服务器A。&lt;br&gt;&lt;figure class=&quot;image-bubble&quot;&gt;
    
      
    
    </summary>
    
      <category term="Linux" scheme="http://yoursite.com/categories/Linux/"/>
    
    
      <category term="ubantu" scheme="http://yoursite.com/tags/ubantu/"/>
    
  </entry>
  
  <entry>
    <title>ubantu源码安装postgresql</title>
    <link href="http://yoursite.com/2018/02/18/ubantu%E6%BA%90%E7%A0%81%E5%AE%89%E8%A3%85postgresql/"/>
    <id>http://yoursite.com/2018/02/18/ubantu源码安装postgresql/</id>
    <published>2018-02-18T07:30:30.000Z</published>
    <updated>2020-09-30T13:33:34.000Z</updated>
    
    <content type="html"><![CDATA[<p>&#160; &#160; &#160; &#160;最近学习postgresql数据库,直接用服务器的包管理器，版本选择处理起来很麻烦，所以查了下资料，选择用源码安装，这里做个笔记记录下(本文基于Ubantu 14.04.5)，方便自己和他人，由于本人很菜，有不对的地方还往大神批评指正，谢谢!<br>        ps：由于刚开始写博客,目前是对以前学习的只是进行一个总结性的记录，可能会涉及到一些和大神作品雷同的内容,如有,请告诉我，谢谢！</p><h2 id="step1-安装前准备"><a href="#step1-安装前准备" class="headerlink" title="step1-安装前准备"></a>step1-安装前准备</h2><h3 id="安装依赖包"><a href="#安装依赖包" class="headerlink" title="安装依赖包"></a>安装依赖包</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install libreadline6-dev</span><br><span class="line">sudo apt-get install zlib1g-dev</span><br><span class="line">sudo apt-get install libssl-dev</span><br></pre></td></tr></table></figure><h3 id="下载源码"><a href="#下载源码" class="headerlink" title="下载源码"></a>下载源码</h3><p>源码下载链接可以到官网上找就行了,<br>eg:<a href="postgresql下载链接">https://www.postgresql.org/ftp/source/</a><br>下载后解压，安装就行了，如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo tar xf postgresql-9.5.2.tar.gz</span><br><span class="line"><span class="built_in">cd</span> postgresql-9.5.2</span><br><span class="line">./configure --prefix=/usr/<span class="built_in">local</span>/pgsql --with-pgport=5432 --with-openssl</span><br></pre></td></tr></table></figure><h2 id="step2-开始安装和配置"><a href="#step2-开始安装和配置" class="headerlink" title="step2-开始安装和配置"></a>step2-开始安装和配置</h2><h3 id="编译安装"><a href="#编译安装" class="headerlink" title="编译安装"></a>编译安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">make &amp;&amp;  sudo make install</span><br></pre></td></tr></table></figure><h3 id="安装contrib"><a href="#安装contrib" class="headerlink" title="安装contrib"></a>安装contrib</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> contrib</span><br><span class="line">make</span><br><span class="line">sudo make install</span><br></pre></td></tr></table></figure><h3 id="添加用户和组"><a href="#添加用户和组" class="headerlink" title="添加用户和组"></a>添加用户和组</h3><p>建立一个超级用户用于管理数据库</p><p>1.添加用户</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo adduser postgres</span><br></pre></td></tr></table></figure><p>2.建立数据库数据目录并授权</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo mkdir /usr/<span class="built_in">local</span>/pgsql/data</span><br><span class="line">sudo chown -R postgres:postgres /usr/<span class="built_in">local</span>/pgsql/data</span><br><span class="line">su postgres</span><br></pre></td></tr></table></figure><p>3.初始化数据库并启动数据库</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">/usr/<span class="built_in">local</span>/pgsql/bin/initdb -D /usr/<span class="built_in">local</span>/pgsql/data/</span><br><span class="line">/usr/<span class="built_in">local</span>/pgsql/bin/postgres -D /usr/<span class="built_in">local</span>/pgsql/data &gt;logfile 2&gt;&amp;1 &amp;</span><br></pre></td></tr></table></figure><p>4.将启动脚本加入到系统服务</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo cp /usr/<span class="built_in">local</span>/src/postgresql-9.5.2/contrib/start-scripts/linux /etc/init.d/postgresql</span><br><span class="line">sudo chmod +x /etc/init.d/postgresql</span><br></pre></td></tr></table></figure><p>5.设置为开机启动<br>sudo update-rc.d postgresql defaults</p><p>经过以上步骤，数据库已经安装配置完成,可以通过命令查看其运行状态</p><h2 id="相关命令"><a href="#相关命令" class="headerlink" title="相关命令"></a>相关命令</h2><h3 id="查看postgresql服务"><a href="#查看postgresql服务" class="headerlink" title="查看postgresql服务"></a>查看postgresql服务</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo service postgresql status</span><br></pre></td></tr></table></figure><h3 id="查看数据库进程运行状态"><a href="#查看数据库进程运行状态" class="headerlink" title="查看数据库进程运行状态"></a>查看数据库进程运行状态</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ps aux | grep postgres</span><br></pre></td></tr></table></figure><h3 id="数据库启动停止等"><a href="#数据库启动停止等" class="headerlink" title="数据库启动停止等"></a>数据库启动停止等</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">启动postgresql数据库</span><br><span class="line">sudo /etc/init.d/postgresql start</span><br><span class="line">重启postgresql数据库</span><br><span class="line">sudo /etc/init.d/postgresql restart</span><br><span class="line">停止postgresql数据库</span><br><span class="line">sudo /etc/init.d/postgresql stop</span><br><span class="line">查看postgresql数据库状态</span><br><span class="line">sudo /etc/init.d/postgresql status</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&amp;#160; &amp;#160; &amp;#160; &amp;#160;最近学习postgresql数据库,直接用服务器的包管理器，版本选择处理起来很麻烦，所以查了下资料，选择用源码安装，这里做个笔记记录下(本文基于Ubantu 14.04.5)，方便自己和他人，由于本人很菜，有不对的地方还
      
    
    </summary>
    
      <category term="数据库" scheme="http://yoursite.com/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
      <category term="数据库" scheme="http://yoursite.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
      <category term="postgresql" scheme="http://yoursite.com/tags/postgresql/"/>
    
  </entry>
  
</feed>
