<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>萝卜平</title>
  
  <subtitle>没想好的一个副标题</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://wjploop.github.io/"/>
  <updated>2022-09-14T03:45:55.955Z</updated>
  <id>http://wjploop.github.io/</id>
  
  <author>
    <name>萝卜平</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>误删两行代码引发的测量View问题</title>
    <link href="http://wjploop.github.io/2022/06/15/%E8%AF%AF%E5%88%A0%E4%B8%A4%E8%A1%8C%E4%BB%A3%E7%A0%81%E5%BC%95%E5%8F%91%E7%9A%84%E6%B5%8B%E9%87%8FView%E9%97%AE%E9%A2%98/"/>
    <id>http://wjploop.github.io/2022/06/15/误删两行代码引发的测量View问题/</id>
    <published>2022-06-15T12:15:49.000Z</published>
    <updated>2022-09-14T03:45:55.955Z</updated>
    
    <content type="html"><![CDATA[<p>今天解决的一个bug很有意思，涉及View测量流程，故记之。</p><h4 id="bug描述："><a href="#bug描述：" class="headerlink" title="bug描述："></a>bug描述：</h4><p>Launcher中，当用户点击<strong>任一全屏App</strong>返回后，GridLayout 中的 ItemView 大小测量不正常了。如下图所示：</p><img src="/images/image-20220615204045941.png"><p>Launcher的布局结构是一个ViewPager，每一页是一个GridLayout，以下是对应的代码（也可以忽略不看）。</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></pre></td><td class="code"><pre><span class="line">public class AppPagerAdapter extends PagerAdapter &#123;</span><br><span class="line">  ...</span><br><span class="line"></span><br><span class="line">// 刷新每一个页的内容</span><br><span class="line">// 一个页面是简单的 FrameLayout &gt; GridLayout 结构</span><br><span class="line">    private void refreshItemLayout(int position, ViewGroup c_item_layout) &#123;</span><br><span class="line">        AppGridItemView c_app_view;</span><br><span class="line">        GridLayout c_grid_layout = (GridLayout) c_item_layout.getChildAt(0);</span><br><span class="line">        for (int c_page_index = 0; c_page_index &lt; m_page_count_per_page; c_page_index++) &#123;</span><br><span class="line">        // 根据在第几页、第几项确定对应的app</span><br><span class="line">            int c_app_index = (position * m_page_count_per_page) + c_page_index;</span><br><span class="line">            View view = c_grid_layout.getChildAt(c_page_index);</span><br><span class="line">            if (view == null || !(view instanceof AppGridItemView)) &#123;</span><br><span class="line">            // 第一个创建 GridLayout 中的 ItemView</span><br><span class="line">                c_app_view = (AppGridItemView) View.inflate(this.m_context, R.layout.app_item_layout, (ViewGroup) null);</span><br><span class="line">                c_app_view.setShortCutClickListener(this.m_listener);</span><br><span class="line">                GridLayout.LayoutParams c_para = new GridLayout.LayoutParams();</span><br><span class="line">                // 关键是这里，误删掉了这两行，导致出现了Bug</span><br><span class="line">//                c_para.width = 0;</span><br><span class="line">//                c_para.height = 0;</span><br><span class="line">                c_para.columnSpec = GridLayout.spec(c_page_index % m_page_colum_num, 1.0f);</span><br><span class="line">                c_para.rowSpec = GridLayout.spec(c_page_index / m_page_colum_num, 1.0f);</span><br><span class="line">                c_app_view.setLayoutParams(c_para);</span><br><span class="line">                c_grid_layout.addView(c_app_view);</span><br><span class="line">            &#125; else &#123;</span><br><span class="line">                c_app_view = (AppGridItemView) view;</span><br><span class="line">            &#125;</span><br><span class="line">            if (c_app_index &lt; this.m_app_list.size()) &#123;</span><br><span class="line">                c_app_view.setAppInfo(this.m_app_list.get(c_app_index));</span><br><span class="line">            &#125; else &#123;</span><br><span class="line">                c_app_view.setAppInfo((AppInfo) null);</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">// ViewPager中创建一个页面</span><br><span class="line">    public Object instantiateItem(@NonNull ViewGroup container, int position) &#123;</span><br><span class="line">        // 复用整个页面View，其结构（FrameLayout &gt; GridLayout）</span><br><span class="line">        FrameLayout c_item_layout = getFreeLayout();</span><br><span class="line">// 针对每一页创建内容</span><br><span class="line">        refreshItemLayout(position, c_item_layout);</span><br><span class="line">        </span><br><span class="line">        this.m_use_layout_list.add(c_item_layout);</span><br><span class="line">        container.addView(c_item_layout);</span><br><span class="line">        c_item_layout.setTag(Integer.valueOf(position));</span><br><span class="line">        return c_item_layout;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="bug原因"><a href="#bug原因" class="headerlink" title="bug原因"></a>bug原因</h4><p>先给出Bug原因：创建GridLayout子项的布局参数中，我把 宽高==0 改为了 宽高==wrap_content。</p><h4 id="追溯我写bug的起因"><a href="#追溯我写bug的起因" class="headerlink" title="追溯我写bug的起因"></a>追溯我写bug的起因</h4><p>创建的 GridLayout 中的 ItemView 时，我将 ” itemView 的 LayoutParam 宽高设为了0“ 的两行代码注释掉了，如下：</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">                GridLayout.LayoutParams c_para = new GridLayout.LayoutParams();</span><br><span class="line">                // 关键是这里，我误删掉了这两行，导致出现了Bug</span><br><span class="line">//                c_para.width = 0;</span><br><span class="line">//                c_para.height = 0;</span><br></pre></td></tr></table></figure><p>回想我怎么干出这样的”蠢事”？</p><p>应该是想到了，一个属性值默认为0，就不需要额外设置了呀。</p><p>一般情况下， <code>ViewGroup.LayoutParams</code>　无参构造方法中，默认的宽高的是0。</p><p>但是，<code>GridLayout.LayoutPrams</code> 的无参构造方法中，会将默认的宽高设为<code>wrap_content</code>。</p><p>这样，因为我的想当然，将布局参数由 <code>0</code>  改为了 <code>wrap_content</code>。</p><h4 id="bug-的分析-布局参数-0-和-wrap-content的不同"><a href="#bug-的分析-布局参数-0-和-wrap-content的不同" class="headerlink" title="bug 的分析　布局参数 0 和 wrap_content的不同"></a>bug 的分析　布局参数 0 和 wrap_content的不同</h4><p>直接给出结论：</p><p>0 时，对应的测量模式是exactly，导致父容器大小变化时，不会重新测量自己，而wrap_content会在每次父容器大小变化时测量自己。</p><p>具体针对本bug，则是在全屏应用切换时，导致 gridLayout的大小发生了变化，进而触发子项view重新测量自己。当从全屏应用返回后，触发了一次重新测量的流程，若是参数为0时，则itemView不会重新测量。</p><blockquote><p>其实深究这个问题，还是存在不清楚的地方：</p><p>为什么进入其他全屏应用时会触发重新测量，而返回Launcher时没有触发测量？</p><p>或是说，仅是从全屏应用返回后，Launcher触发了重新测量，但是测量时状态栏仍是处于隐藏的状态，故而导致此情况。</p></blockquote><h4 id="bug延伸，源码追踪-为什么０和-wrap-content不同"><a href="#bug延伸，源码追踪-为什么０和-wrap-content不同" class="headerlink" title="bug延伸，源码追踪,为什么０和 wrap_content不同"></a>bug延伸，源码追踪,为什么０和 wrap_content不同</h4><p>从GridLayout的 onMeasure() 开始追踪</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><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><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><span class="line">   protected void onMeasure(int widthSpec, int heightSpec) &#123;</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">// 第一次测量，（1）</span><br><span class="line">       measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, true);</span><br><span class="line">// 第二次测量</span><br><span class="line">measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false);</span><br><span class="line"></span><br><span class="line">// 确定当前 GridLayout 的大小</span><br><span class="line">       setMeasuredDimension(</span><br><span class="line">               resolveSizeAndState(measuredWidth,   widthSpec, 0),</span><br><span class="line">               resolveSizeAndState(measuredHeight, heightSpec, 0));</span><br><span class="line">   &#125;</span><br><span class="line">   </span><br><span class="line">   </span><br><span class="line">   private void measureChildrenWithMargins(int widthSpec, int heightSpec, boolean firstPass) &#123;</span><br><span class="line">       for (int i = 0, N = getChildCount(); i &lt; N; i++) &#123;</span><br><span class="line">           View c = getChildAt(i);</span><br><span class="line">           // 这里，就是使用布局参数的地方</span><br><span class="line">           LayoutParams lp = getLayoutParams(c);</span><br><span class="line">           if (firstPass) &#123;</span><br><span class="line">// 结合父级的spec和子View的想要的大小，来确定子View的大小 （2）</span><br><span class="line">               measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height);</span><br><span class="line">           &#125; else &#123;</span><br><span class="line">               boolean horizontal = (mOrientation == HORIZONTAL);</span><br><span class="line">               Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;</span><br><span class="line">               if (spec.getAbsoluteAlignment(horizontal) == FILL) &#123;</span><br><span class="line">                   Interval span = spec.span;</span><br><span class="line">                   Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;</span><br><span class="line">                   int[] locations = axis.getLocations();</span><br><span class="line">                   int cellSize = locations[span.max] - locations[span.min];</span><br><span class="line">                   int viewSize = cellSize - getTotalMargin(c, horizontal);</span><br><span class="line">                   if (horizontal) &#123;</span><br><span class="line">                   // 若是水平的，即GridLayout限制了水平方向有固定N个，将父级width平分N分后，做为itemView的宽</span><br><span class="line">                   // 而在垂直方向，仍尊重孩子自己申请的大小</span><br><span class="line">                       measureChildWithMargins2(c, widthSpec, heightSpec, viewSize, lp.height);</span><br><span class="line">                   &#125; else &#123;</span><br><span class="line">                       measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, viewSize);</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">   private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec,</span><br><span class="line">           int childWidth, int childHeight) &#123;</span><br><span class="line">       // itemView 测量自身使用 measureSpec 的确定过程，</span><br><span class="line">       int childWidthSpec = getChildMeasureSpec(parentWidthSpec,</span><br><span class="line">               getTotalMargin(child, true), childWidth);</span><br><span class="line">       int childHeightSpec = getChildMeasureSpec(parentHeightSpec,</span><br><span class="line">               getTotalMargin(child, false), childHeight);</span><br><span class="line">       // itemView 得到 spec 后来测量自身</span><br><span class="line">       child.measure(childWidthSpec, childHeightSpec);</span><br><span class="line">   &#125;</span><br><span class="line">   </span><br><span class="line">// ViewGroup.java</span><br><span class="line">   public static int getChildMeasureSpec(int spec, int padding, int childDimension) &#123;</span><br><span class="line">   // spec 的拆分，前2位来存储mode, 后30位存储期望的值</span><br><span class="line">       int specMode = MeasureSpec.getMode(spec);</span><br><span class="line">       int specSize = MeasureSpec.getSize(spec);</span><br><span class="line"></span><br><span class="line">       int resultSize = 0;</span><br><span class="line">       int resultMode = 0;</span><br><span class="line"></span><br><span class="line">       switch (specMode) &#123;</span><br><span class="line">       // Parent has imposed an exact size on us</span><br><span class="line">      // 正常情况，GridLayout 传下来的是 exactly, 即gridLayout本身确定了自己的大小，不期望收到孩子的影响</span><br><span class="line">       case MeasureSpec.EXACTLY:</span><br><span class="line">       // 这里，孩子的布局参数设置为0时，即导致孩子使用的spec为 (exactly + 0) 组成</span><br><span class="line">           if (childDimension &gt;= 0) &#123;</span><br><span class="line">               resultSize = childDimension;</span><br><span class="line">               resultMode = MeasureSpec.EXACTLY;</span><br><span class="line">           &#125; else if (childDimension == LayoutParams.MATCH_PARENT) &#123;</span><br><span class="line">               // Child wants to be our size. So be it.</span><br><span class="line">               resultSize = size;</span><br><span class="line">               resultMode = MeasureSpec.EXACTLY;</span><br><span class="line">           &#125; else if (childDimension == LayoutParams.WRAP_CONTENT) &#123;</span><br><span class="line">               // 当我们使用默认的 wrap_content时，孩子会使用的 spec 为 (at_most + size)</span><br><span class="line">               // 注意这个size为GridLayout已经确定好的size</span><br><span class="line">               resultSize = size;</span><br><span class="line">               resultMode = MeasureSpec.AT_MOST;</span><br><span class="line">           &#125;</span><br><span class="line">           break;</span><br><span class="line"></span><br><span class="line">       return MeasureSpec.makeMeasureSpec(resultSize, resultMode);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>到这里，我们完成了从 LayoutParam的到 spec 的转变过程。</p><p>为了描述本Bug，分为了两种情况，</p><ol><li>布局参数宽高设为  wrap_content， 即 GridLayout.LayoutParam 默认方法</li><li>布局参数宽高设为  0，即我们解决bug的情况</li></ol><p>第一种 spec 为 (at_most + size)，即孩子的大小未确定，只是限制了最大值为size，即父级大小减去一些padding.第二种 spec 为 (exatly + 0)，当孩子使用这个 spec时，会确定自己大小为 0 ? </p><p> 开始追到这里会有一些疑惑，当使用 spec 为 （exactly + 0 ）去调用 child.measure()时，那么ItemView得大小不是应该为 0吗？实际上，就是这样。但这只是初步的结果，回顾发现，会GridLayout区分了第一次测量和第二次测量，此时，第一次测量结果就是孩子得大小都为 0。</p><p>这里，我们可以做一个初步的总结，GridLayout测量孩子时，会分为两次测量，第一次会询问孩子想要的大小，</p><p>若是wrap_content，那么孩子会根据自身出发先报出自己想要的大小，作为暂定值。</p><p>若是 &gt;=0的值，那么也暂定大小为当前申请的值，比如我们报的值0.</p><p>当然还有一种额外的情况，就是 match_parent，暂定大小与父级容忍的最大值相同。</p><p>无论布局参数参数是哪种，都会先得到一个暂定值。</p><p>接下来，我们进入GridLayout 的第二次测量的过程。</p><p>回头看代码，firstPass == false的情况，</p><p>讨论GridLayout是水平的，即GridLayout限制了水平方向有固定N个itemView，将父级的宽度平分N分后，做为itemView的宽度，而在垂直方向，仍尊重孩子自己申请的大小。</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">measureChildWithMargins2(c, widthSpec, heightSpec, viewSize, lp.height);// viewSize即划分父级为N份的大小</span><br></pre></td></tr></table></figure><p>接下来再次得到一个 spec 后，进入 child.measure(childWidthSpec, childHeightSpec)，我们这里追踪进入</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></pre></td><td class="code"><pre><span class="line">  // View.java</span><br><span class="line">  </span><br><span class="line">  public final void measure(int widthMeasureSpec, int heightMeasureSpec) &#123;</span><br><span class="line">// 传统的cache流程，忽略</span><br><span class="line">      // Suppress sign extension for the low bytes</span><br><span class="line">      long key = (long) widthMeasureSpec &lt;&lt; 32 | (long) heightMeasureSpec &amp; 0xffffffffL;</span><br><span class="line">      if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);</span><br><span class="line"></span><br><span class="line">// 当前View是否申请有强制布局，即某个地方已经判断该View是脏的</span><br><span class="line">      final boolean forceLayout = (mPrivateFlags &amp; PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;</span><br><span class="line"></span><br><span class="line">// 翻译：当前子View已经测量出正确尺寸时，父级会传参exatcly，（避免无效测量），以达到优化布局目的</span><br><span class="line">      // Optimize layout by avoiding an extra EXACTLY pass when the view is</span><br><span class="line">      // already measured as the correct size. In API 23 and below, this</span><br><span class="line">      // extra pass is required to make LinearLayout re-distribute weight.</span><br><span class="line">      final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec</span><br><span class="line">              || heightMeasureSpec != mOldHeightMeasureSpec;</span><br><span class="line">      final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY</span><br><span class="line">              &amp;&amp; MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;</span><br><span class="line">      final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)</span><br><span class="line">              &amp;&amp; getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);</span><br><span class="line">              </span><br><span class="line">      // 是否需要重新布局（当然包括测量了）？</span><br><span class="line">// spec是否已经变化了？</span><br><span class="line">// 与第一次传进来的的spec相比，当然是变化了。</span><br><span class="line">// 即使变化了，也不一定需要重新</span><br><span class="line">      final boolean needsLayout = specChanged</span><br><span class="line">              &amp;&amp; (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);</span><br><span class="line"></span><br><span class="line">      if (forceLayout || needsLayout) &#123;</span><br><span class="line">      </span><br><span class="line">      // 测量本身</span><br><span class="line">       onMeasure(widthMeasureSpec, heightMeasureSpec);</span><br><span class="line"></span><br><span class="line">// 测量后，标记需要进入layout流程。</span><br><span class="line">          mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;</span><br><span class="line">      &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>第二次进入时，看看是否会触发 needLayout ？首先specChanged必然是 ture 的，然后看三个标志：(sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize)</p><ol><li>sAlwaysRemeasureExactly  是否设置了强制重新测量，这里先忽略，正常请情况应该都是false的</li><li>!isSpecExactly  测量模式不是 exactly</li><li>!matchesSpecSize 已经测量的值 不等于 spec期望的值</li></ol><p>三个标志位任一条件满足都会触发重新测量，但这么看人还是挺懵的，反过来看，什么时候会避免重新测量呢？</p><p>结合日常工作的常见的情况，什么时候，子View会避免重新测量呢？即 needLayout = false的情况</p><ol><li><p>当父级 spec不变，即父级大小不变，自然不会要求孩子重测了</p></li><li><p>当 spec变了，即父级大小变化了，孩子仍能避免重新测量</p></li></ol><p>第二种情况比较有趣，也是注释里面提到的优化点，即：当前子View已经测量出正确尺寸时，父级会传参exatcly，（避免无效测量），以达到优化布局目的。</p><p>此时，父级传来的 spec(mode + size ) 的size变化了，但是 mode 仍然是 exactly，可以判断不必测量自己View。</p><p>但若是 spec的mode是非 exatcly，即子View设置了wrap_content转换的 at_most，则会每次父级变化，自己本身也会要求重新测量。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;今天解决的一个bug很有意思，涉及View测量流程，故记之。&lt;/p&gt;
&lt;h4 id=&quot;bug描述：&quot;&gt;&lt;a href=&quot;#bug描述：&quot; class=&quot;headerlink&quot; title=&quot;bug描述：&quot;&gt;&lt;/a&gt;bug描述：&lt;/h4&gt;&lt;p&gt;Launcher中，当用户点击&lt;
      
    
    </summary>
    
      <category term="code" scheme="http://wjploop.github.io/categories/code/"/>
    
    
  </entry>
  
  <entry>
    <title>平静的感觉</title>
    <link href="http://wjploop.github.io/2022/04/06/%E5%B9%B3%E9%9D%99%E7%9A%84%E6%84%9F%E8%A7%89/"/>
    <id>http://wjploop.github.io/2022/04/06/平静的感觉/</id>
    <published>2022-04-06T03:13:42.000Z</published>
    <updated>2022-09-14T01:53:41.188Z</updated>
    
    <content type="html"><![CDATA[<p>清明收假回来上班，没什么活，坐在电脑前，带着降噪耳机，享受这平静的感觉。</p><p>平静下来，脑子也会浮现出很多事情，比如</p><ul><li>清明去广州玩遇到一贩卖机借机器故障之名拉人关注公众号</li><li>老婆去广州知识城学习，因为崇拜这个城市，连同将卖地摊衣服一眼误认为高大上的赠品</li><li>加了一个副业群，看着他人在讨论做什么产品好挣钱，感觉大家的想法往往会源自自己的生活，做的产品一般都是自己能够接触到的事物。</li></ul><p>关于副业的挣钱的事，以下展开来说。</p><p>副业自己能做什么，有什么好的点子呢？</p><p>自己所能想到的点子，往往是自己生活接触到的，自己受用的。据说，程序员搞副业的最火热的点子是前端搞导航页，移动端搞Todo、记账、日记。这些点子，因为自己平常都熟悉它的功能流程，所以在作为练手项目可以让我们专注于技术的实现。作为副业，这几个点子用于需求很多，但也存在竞争火热的特点，不太容易出众。</p><p>若想要做一个脱离自己的认知的范围的 App，试图找一个完全没人涉足的领域，做一个第一个吃螃蟹的人，着实不太实际。过于小众的需求，用户量就会太小撑不起一个app, 想起来自己做过一个小众工具的实践，解决用户想台式机电脑端控制手机蓝牙音乐的需求，实在太小众且不符合电脑自带蓝牙的趋势。最近在考科三练习模拟灯光时，开始以为自己发现了一个蓝海的需求，对于这个单一需求，市面上app过于古老没法用了，小程序只是很简单的文字选择题，再继续调查，发现在市场排行第一的“驾考宝典”已经支持了这个一功能，效果做的非常好，实物贴图了实车的灯光控制开关，开通使用这一功能还能开通其他功能，满足正常人贪全的心理，体验到这我也就心灰意冷了。小众单一的需求往往已经包括在大平台的一个小模块当中。</p><p>一个点子合不合适，小众或大众似乎也不是决定因素，大众的需求，即使竞争者很多，可市场可容纳的量也很大。瞎想这些东西也没什么意思。</p><p>记录一个感触，做一些顺应趋势的东西。随着物质水平的提高，大家压力都挺大，记录植物浇灌的app也出现了。养狗、养猫、养花花草草，要求是在依次降低的，在农村、别墅养个狗可以，在小出租房养猫方便，养花花草草条件要求更低了，应该适用于那些安静的人使用，而且，养了花花草草的人，应该都挺孤单的吧，养了花花草草的时候，应该是能享受这片刻的平静，也会耐心做一些纪录浇水施肥的过程。哈，养狗》猫》花草的过程的物质水平似乎趋向是更低的。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;清明收假回来上班，没什么活，坐在电脑前，带着降噪耳机，享受这平静的感觉。&lt;/p&gt;
&lt;p&gt;平静下来，脑子也会浮现出很多事情，比如&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;清明去广州玩遇到一贩卖机借机器故障之名拉人关注公众号&lt;/li&gt;
&lt;li&gt;老婆去广州知识城学习，因为崇拜这个城市，连同将卖
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>dart异步编程：Isolate和事件循环（译）</title>
    <link href="http://wjploop.github.io/2021/09/10/dart%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%EF%BC%9AIsolate%E5%92%8C%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF%EF%BC%88%E8%AF%91%EF%BC%89/"/>
    <id>http://wjploop.github.io/2021/09/10/dart异步编程：Isolate和事件循环（译）/</id>
    <published>2021-09-10T07:21:53.000Z</published>
    <updated>2022-09-14T01:53:41.175Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p><a href="https://medium.com/dartlang/dart-asynchronous-programming-isolates-and-event-loops-bffc3e296a6a" target="_blank" rel="noopener">原文</a></p></blockquote><p>Dart, 尽管作为一个单线程语言，但也提供了一系列的API，诸如Future, Stream，来帮助我们编写一个现代、异步、响应式的程序。本文主要讲述Dart在底层如何支持这样的后台任务，包括 isolate, 事件循环。</p><h2 id="Isolate"><a href="#Isolate" class="headerlink" title="Isolate"></a>Isolate</h2><p>Isolate，指代码运行之处，指Dart程序所拥有的内存空间，以及在其之上运行的事件循环。</p><p>在一些类似C++的语言中，支持多个线程共享一段内存空间，每个线程可以运行你想要的代码块（堆、方法区共享）。而在dart中，一个线程就是一个isolate和其持有的内存区，其线程就只是一直在处理事件。</p><p>通常一个Dart程序只会开启一个Isolate, 但也支持开启多个。若是有一个耗时任务在主线程中执行，会导致app丢帧，就可以用 <code>Isolsate.spawn()</code>, <code>Flutter的 compute()</code>方法，两者都会开启一个新的Isolate， 使得主Isolate忙于重建、渲染widget。</p><p>新的Isolate拥有自己的事件循环和内存空间，即使是由父类孵化(Spawn)出来的，父级也不允许访问孵化出来的Isolate。也就如其名所言，Isolate岛屿之意。</p><p>实际上，Isolate之间的交互只能通过相互发送信息。，相比于Java/C++不同线程共享一个内存空间的机制而言，这种缺乏共享内存的设计可能看起来过于严格，效率不高。但Dart定位为客户端语言来说，却也有一定好处。比如，内存分配，垃圾回收不必需要使用锁了。因为只有一个线程，对于一个段内存空间而言，在某一个时刻，永远只会有一个CPU在访问。这样的特点，非常适用于客户端程序，因为需要频繁的重建、销毁原来的widget对象。</p><h2 id="Event-Loops-事件循环"><a href="#Event-Loops-事件循环" class="headerlink" title="Event Loops 事件循环"></a>Event Loops 事件循环</h2><p>以上简单介绍了Isolate，让我们继续探究一个单线程如何实现异步的，即事件循环。</p><p>想象一个app的生命周期，包括了开始、结束、和中间一些系列事件的发生，包括io事件、用户点击事件等。</p><p>你的程序并不能预测这些事件何时会发生，所以你只能开始时定义好收到什么事件时，该如何处理。</p><blockquote><blockquote><p>不想继续翻译了，后面重复一个概念，告诉我们，所有无论是启动一个Callback、或开启一个Future，都是在添加一个事件处理到事件循环中，当其被调用时，只是收到了一个事件。</p></blockquote></blockquote><h2 id="本人后续思考补充"><a href="#本人后续思考补充" class="headerlink" title="本人后续思考补充"></a>本人后续思考补充</h2><h3 id="关于dart单线程的设计在客户端中具有优势？"><a href="#关于dart单线程的设计在客户端中具有优势？" class="headerlink" title="关于dart单线程的设计在客户端中具有优势？"></a>关于dart单线程的设计在客户端中具有优势？</h3><p>感觉优势不是很大呀。即便在支持多线程的语言的Android中，管理UI会由唯一的UI线程来处理。即便是对于SurfaceView的场景来说，有了额外的线程来绘制UI，在展示时还是得交由UI线程保证每一帧的有序。但，这应该不是dart所提到相对优势的点，多个线程同时处理一个UI的框架，似乎没有遇到过。</p><h3 id="dart有俩个队列，micro-event，为啥Android只有一个队列呢？"><a href="#dart有俩个队列，micro-event，为啥Android只有一个队列呢？" class="headerlink" title="dart有俩个队列，micro, event，为啥Android只有一个队列呢？"></a>dart有俩个队列，micro, event，为啥Android只有一个队列呢？</h3><p>问答这个问题，得先了解dart为什么要分了俩个队列，其作用是什么？</p><p><img src="https://dart.cn/articles/archive/images/both-queues.png" alt="flowchart: main() -&gt; microtasks -&gt; next event -&gt; microtasks -&gt; ..."></p><p>通过这个图，可知，microtask的优先级很高，每次从event队列取出一task时，会先把micro队列的task执行完。</p><p>假设我们 new 俩个future，相当于把俩个task A, B 依次放入到 event队列中，这里，我们可以保证 A &gt; B的顺序。</p><p>在执行A时，我们又 new 了 task C, 那么，可以保证顺序 A &gt; B &gt; C。</p><p>若是在执行 task A时，需要执行一个比较急迫的任务 urgentC，需要保证 A &gt; ugentC &gt; B,  故需要将urgentC放入到micro队列。</p><p>为什么会有这么一个需求呢？因为我们不知道后续的B需要执行多久，或是A，B同时修改一个状态时，我们的 urgentC需要依赖A修改后的状态。或是，我们在执行A时，发现不需要后续的B执行了。这个有点像Android Handler的removeMessage，比如急着将对象引用断开。可以注意到的是，这个急迫的 urgentC的队列为啥叫 microtask queue, 相比于急迫，更重要的是要保证该任务是轻量的。 实际上很多task放入event队列，比如响应用户的事件。</p><p>简单说，microd task就是保证该任务会优先于后续的event task执行。既然存在这么一个需求，dart存在micro task方案，那么在Android中肯定也有对应的方案。是的，MessageQueue存在一个同步屏障的概念，sync barrier。postSyncBarrier() 有这么一段注释:</p><blockquote><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">&gt;      * This method is used to immediately postpone execution of all subsequently posted</span><br><span class="line">&gt;      * synchronous messages until a condition is met that releases the barrier.</span><br><span class="line">&gt;      * Asynchronous messages (see &#123;@link Message#isAsynchronous&#125; are exempt from the barrier</span><br><span class="line">&gt;      * and continue to be processed as usual.</span><br><span class="line">&gt; </span><br><span class="line">&gt;       该方法用于推迟处理后续提交同步消息，直至同步屏障的释放。而异步消息则免于其干扰正常执行。</span><br><span class="line">&gt;</span><br></pre></td></tr></table></figure></blockquote><p>在Android中急迫的task是根据 异步任务和同步屏障配合而生效的，入队列的任务，当 barrier出现时，先优先执行异步消息。</p><p>异步任务与dart的micro task有一点区别，dart一旦发布micro task，必将优先一个个执行完。而android的异步任务，其优先性的是依赖barrier而生效的，可以说异步任务更为灵活点。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/dartlang/dart-asynchronous-programming-isolates-and-event-loops-bffc3e296a6a&quot; target=&quot;_blank&quot; re
      
    
    </summary>
    
    
      <category term="code flutter" scheme="http://wjploop.github.io/tags/code-flutter/"/>
    
  </entry>
  
  <entry>
    <title>谈谈对声明式框架的理解</title>
    <link href="http://wjploop.github.io/2021/08/28/%E8%B0%88%E8%B0%88%E5%AF%B9%E5%A3%B0%E6%98%8E%E5%BC%8F%E6%A1%86%E6%9E%B6%E7%9A%84%E7%90%86%E8%A7%A3/"/>
    <id>http://wjploop.github.io/2021/08/28/谈谈对声明式框架的理解/</id>
    <published>2021-08-28T08:58:51.000Z</published>
    <updated>2021-10-17T02:38:01.321Z</updated>
    
    <content type="html"><![CDATA[<p>昨晚把<code>React</code>的官方入门Demo敲了一遍，感觉对声明式的框架 <code>React,Flutter,Android-Compose</code>都有了一些认知，故想记录下。</p><p>我开始学习的是Flutter，看了文档，敲Demo，对比Android原生开发的一些熟悉概念，后面看到Android也推出了Compose，又跑去玩玩Compose，发现两个框架都在”抄” React，忍不住去认认这”祖师爷”，通过不同框架对同一概念的实现，从而加深对声明式框架的理解。</p><p>前端，主要干的活，定义如何展示数据，处理数据和视图的关系，处理事件到来后数据变化，重新渲染数据成视图。</p><p>其中，由于数据在视图中是变化的，故，我们将之称为状态state，而对于一部分数据对本视图而言，它是不可变的，我们称之为属性props，要注意的是这里的可变或不可变是对于本视图而言的，一般，父亲的state对孩子的来说就是props。而，事件通常分为用户产生，如触摸事件，和系统产生，如时钟滴答导致状态进入下一个生命周期，我们称之为动作action。</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">model = props + state 　// 数据由不变的属性和可变状态组成</span><br><span class="line">view = render(model)    // 视图通过渲染model形成</span><br></pre></td></tr></table></figure><p>此后便是声明式和传统的开发的区分</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></pre></td><td class="code"><pre><span class="line">action(model) &gt;&gt; diff   // 事件过来进行处理，产生一些变化diff</span><br><span class="line">new_view = view + renderDiff(dff)   // 根据这个diff渲染出一部分区别的view,　再将这个差别view融入原来的view以形成新的view</span><br></pre></td></tr></table></figure><p>存在的问题是，我们渲染数据的逻辑变得很复杂，为何？一个事件，产生一个变化diff，需要对这个diff添加新的渲染代码，即随着事件的远远不断地到来，我们要不断地维护更新这个视图树，随着事件变多，往往会出现，model和view不同步的情况，因为我们在修改model的同时，也要同时修改view。</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></pre></td><td class="code"><pre><span class="line">action(model) &gt;&gt; new_model  // 事件过来产生一个新的model</span><br><span class="line">new_view = render(new_model)    // 由于新的model和原来的model结构完全一致，故，我们可以使用原来的render方法</span><br></pre></td></tr></table></figure><p>这样，当定义好一个渲染逻辑后，只要model的结构不变，我们就可以不用改变原来的render方法，从而保持渲染逻辑的简单。</p><p>以上，是对声明式框架的理解，以下做一些额外的思考。</p><p>若论性能而言，传统开发更优，但是声明式开发模式更注重开发效率和代码维护性。 以此，声明式开发是未来的一种趋势。</p><p>传统方式，直接修改原来数据，然后视图同步数据的修改，而声明式开发，复制了全部的数据，修改数据后交给视图一个全新的数据镜像重新渲染。根据这点，声明式要使用更多的内存和更频繁的对象销毁回收，因此在利用计算机性能上效率低。但，从历史趋势的趋势上声明式开发是未来，一方面，计算性能在增加，另一方面，降低编程心智负担一直我们追求的目标；这个论证过程也可从编程语言的发展史得到印证。</p><p>而在框架的具体实现上，性能上也做了很多优化。Flutter，从用户描述到渲染经过了 widget &gt; element &gt; renderObject的过程，widget是我们对一个组件的最简单的描述定义，element表示了其组件的关系树，而renderObject才是真正用于渲染的视图树。其作用是，导致widget对象都很小，回收成本低，而真正的渲染树，在widget修改时，对应渲染树只是增量更新，而不是和widget一样全量更新。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;昨晚把&lt;code&gt;React&lt;/code&gt;的官方入门Demo敲了一遍，感觉对声明式的框架 &lt;code&gt;React,Flutter,Android-Compose&lt;/code&gt;都有了一些认知，故想记录下。&lt;/p&gt;
&lt;p&gt;我开始学习的是Flutter，看了文档，敲Demo，对比
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>追《新世纪福音战士》后做了一个奇怪的梦”</title>
    <link href="http://wjploop.github.io/2021/08/02/%E8%BF%BD%E3%80%8A%E6%96%B0%E4%B8%96%E7%BA%AA%E7%A6%8F%E9%9F%B3%E6%88%98%E5%A3%AB%E3%80%8B%E5%90%8E%E5%81%9A%E4%BA%86%E4%B8%80%E4%B8%AA%E5%A5%87%E6%80%AA%E7%9A%84%E6%A2%A6%E2%80%9D/"/>
    <id>http://wjploop.github.io/2021/08/02/追《新世纪福音战士》后做了一个奇怪的梦”/</id>
    <published>2021-08-02T02:57:15.000Z</published>
    <updated>2022-09-14T01:53:41.176Z</updated>
    
    <content type="html"><![CDATA[<p>梦醒回到现实，我庆幸我活到现在没有做过一件无法弥补的事，至少相对梦境中我的处境而言…</p><p>梦中，我抢了银行，钱到手后逃跑途中，我拿着一大跌崭新的纸币，在一片慌乱中，我想将钱塞给逃跑中的帮手们，我纠结我该一人给多少张百元纸币，一人给5张可能不够发，一人给3张可能会让他们不满意，在纠结中和慌忙逃跑中，我忘了分发了多少，有的塞了少有的多，过后我还剩下了很多。而这些帮手们也没有过于在意收到了多少，因为大家都在慌忙地逃跑，后面有枪声，也不清楚其他人收到了多少。  </p><p>我逃到教室上自己座位上，手中还有一小碟纸币，裤袋还有一大叠纸币，这都是我剩下没有发完的，我后悔了，我不该把这些钱带回来，这有可能会暴露我，我后悔没有直接将钱撒在路上让他们拿了。</p><p>我想逃跑中肯定有人被捉住了，帮手们不认识我，不会通过他们找到我，但是剩下的这些纸币却可能暴露我，因为这都是一叠纸币分发的，其中序号是相连的，只要我使用了就可能会被追踪到，这钱我不能用。我想找个地方把钱埋了，害怕新挖的土会被人看到。我想把钱烧了，却没有一个合适的地方，我怕钱烧起来会发出味道被人发现，我怕钱烧成了灰还是可以被认出是纸币，我要把它的灰搓碎认不出。</p><p>可，现在我在教室中，我在我的位置上，我同桌来了，我裤带中的人民币露出了一个角，闪着崭新人民币的光，我赶紧用上衣遮住，身体也弯腰趴到桌子上，我想把钱放到抽屉中，不能放在抽屉，纸币那么薄，从抽屉的缝掉出来我就完了，我低头看了抽屉中还好有一个书包，我想到纸币放到书包中，拉着拉链不会掉出来。好巧，同桌出去了，他到走廊吹风聊天了，我回头环视后面，我怕后面有人看到我把纸币放到抽屉中书包的过程，后面有个人似乎可以看到，但那人永远一副趴桌子的样子，我不知道他有没有在睡着，万一他刚好起来看到怎么办。纸币放在裤带中，太容易暴漏了，我要把它放到抽屉中，我用身体掩着，我要把钱放到抽屉中，我不敢回头，我怕那个同学刚好抬头看到我这么鬼祟的样子萌发好奇心看到，我不敢回头确认他有没有起来，回头可能会增加我鬼祟的模样，前面远处的同学反而好奇怀疑我在干嘛，我只能自顾尽快把纸币放进去，祈祷不会有人看到，我也不知道有没有看到。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;梦醒回到现实，我庆幸我活到现在没有做过一件无法弥补的事，至少相对梦境中我的处境而言…&lt;/p&gt;
&lt;p&gt;梦中，我抢了银行，钱到手后逃跑途中，我拿着一大跌崭新的纸币，在一片慌乱中，我想将钱塞给逃跑中的帮手们，我纠结我该一人给多少张百元纸币，一人给5张可能不够发，
一人给3张可能会
      
    
    </summary>
    
    
      <category term="生活" scheme="http://wjploop.github.io/tags/%E7%94%9F%E6%B4%BB/"/>
    
  </entry>
  
  <entry>
    <title>OKHttp 文档篇</title>
    <link href="http://wjploop.github.io/2021/07/28/OKHttp-%E6%96%87%E6%A1%A3%E7%AF%87/"/>
    <id>http://wjploop.github.io/2021/07/28/OKHttp-文档篇/</id>
    <published>2021-07-28T06:03:07.000Z</published>
    <updated>2022-05-17T13:49:02.366Z</updated>
    
    <content type="html"><![CDATA[<p>初略的看了Retrofit之后，OkHttp就是必不可少的了，Retrofit依赖了OkHttp，两个库都有一个<code>Call</code>这么一个类，其意图也一样，只是所处的层次不同。Retrofit依赖OkHttp来实现真正的Http网络请求。而OkHttp相对来是更加复杂的，本文先把其文档过一遍。</p><h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><h3 id="OkHttp"><a href="#OkHttp" class="headerlink" title="OkHttp"></a>OkHttp</h3><p>OkHttp是一个实现HTTP协议的客户端，其高效性源于：</p><ul><li>Http/2 若多个请求目标是一个主机，这几个请求可以共享一个Socket</li><li>若是Http/2不支持，即服务端不支持，用连接池的降低请求延迟</li><li>自带GZIP压缩数据</li><li>对于重复请求，自带一个Response缓存避免无意义请求</li></ul><p>当网络不好时，OkHttp支持自动重试。能帮助处理一些常见的问题，比如服务有多个IP地址，若第一个IP失败，会自动切换到后续的IP尝试，这对于多个数据源的服务端来说很有用。<br>支持现代 TLS 特性，（TLS 1.3, ALPN, certificate pinning）// todo 了解下</p><p>OkHttp是易用的，其request/response可以用构造者模式创建，且其对象是不可变的（immutability）。<br>支持同步异步调用。</p><blockquote><p>补充，不可变性的优势有<br>不用担心并发，既然该对象不可变，那么多个线程访问时，就不存在不同步问题。免除副作用的影响，一个对象不可变，那么其使用过程中始终是一样的，假设使用两次，第二次结果保证和第一次一样。</p></blockquote><h3 id="Get-a-URL"><a href="#Get-a-URL" class="headerlink" title="Get a URL"></a>Get a URL</h3><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">OkHttpClient client = new OkHttpClient();</span><br><span class="line"></span><br><span class="line">String run(String url) throws IOException &#123;</span><br><span class="line">  // 构造者模式，按照需修改部分配置</span><br><span class="line">  Request request = new Request.Builder()</span><br><span class="line">      .url(url)</span><br><span class="line">      .build();</span><br><span class="line"></span><br><span class="line">    // 同步调用    </span><br><span class="line">  try (Response response = client.newCall(request).execute()) &#123;</span><br><span class="line">    return response.body().string();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Post-to-a-Server"><a href="#Post-to-a-Server" class="headerlink" title="Post to a Server"></a>Post to a Server</h3><p>发送http post请求时，携带实体数据body   </p><blockquote><p>http 格式包括 requestLine、header、body</p></blockquote><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">public static final MediaType JSON = MediaType.get(&quot;application/json; charset=utf-8&quot;);</span><br><span class="line"></span><br><span class="line">OkHttpClient client = new OkHttpClient();</span><br><span class="line"></span><br><span class="line">String post(String url, String json) throws IOException &#123;</span><br><span class="line">  RequestBody body = RequestBody.create(JSON, json);</span><br><span class="line">  Request request = new Request.Builder()</span><br><span class="line">      .url(url)</span><br><span class="line">      .post(body)</span><br><span class="line">      .build();</span><br><span class="line">  try (Response response = client.newCall(request).execute()) &#123;</span><br><span class="line">    return response.body().string();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Requirements"><a href="#Requirements" class="headerlink" title="Requirements"></a>Requirements</h3><p>要求Android 5.0+，Java 8+</p><p>依赖了 OKio 和 Kotlin标准库，两者向后兼容都是令人放心的。</p><p>推荐保持该库最新版本，和浏览器一样，保持最新的http客户端可提升安全性。本库会跟上TLS的更新。</p><p>OkHttp 会使用平台自带的TLS的实现等等。</p><h2 id="Call"><a href="#Call" class="headerlink" title="Call"></a>Call</h2><p>Http client要做的事很清晰，接受一个request，产生一个response，使用Call来表示。</p><h3 id="Rewriting-request-重写一个请求"><a href="#Rewriting-request-重写一个请求" class="headerlink" title="Rewriting request 重写一个请求"></a>Rewriting request 重写一个请求</h3><p>用户在使用OkHttp时，是在站在比较高得层次思考需求得，我们只是简单得描述一个http请求，而okHttp会补充一些额外得细节，使得请求更加有效率。</p><p>对于 origin request，okHttp可能会添加 Content-Length, TransferEncoding, User-Agent, Host, Connection, Content-Type。若是没有注明 Accept-Encoding，会依据Response添加合适的。<br>若存在Cookie，也会添加。  </p><p>对于一些请求会使用缓存好的Response.当服务端支持了，If-Modified-Since If-None-Match头部信息，当客户端存在缓存时，会发送一个 get 请求来获知本地的缓存是否可用，客户端携带着我何时获取过这个信息，服务端判断是否需要返回更新的数据，假设没有更新，将返回一个没有body的 response，从而提高效率。</p><h3 id="Rewriting-Response-重写一个响应"><a href="#Rewriting-Response-重写一个响应" class="headerlink" title="Rewriting Response 重写一个响应"></a>Rewriting Response 重写一个响应</h3><p>若是透明压缩开启，OkHttp将会丢弃Content-Encoding 和 Content-Length，因为对用户来说，其得到的时解压后的body，而header返回的是压缩的body，故其长度是不对的。 （个人疑问？为什么不把解压后的长度值更新到header中，然后返回给用户?）</p><h3 id="Follow-up-Request-自动跟随request"><a href="#Follow-up-Request-自动跟随request" class="headerlink" title="Follow-up Request 自动跟随request"></a>Follow-up Request 自动跟随request</h3><blockquote><p>重定向，转发的区别？重定向会返回一个新的地址给客户端，让其重新请求，客户端有感知转发，则在服务器内部转发该请求，客户端无感知</p></blockquote><p>对于重定向请求，服务器返回响应码为 302 ，okHttp会帮助重新请求新的地址，返回给最终结果给用户。这样，用户对重定向也无感知了~~</p><p>对于需要验证身份的请求，（响应码为401，身份未验证），OkHttp会自动从 Authenticator(需要自己配置)，获取对应的凭证来验证身份。</p><h3 id="Retrying-Request-请求重试"><a href="#Retrying-Request-请求重试" class="headerlink" title="Retrying Request 请求重试"></a>Retrying Request 请求重试</h3><p>有时连接失败了，可能时因为连接断开了，或服务端不可达，OkHttp会自动尝试不同的路由。</p><p>由于，rewrite, redirect, follow-ups 和 retries的存在，一个请求可能会在中间过程会有很多个 request和 response,而 OkHttp 使用Call来封装这些过程，用户感觉到还是只有一个输入一个输出。</p><p>Call执行有两种方式：</p><ul><li>同步 sync: 调用线程阻塞等待知道结果可读。</li><li>异步 async: 调用线程只是将请求入队列，传入的callback会在结果可用时执行。</li></ul><p>Call 可以被任意线程取消，只要该请求完成都可以取消。<br>注意，当某一个线程被取消时，此时在写入reqeustBody或读取responseBody的代码都会抛出IO异常，设计如此。</p><h3 id="Dispatch-分发调度"><a href="#Dispatch-分发调度" class="headerlink" title="Dispatch 分发调度"></a>Dispatch 分发调度</h3><p>对于异步请求，如何管理同时并发的请求是个问题。创建过多的连接会方法资源，过少会导致请求延迟较大。</p><p>如何平衡空间与时间的问题，使用Dispatcher来设置，默认的连接池大小的，默认为5，最大值为64。  </p><blockquote><p>其原理与线程池基本是一致的。</p></blockquote><h2 id="Caching"><a href="#Caching" class="headerlink" title="Caching"></a>Caching</h2><p>OkHttp 提供了一个可选的缓存模块，默认关闭。 参照一些实用的设计，比如市面的浏览器。</p><h3 id="Basic-Usage"><a href="#Basic-Usage" class="headerlink" title="Basic Usage"></a>Basic Usage</h3><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">private val client: OkHttpClient = OkHttpClient.Builder()</span><br><span class="line">    .cache(Cache(</span><br><span class="line">          // 缓存文件路径</span><br><span class="line">        directory = File(application.cacheDir, &quot;http_cache&quot;),</span><br><span class="line">        // $0.05 worth of phone storage in 2020</span><br><span class="line">        // 最大的缓存大小</span><br><span class="line">        maxSize = 50L * 1024L * 1024L // 50 MiB</span><br><span class="line">    ))</span><br><span class="line">    .build()</span><br></pre></td></tr></table></figure><p>理想情况下，若是缓存命中，会跳过 DNS, 连接网络，下载数据包，</p><p>未命中，比如，未请求过该数据，或该数据不可缓存，依据返回来的时间判断缓存已经过期了。</p><p>Conditional Cache Hit, (条件命中？)，需要请求网络来确定本地的缓存能够可用，通常是服务端返回一个304的状态告知缓存未修改，即依据客户端请求发来的if-modified-since。</p><h2 id="Connection"><a href="#Connection" class="headerlink" title="Connection"></a>Connection</h2><p>虽然用户只提供了一个URL来发送一个请求，但是OkHttp建立一个连接却需要三个要素: URL, Address, Route</p><h3 id="URL"><a href="#URL" class="headerlink" title="URL"></a>URL</h3><p>何为URL, Uniform Resource Locator，统一资源定位，比如一个 <a href="https://github.com/square/okhttp。" target="_blank" rel="noopener">https://github.com/square/okhttp。</a>  </p><p>是抽象的</p><ul><li>只是简单区分非加密http和加密https,却没有指定使用哪种加密算法。既没有要求如何验证对方的证书（HostNameVerify），也没说哪些证书可以信任（SSLSocketFactory）。  </li><li>没有指明哪些代理服务应该使用，如何验证代理服务器。</li></ul><p>也是具体的，一个URL指定了资源路径和查询参数，每个Web服务支持很多URL.</p><h3 id="Address"><a href="#Address" class="headerlink" title="Address"></a>Address</h3><p>地址指定了连接的是哪一个web服务，以及一些静态的配置，如端口，https的设置，偏好的协议（Http/2, SPDY）</p><blockquote><p>SPDY是一个优化的Http协议，多路复用、请求优先级、报头压缩（http报头用的明文，这里用的是二进制）。不过，http/2推出后已经不建议使用了<del>~</del></p></blockquote><p>同一个地址的URL可以共享一个tcp socket连接，低延迟，高吞吐。<br>若是服务端支持http/1.x，那么其连接池会复用。</p><blockquote><p>还是好好了解http 1.x &gt; 2.0的变化吧</p></blockquote><h3 id="Routes"><a href="#Routes" class="headerlink" title="Routes"></a>Routes</h3>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;初略的看了Retrofit之后，OkHttp就是必不可少的了，Retrofit依赖了OkHttp，两个库都有一个&lt;code&gt;Call&lt;/code&gt;这么一个类，其意图也一样，只是所处的层次不同。
Retrofit依赖OkHttp来实现真正的Http网络请求。而OkHttp相对
      
    
    </summary>
    
    
      <category term="Android 笔记" scheme="http://wjploop.github.io/tags/Android-%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>SAM-conversion 是什么</title>
    <link href="http://wjploop.github.io/2021/07/10/SAM-conversion%20%E6%98%AF%E4%BB%80%E4%B9%88/"/>
    <id>http://wjploop.github.io/2021/07/10/SAM-conversion 是什么/</id>
    <published>2021-07-10T07:08:41.000Z</published>
    <updated>2022-09-28T06:12:35.382Z</updated>
    
    <content type="html"><![CDATA[<h2 id="关于-SAM-conversion"><a href="#关于-SAM-conversion" class="headerlink" title="关于 SAM-conversion"></a>关于 SAM-conversion</h2><p>看到一些注释提到 SAM-conversion，查找之后记录下。</p><p>SAM全称是 Single Abstract Method，当 Interface 只有一个抽象方法时，它可以当作一个函数来调用。比如View点击接口：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 单一抽象方法的接口</span></span><br><span class="line">inteface OnClickListener&#123;</span><br><span class="line">    onClick(View v);</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">View</span></span>&#123;</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">setOnClickListener</span><span class="params">(OnClickListener l)</span> </span>&#123;</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="comment">// 不使用lambda，显式的创建实例</span></span><br><span class="line">view.setOnClickListener(object:View.OnClickListener&#123;</span><br><span class="line">       <span class="function">override fun <span class="title">onClick</span><span class="params">(v: View?)</span> </span>&#123;</span><br><span class="line">                </span><br><span class="line">          &#125;</span><br><span class="line">        &#125;)</span><br><span class="line"><span class="comment">// 使用lambda，隐式创建实例，编译器帮我们转换成显式的代码</span></span><br><span class="line">view.setOnClickListener&#123;v-&gt;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当将这个接口作为一个方法参数时，需要分两步：</p><ol><li>创建一个实现类，并实现抽象方法（一般是匿名类）</li><li>创建的实现类的实例</li></ol><p>代码上，当形参类型是一个单一方法的接口时，可以用 lambda 表达式代替，但实际中，其形参还是需要一个具体的对象，所以 SAM-conversion就是，代码写了lambda，编译时帮我们转换成显式的代码，运行时创建接口的实例。</p><h2 id="SAM对比类型别名（Type-aliases）"><a href="#SAM对比类型别名（Type-aliases）" class="headerlink" title="SAM对比类型别名（Type aliases）"></a>SAM对比类型别名（Type aliases）</h2><p>与 Java 不同，Kotlin 支持函数类型，函数可以作为方法的形参。我们可以为一个函数类型定义一个别名，如下：</p><figure class="highlight kotlin"><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="comment">// 为一个函数类型取一个别名</span></span><br><span class="line">typelias OnClick = (view) -&gt; <span class="built_in">Unit</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 函数作为形参</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> View.<span class="title">setOnClick</span><span class="params">(onClick: <span class="type">OnClick</span>)</span></span></span><br></pre></td></tr></table></figure><p>虽然类型别名和SAM接口非常像，尤其在使用的时候，都可以写成lambda的形式，但其区别还是有的。</p><ol><li>SAM效率效率低点，需要创建一个新的实例类，并创建实例。而类型别名不会创建新的类型</li><li>SAM也有优势，其接口类可以包含其他成员，比如其他非抽象成员，适用复杂的场景<ol><li>SAM适用和 Java无缝调用，而在 Kotlin 定义的类型别名，在Java中无妨使用</li></ol></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;关于-SAM-conversion&quot;&gt;&lt;a href=&quot;#关于-SAM-conversion&quot; class=&quot;headerlink&quot; title=&quot;关于 SAM-conversion&quot;&gt;&lt;/a&gt;关于 SAM-conversion&lt;/h2&gt;&lt;p&gt;看到一些注释提到 S
      
    
    </summary>
    
    
      <category term="Android" scheme="http://wjploop.github.io/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>Retrofit</title>
    <link href="http://wjploop.github.io/2021/07/10/Retrofit/"/>
    <id>http://wjploop.github.io/2021/07/10/Retrofit/</id>
    <published>2021-07-10T07:08:41.000Z</published>
    <updated>2022-05-17T13:49:02.365Z</updated>
    
    <content type="html"><![CDATA[<h2 id="整体认识"><a href="#整体认识" class="headerlink" title="整体认识"></a>整体认识</h2><p>Retrofit 用接口中的方法和注解来描述一个HTTP请求，用户只用关注接口如何描述这个请求，而不用关注内部的具体实现。它将网络请求中，封装Request对象，解析Response对象等模板代码封装了起来，使得使用者<em>只要描述做什么，而无需关心怎么做</em>。</p><p>内部网络请求部分依赖了OkHttp,这样，解析方法后，就是OkHttp的工作了，创建Request，解析Response，而在这两个过程，涉及到数据（json, xml, 二进制（protobuf)）与Java对象的转换，即数据序列化/反序列，这里Retrofit没有新造轮子，而是提供一个适配器，让用户选择合适自己需求的工具，完美践行了<em>对扩展开放</em>的原则。</p><h2 id="动态代理"><a href="#动态代理" class="headerlink" title="动态代理"></a>动态代理</h2><p>关于这个设计模式，说下自己的理解。</p><p>代理，需要一个服务<code>ServiceA</code>，存在现有的类（代理目标类<code>Obj</code>）可以满足或满足部分需求，这时，假设想让<code>User</code>也拥有提供该服务的能力，我们不是直接继承<code>Obj</code>， 而时将该服务抽象为一个接口<code>ProvideServiceA</code>，表示具备可以提供这个服务A，此时，真正提供具备这个服务实现类为<code>Obj</code>，而我们的<code>User</code>也想拥有这份能力，可<code>User</code>又不但仅有这份能力，它还可能拥有A,B,C能力，故，它在是实现了 <code>ProviderServiceA</code>接口，但不是自己实现，而是直接使用<code>Obj</code>来提供该服务。</p><p>简单说，就是声明了具备某个服务的能力后，内部提供该接口的实现类的实例，在方法中转发该消息到实例来完成。</p><p>可以发现，当有一个类可以提供某个服务后，我们不是简单继承它来表示具备有该服务能力，而是添加一个接口，表示是是实现该接口的类都具备有这份能力，这样，我们会发现，我们需要额外引入一个接口类，这是不是Java不支持多继承导致的额外成本呢？</p><p>这个问题我无法判断。</p><p>在Java的概念中，接口表示一份契约，只是表示提供了某个服务，具体的实现延迟到了具体的实现类实现。  </p><p>一个复杂的类，可以实现多个接口，它表示可以提供一系列服务的类，这些服务对外形成了一个整体的概念，而在实现方法中，又可以划分为子服务，在内部使用其他自服务的实例来完成，以这个概念来看，我觉得这样的思考方法非常符合<code>分治</code>的思维，将大问题拆分为子问题，在子问题的实现上，可能又会继续拆分。大问题和子问题之间的关系，完成依照它们的接口来完成，不会跟他们的子问题实现产生联系，这样，在查看代码时，使得我们很容易在了解这一个层次上类关系，而不是陷入子问题实现的细节中。       </p><p>好吧，扯得有点远了。<br>代理模式在印象中有一个案例是，在Android插件化中，对于Android四大组件类，我们会对他们进行插桩，在Manifest清单文件中声明一个ProxyActivity，而在我们得插件类PluginActivity中，虽然我们实现了Activity得方法，但是其具体得实现是转发给ProxyActivity来是实现的，比如 <code>findViewById()</code>,<code>getAssertManager()</code>方法，因为即使我们在插件模块中创建了Activity实例，我们也不能直接使用相关的方法，因为这个Activity是没有在Android系统中注册，系统在处理Intent时是找不到该Activity。</p><p>而动态代理，区别于静态代理需要声明一个接口，在目标类和代理中都要是实现接口方法，动态代理不需要在编译时引入接口类，在调用代理类方法时，除了转发给目标类对应方法，也可以添加自己的增强代码（日志、额外操作等）。</p><h2 id="感叹细节实现"><a href="#感叹细节实现" class="headerlink" title="感叹细节实现"></a>感叹细节实现</h2><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"> </span><br><span class="line"> // 描述业务上需要Http方法</span><br><span class="line">interface Service &#123;</span><br><span class="line">  @GET(&quot;/&quot;) Call&lt;ResponseBody&gt; getBody();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  // 用于创建一个Retrofit实例</span><br><span class="line">  Retrofit retrofit = new Retrofit.Builder()</span><br><span class="line">      .baseUrl(server.url(&quot;/&quot;))</span><br><span class="line">      .build();</span><br><span class="line">  // 为描述接口方法创建具体的代理实例</span><br><span class="line">  Service example = retrofit.create(Service.class);</span><br></pre></td></tr></table></figure><h2 id="创建一个代理实例"><a href="#创建一个代理实例" class="headerlink" title="创建一个代理实例"></a>创建一个代理实例</h2><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">public &lt;T&gt; T create(final Class&lt;T&gt; service) &#123;</span><br><span class="line">  validateServiceInterface(service);</span><br><span class="line">  return (T)</span><br><span class="line">          // 返回一个代理实例，代理实现interfaces中的方法</span><br><span class="line">          // 所有方法都会通过InvocationHandler来实现</span><br><span class="line">          // 被代理的接口，仅是描述了要干什么，怎么干委托给了handler</span><br><span class="line">          // 原本接口的方法实现仅是 method.invoke(proxy, args)，或者为abstract方法无法直接调用</span><br><span class="line">          // 而在handler中我们可以在基于该方法默认实现之上，可以做AOP类似的骚操作，比如修改参数、修改返回值，插入日志、甚至替换原来的实现</span><br><span class="line">          // 而在这个库中，只是提取方法中信息（主要是注解），创建一个ServiceMethod，而在这个方法后</span><br><span class="line">      Proxy.newProxyInstance(</span><br><span class="line">          service.getClassLoader(),</span><br><span class="line">          // 被代理额目标类，所有的方法都会转发给下面的Handler处理</span><br><span class="line">          new Class&lt;?&gt;[] &#123;service&#125;,</span><br><span class="line">          new InvocationHandler() &#123;</span><br><span class="line">            private final Platform platform = Platform.get();</span><br><span class="line">            private final Object[] emptyArgs = new Object[0];</span><br><span class="line"></span><br><span class="line">            @Override</span><br><span class="line">            public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)</span><br><span class="line">                throws Throwable &#123;</span><br><span class="line">              // If the method is a method from Object then defer to normal invocation.</span><br><span class="line">              // 正常调用Object的方法</span><br><span class="line">              if (method.getDeclaringClass() == Object.class) &#123;</span><br><span class="line">                return method.invoke(this, args);</span><br><span class="line">              &#125;</span><br><span class="line">              args = args != null ? args : emptyArgs;</span><br><span class="line">              return platform.isDefaultMethod(method)</span><br><span class="line">                  // 若是Java8提供的默认方法，直接调用</span><br><span class="line">                  ? platform.invokeDefaultMethod(method, service, proxy, args)</span><br><span class="line">                  // 解析该方法并执行</span><br><span class="line">                  : loadServiceMethod(method).invoke(args);</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>以下就是解析方法，执行方法了</p><h2 id="解析方法"><a href="#解析方法" class="headerlink" title="解析方法"></a>解析方法</h2><h3 id="使用缓存避免解析多次"><a href="#使用缓存避免解析多次" class="headerlink" title="使用缓存避免解析多次"></a>使用缓存避免解析多次</h3><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">ServiceMethod&lt;?&gt; loadServiceMethod(Method method) &#123;</span><br><span class="line">  // 解析方法费时，缓存下来</span><br><span class="line">  ServiceMethod&lt;?&gt; result = serviceMethodCache.get(method);</span><br><span class="line">  if (result != null) return result;</span><br><span class="line"></span><br><span class="line">  // 这里为什么需要加锁呢？</span><br><span class="line">  // 在添加方法时，避免解析多次</span><br><span class="line">  synchronized (serviceMethodCache) &#123;</span><br><span class="line">    // 假设由两个线程A,B都调用同一个方法，且该方法未解析</span><br><span class="line">    // A解析后修改map，退出</span><br><span class="line">    // B等到A释放锁后，再次检验该map，来确认是否要解析</span><br><span class="line">    result = serviceMethodCache.get(method);</span><br><span class="line">    if (result == null) &#123;</span><br><span class="line">      // 见下解析方法2</span><br><span class="line">      result = ServiceMethod.parseAnnotations(this, method);</span><br><span class="line">      serviceMethodCache.put(method, result);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  return result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="解析出信息"><a href="#解析出信息" class="headerlink" title="解析出信息"></a>解析出信息</h3><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">static &lt;T&gt; ServiceMethod&lt;T&gt; parseAnnotations(Retrofit retrofit, Method method) &#123;</span><br><span class="line">  // 只是解析注解信息出来</span><br><span class="line">  RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);</span><br><span class="line"></span><br><span class="line">  // 判断方法的返回值</span><br><span class="line">  Type returnType = method.getGenericReturnType();</span><br><span class="line">  if (Utils.hasUnresolvableType(returnType)) &#123;</span><br><span class="line">    throw methodError(</span><br><span class="line">        method,</span><br><span class="line">        &quot;Method return type must not include a type variable or wildcard: %s&quot;,</span><br><span class="line">        returnType);</span><br><span class="line">  &#125;</span><br><span class="line">  // 注意定义的接口时不能返回值为空的，</span><br><span class="line">  // 那么怎么不判断Kotlin的Unit? todo</span><br><span class="line">  if (returnType == void.class) &#123;</span><br><span class="line">    throw methodError(method, &quot;Service methods cannot return void.&quot;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  // Http方法具体实现，见下</span><br><span class="line">  return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>HttpServiceMethod.java</p></blockquote><p>解析注解信息后，依据返回值类型，选择可用的 CallAdapter、Converter</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">static &lt;ResponseT, ReturnT&gt; HttpServiceMethod&lt;ResponseT, ReturnT&gt; parseAnnotations(</span><br><span class="line">     Retrofit retrofit, Method method, RequestFactory requestFactory) &#123;</span><br><span class="line">   Annotation[] annotations = method.getAnnotations();</span><br><span class="line">   // 获取返回值类型</span><br><span class="line">   Type adapterType;</span><br><span class="line">   adapterType = method.getGenericReturnType();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">   // 根据解析出来了方法返回值类型，选择合适的CallAdapter</span><br><span class="line">   // 何为CallAdapter, 将</span><br><span class="line">   CallAdapter&lt;ResponseT, ReturnT&gt; callAdapter =</span><br><span class="line">       createCallAdapter(retrofit, method, adapterType, annotations);</span><br><span class="line">   // 返回确切的Response类型</span><br><span class="line">   // 比如在RxJava Complete会是Void.class, Observable&lt;User&gt;，则是User</span><br><span class="line">   Type responseType = callAdapter.responseType();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">   if (responseType == Response.class) &#123;</span><br><span class="line">     throw methodError(method, &quot;Response must include generic type (e.g., Response&lt;String&gt;)&quot;);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   // OKHttp返回一个ResponseBody，选择一个Converter来转换</span><br><span class="line"></span><br><span class="line">   Converter&lt;ResponseBody, ResponseT&gt; responseConverter =</span><br><span class="line">       createResponseConverter(retrofit, method, responseType);</span><br><span class="line"></span><br><span class="line">   okhttp3.Call.Factory callFactory = retrofit.callFactory;</span><br><span class="line">   return new CallAdapted&lt;&gt;(requestFactory, callFactory, responseConverter, callAdapter);</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="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">@Override</span><br><span class="line">final @Nullable ReturnT invoke(Object[] args) &#123;</span><br><span class="line">  // 创建一个OkHttpCall</span><br><span class="line">  Call&lt;ResponseT&gt; call = new OkHttpCall&lt;&gt;(requestFactory, args, callFactory, responseConverter);</span><br><span class="line">  // 准备一切后，真正开始调用方法</span><br><span class="line">  return adapt(call, args);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>比如在Rxjava中的 adapt方法实现</p><p>原本一个Call的返回只是一个Response，通过适配adapt，将其转换成我们想要的东西。</p><p>在RxJava中将Response创建了一个可以观察的源，</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  @Override</span><br><span class="line">  public Object adapt(Call&lt;R&gt; call) &#123;</span><br><span class="line">  </span><br><span class="line">    Observable&lt;Response&lt;R&gt;&gt; responseObservable =</span><br><span class="line">        isAsync ? new CallEnqueueObservable&lt;&gt;(call) : new CallExecuteObservable&lt;&gt;(call);</span><br><span class="line"></span><br><span class="line">    Observable&lt;?&gt; observable;</span><br><span class="line">    if (isResult) &#123;</span><br><span class="line">      observable = new ResultObservable&lt;&gt;(responseObservable);</span><br><span class="line">    &#125; else if (isBody) &#123;</span><br><span class="line">      observable = new BodyObservable&lt;&gt;(responseObservable);</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      observable = responseObservable;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    // 不为空，还为其切换到响应的线程执行</span><br><span class="line">    if (scheduler != null) &#123;</span><br><span class="line">      observable = observable.subscribeOn(scheduler);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if (isFlowable) &#123;</span><br><span class="line">      return observable.toFlowable(BackpressureStrategy.LATEST);</span><br><span class="line">    &#125;</span><br><span class="line">    if (isSingle) &#123;</span><br><span class="line">      return observable.singleOrError();</span><br><span class="line">    &#125;</span><br><span class="line">    if (isMaybe) &#123;</span><br><span class="line">      return observable.singleElement();</span><br><span class="line">    &#125;</span><br><span class="line">    if (isCompletable) &#123;</span><br><span class="line">      return observable.ignoreElements();</span><br><span class="line">    &#125;</span><br><span class="line">    return RxJavaPlugins.onAssembly(observable);</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><p>整体下来，看得挺懵逼的~  </p><p>基本思路，解析方法上的信息后，尤其时返回值类型，判断其需要的CallAdapter，用于将请求返回值构建成对应的实例。默认下，返回一个定义Call,表示了一个请求，包括发送一个request和返回一个Response，当RxJava的适配器时则返回一个Observable。</p><p>另一个就是Converter，将对象转换成http协议支持的类型，比如Json，普通文本，或数据流。转换时，包括创建请求时的requestBodyConverter以及responseBodyConverter。</p><p>无论是CallAdapter，还是Converter，两者都是可以配置补充的。当解析接口方法时，检查到其类型时，在Retrofit配置的Factory支持集合中选择合适的类型。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;整体认识&quot;&gt;&lt;a href=&quot;#整体认识&quot; class=&quot;headerlink&quot; title=&quot;整体认识&quot;&gt;&lt;/a&gt;整体认识&lt;/h2&gt;&lt;p&gt;Retrofit 用接口中的方法和注解来描述一个HTTP请求，用户只用关注接口如何描述这个请求，而不用关注内部的具体实现。它
      
    
    </summary>
    
    
      <category term="Android 笔记" scheme="http://wjploop.github.io/tags/Android-%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>SparseArray vs HashMap</title>
    <link href="http://wjploop.github.io/2021/07/07/SparseArray-vs-HashMap/"/>
    <id>http://wjploop.github.io/2021/07/07/SparseArray-vs-HashMap/</id>
    <published>2021-07-07T08:18:29.000Z</published>
    <updated>2022-10-11T08:59:56.193Z</updated>
    
    <content type="html"><![CDATA[<p>给自己一个自认为熟悉的感念，表达出来能得到多少分？</p><p>是的，我认为我对 <code>SparseArray</code> 熟悉，可当我要表述这个数据结构时，我该怎么表达呢？</p><h3 id="首先，描述使用范围，对比HashMap"><a href="#首先，描述使用范围，对比HashMap" class="headerlink" title="首先，描述使用范围，对比HashMap"></a>首先，描述使用范围，对比HashMap</h3><p><code>SparseArray</code> 适用于key-value结构的数据，相比于Java基础库提供的通用的 <code>HashMap</code>，<code>SparseArray</code> 限制了key只能为int类型，可以节省存储空间，在数据范围小时存取效率优于 <code>HashMap</code>。</p><p>为何省空间？</p><p>其key直接使用了基本类型int，不同于HashMap存储时经过装箱使用Integer，而且，当确定value也是一种基本类型时，可以使用对相应得SparseXXXArray，比如对于 &lt;Integer,Integer&gt;，可以使用 <code>SparseIntArray</code>，这样value也可以存储在基本类型数组，而基本类型所占内存是远小于对象的。</p><p><code>SparseArray</code>内部有两个数组，<code>mKeys</code>, <code>mValues</code>, key直接升序放置在 <code>mKeys</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></pre></td><td class="code"><pre><span class="line">key -&gt; binarySearch -&gt; index </span><br><span class="line">    put key in mKeys[index]</span><br><span class="line">    put value in mValues[index]</span><br></pre></td></tr></table></figure><p>将key转换成index是一次二分查找得过程，其要求key在数组中有序的。</p><p>而 HashMap 其内部可以看作有一个 <code>tables</code>，元素类型是一个Entry&lt;Key,Value&gt;，其插入过程：</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">key -&gt; hash -&gt; index</span><br><span class="line">     h = key.hashCode()</span><br><span class="line">     hash = h ^ ( h &gt;&gt;&gt; 16) </span><br><span class="line">     ensure hash &lt; n ,(n = table.length)</span><br><span class="line">     index = hash &amp; (n - 1)   // 依赖n为2的k次方</span><br></pre></td></tr></table></figure><p>生成hash时，将高16位与低位异或，高16位相当于取非了，从而把高位的信息分配到低位上，这样做的原因是，最后我们可以利用到的高位的信息。<br>为何？假设 n = table.length &lt;= pow(2,16)，那么在转移成index时，我们仅仅利用到了hash的低16位。</p><p>生后hash后，要确保hash值能在范围n里面，其思路是指截取n包括的低比特位，大于n的比特位都丢掉的，这样便得到所需的index。</p><p>使用index时，理想情况下，table[index]没有元素，即无hash冲突。<br>冲突时，转为链表，其实存储时，tables存储的就是链表节点，甚至为树节点。<br>存在类存在这样的继承关系，Entry &gt; Node &gt; TreeNode。    </p><p>两者都存在从 key 到 index的过程，理论上，可以看到查找时间复杂度，前者为 O(logN)，后者为 O(1)，似乎后者更胜一筹。 但应了解到，这里说的时间复杂度是简化分析后的结果，忽略了复杂度前面的常量，前者常量可能是1，后者可能是50，当数据量N比较小时，假设N=20，<code>1 * log2(20) &lt; 50 * 1</code>实际上是前者更快一点的。这一点，做题时也有所体会，有时暴力比最优解更快，原因是用例N太小了。 </p><h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><h4 id="结构"><a href="#结构" class="headerlink" title="结构"></a>结构</h4><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></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">SparseArray</span>&lt;<span class="title">E</span>&gt; <span class="keyword">implements</span> <span class="title">Cloneable</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 删除时用于标记某坑位，避免每次删除时都要挪动元素</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Object DELETED = <span class="keyword">new</span> Object();</span><br><span class="line">    <span class="comment">// 是否有可能清理DELETED垃圾</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">boolean</span> mGarbage = <span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 关键两个数组</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span>[] mKeys;</span><br><span class="line">    <span class="keyword">private</span> Object[] mValues;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> mSize;</span><br></pre></td></tr></table></figure><h4 id="put"><a href="#put" class="headerlink" title="put"></a>put</h4><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">put</span><span class="params">(<span class="keyword">int</span> key, E value)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 寻找到这个key应该放在哪？</span></span><br><span class="line">    <span class="comment">// 这个二分查找，特别之处在于当找不到时，返回该插入下标的取非（不是相反数吧，取反+1才是，不纠结）</span></span><br><span class="line">    <span class="comment">// 取非必然为负数，因为首位0改为1了,这样用负数表示不能存在该key，而且还把该插入的下标变相地保存了下来</span></span><br><span class="line">    <span class="comment">// 是我孤陋寡闻了</span></span><br><span class="line">    <span class="keyword">int</span> i = ContainerHelpers.binarySearch(mKeys, mSize, key);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (i &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 存在已放置的 key 相同，直接替换（并没有HashMap的替换与否的策略）</span></span><br><span class="line">        mValues[i] = value;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 转为该插入下的下标</span></span><br><span class="line">        i = ~i;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 发现居然那个坑是无用的，直接替换，免除数组插入的困扰</span></span><br><span class="line">        <span class="keyword">if</span> (i &lt; mSize &amp;&amp; mValues[i] == DELETED) &#123;</span><br><span class="line">            mKeys[i] = key;</span><br><span class="line">            mValues[i] = value;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 坑位使用完了，且可能存在垃圾，尝试回收下</span></span><br><span class="line">        <span class="keyword">if</span> (mGarbage &amp;&amp; mSize &gt;= mKeys.length) &#123;</span><br><span class="line">            <span class="comment">// 思路就是移除mValues数组中DELETED无效元素，有效元素往前移</span></span><br><span class="line">            <span class="comment">// JVM老年代垃圾清除算法思路（更追求省内存，碎片整体）</span></span><br><span class="line">            gc();</span><br><span class="line"></span><br><span class="line">            <span class="comment">// Search again because indices may have changed.</span></span><br><span class="line">            i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 将key插入到mKeys的i位置上</span></span><br><span class="line">        <span class="comment">// mSize + 1 == mKeys.length 时，扩容 currentSize &lt;= 4 ? 8 : currentSize * 2，创建新的数组复制原来的元素</span></span><br><span class="line">        <span class="comment">// 即使不需要扩容，也需要移动 i 位置之后元素后移一位</span></span><br><span class="line">        mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);</span><br><span class="line">        mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);</span><br><span class="line">        mSize++;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="查询-无需整理"><a href="#查询-无需整理" class="headerlink" title="查询,无需整理"></a>查询,无需整理</h4><p>通过key来查询，不需要整理</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">get</span><span class="params">(<span class="keyword">int</span> key)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> get(key, <span class="keyword">null</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">get</span><span class="params">(<span class="keyword">int</span> key, E valueIfKeyNotFound)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> i = ContainerHelpers.binarySearch(mKeys, mSize, key);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 利用标记，我们确认某个key不存在，或已经被删除</span></span><br><span class="line">    <span class="keyword">if</span> (i &lt; <span class="number">0</span> || mValues[i] == DELETED) &#123;</span><br><span class="line">        <span class="keyword">return</span> valueIfKeyNotFound;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> (E) mValues[i];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="查询，需整理"><a href="#查询，需整理" class="headerlink" title="查询，需整理"></a>查询，需整理</h4><p>发现依赖了index的查询需要gc,当然还包括插入时也得gc</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">public int size() &#123;</span><br><span class="line">    if (mGarbage) &#123;</span><br><span class="line">        gc();</span><br><span class="line">    &#125;</span><br><span class="line">    return mSize;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">public int keyAt(int index) &#123;</span><br><span class="line">    if (mGarbage) &#123;</span><br><span class="line">        gc();</span><br><span class="line">    &#125;</span><br><span class="line">    return mKeys[index];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="remove"><a href="#remove" class="headerlink" title="remove"></a>remove</h4><p>可以欣赏的一点就是，频繁删除元素后，不会马上整理Values数组，而时延后到查询/插入时才会整理，整理就是清除无用的DELETED坑位</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></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">remove</span><span class="params">(<span class="keyword">int</span> key)</span> </span>&#123;</span><br><span class="line">    delete(key);</span><br><span class="line">&#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">delete</span><span class="params">(<span class="keyword">int</span> key)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> i = ContainerHelpers.binarySearch(mKeys, mSize, key);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (i &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (mValues[i] != DELETED) &#123;</span><br><span class="line">            mValues[i] = DELETED;</span><br><span class="line">            mGarbage = <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">public</span> E <span class="title">removeReturnOld</span><span class="params">(<span class="keyword">int</span> key)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> i = ContainerHelpers.binarySearch(mKeys, mSize, key);</span><br><span class="line">    <span class="keyword">if</span> (i &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (mValues[i] != DELETED) &#123;</span><br><span class="line">            <span class="keyword">final</span> E old = (E) mValues[i];</span><br><span class="line">            mValues[i] = DELETED;</span><br><span class="line">            mGarbage = <span class="keyword">true</span>;</span><br><span class="line">            <span class="keyword">return</span> old;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h3><p>另外，SparseXXXMap都要求Key必须为int，若是key不是int，可以用ArrayMap，大略看了下，相似度挺高的，大致是将key转为hash作为key来排序。</p><p>好了，发现自己表达起来确实很着急~对比大佬的<a href="https://juejin.cn/post/6844903961963528199#heading-10" target="_blank" rel="noopener">博文</a></p><p>别人家写的，虽然自己也能感受到的，但表达出来还是很困难。</p><p>引用大佬总结的三个关键字，<em>双数组，二分查找，DELETED标记</em>。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;给自己一个自认为熟悉的感念，表达出来能得到多少分？&lt;/p&gt;
&lt;p&gt;是的，我认为我对 &lt;code&gt;SparseArray&lt;/code&gt; 熟悉，可当我要表述这个数据结构时，我该怎么表达呢？&lt;/p&gt;
&lt;h3 id=&quot;首先，描述使用范围，对比HashMap&quot;&gt;&lt;a href=&quot;#首
      
    
    </summary>
    
    
      <category term="Android" scheme="http://wjploop.github.io/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>关于并发</title>
    <link href="http://wjploop.github.io/2021/06/19/%E5%85%B3%E4%BA%8E%E5%B9%B6%E5%8F%91/"/>
    <id>http://wjploop.github.io/2021/06/19/关于并发/</id>
    <published>2021-06-19T07:50:13.000Z</published>
    <updated>2021-08-28T08:57:57.921Z</updated>
    
    <content type="html"><![CDATA[<h3 id="区别与并行"><a href="#区别与并行" class="headerlink" title="区别与并行"></a>区别与并行</h3><blockquote><p>感觉了解其区分，描述起来好难</p></blockquote><h4 id="他人的回答："><a href="#他人的回答：" class="headerlink" title="他人的回答："></a>他人的回答：</h4><p> 并发（Concurrency）是说进程B的开始时间是在进程A的开始时间与结束时间之间，我们就说A和B是并发的。</p><p> 并行（Parallel Execution）是并发的真子集，指同一时间两个进程运行在不同的机器上或者同一个机器不同的核心上。</p><h4 id="自己的话"><a href="#自己的话" class="headerlink" title="自己的话"></a>自己的话</h4><p>并行是同一个时刻有多个任务被执行，至少得有两个CPU。</p><p>并发不要求同一个刻被运行，两个任务可以交替执行，或是同时运行，即并发包括了并行的情况。</p><h3 id="工作中理解到并发"><a href="#工作中理解到并发" class="headerlink" title="工作中理解到并发"></a>工作中理解到并发</h3><p>在Android中什么时候有多个任务并发运行呢？  </p><p>多个任务并发运行，即可以理解为多个线程的情况。容易想到的是JVM中存在一个用于垃圾回收守护线程和主线程，守护线程一直在监控着资源的使用情况，</p><p>进行耗时操作，比如网络请求、解析xml文件，创建一个子线程来处理。</p><p>子线程处理完后，如何将结果通知给主线程呢？</p><p>比如 Main 启动一个 A线程执行任务，那么计算完如何通知A线程呢？</p><p>想到的是创建一个Callback，创建任务传递A线程，那么在A线程计算得到结果后，调用callback？</p><p>若是在A线程中直接调用callback，那么该方法实在A线程中运行的，而我们想要该callback在Main线程中执行呢？</p><p>想到这，此刻我有点惊讶我没有认真过这个问题~ </p><p>我们假设一种简单情况，Main线程在启动A线程后，阻塞等待，等待A线程返回结果后再执行后续的处理, 那么可以使用 <code>join()</code>方法等待A线程计算完成，代码如下。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义一个公共变量，在A线程中修改，在主线程中展示，这里使用volatile使其修改对主线程可见</span></span><br><span class="line"><span class="comment">// 使用数组达到传址的效果</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">int</span>[] result = <span class="keyword">new</span> <span class="keyword">int</span>[]&#123;-<span class="number">1</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        Thread tA = <span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">        result[<span class="number">0</span>] = <span class="number">100</span>;</span><br><span class="line">        System.out.println(<span class="string">"a:"</span> + result[<span class="number">0</span>]);</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        tA.start();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 主线程等待a线程执行完</span></span><br><span class="line">        tA.join();</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">"main:"</span> + result[<span class="number">0</span>]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>看了 <code>join()</code> ，其实现还是用了 wait/notify机制，直接使用wait/notify带来实现上述需求</p><p>obj.wait() 将该线程进入等待状态，知道其它线程调用obj的notify或被当前线程被中断<br>其本质是将本线程加入到<code>obj</code>的对象头中的等待集合中，并且本线程不再作为该对象的owner of the object’s monitor,<br>本线程不再拥有该对象的拥有权，注意是拥有该对象的所有权才能调用该方法的  </p><p>obj.notify()，也是得到了该对象的所有权后才能调用notify(),故需要先获取锁再操作。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">int</span>[] result = <span class="keyword">new</span> <span class="keyword">int</span>[]&#123;-<span class="number">1</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用一个对象作为锁</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> Object lock = <span class="keyword">new</span> Object();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line"></span><br><span class="line">    Thread tA = <span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">        result[<span class="number">0</span>] = <span class="number">100</span>;</span><br><span class="line">        System.out.println(<span class="string">"a:"</span> + result[<span class="number">0</span>]);</span><br><span class="line">        <span class="keyword">synchronized</span> (lock) &#123;   <span class="comment">// 获取到锁</span></span><br><span class="line">            <span class="comment">// 唤醒其他等待在该lock的一个线程</span></span><br><span class="line">            <span class="comment">// 注意只是唤醒,其他线程还是等到本线程释放锁才能获取继续运行</span></span><br><span class="line">            lock.notify();</span><br><span class="line">            <span class="comment">// 假设这里 sleep(3000)，线程A还是会因为持有该锁，故，主线程得等到sync结束后才能继续运行</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    tA.start();</span><br><span class="line">    <span class="keyword">synchronized</span> (lock) &#123;</span><br><span class="line">        lock.wait();</span><br><span class="line">    &#125;</span><br><span class="line">    System.out.println(<span class="string">"main:"</span> + result[<span class="number">0</span>]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以上，主线程阻塞运行，等待子线程结束后才继续运行，而，若是想再开启子任务后，想要主线程继续运行呢？</p><p>这个样的需求，其实就是Handle处理的场景，那我们不依赖Android如何实现呢？</p><h3 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h3><p>维护一个任务队列 queue，主线程开启一个死循环，不断取出队列中的任务来执行；<br>当我们想在主线程开启一个子线程来计算时，</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;区别与并行&quot;&gt;&lt;a href=&quot;#区别与并行&quot; class=&quot;headerlink&quot; title=&quot;区别与并行&quot;&gt;&lt;/a&gt;区别与并行&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;感觉了解其区分，描述起来好难&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;他人的回
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>关于JVM</title>
    <link href="http://wjploop.github.io/2021/06/17/%E5%85%B3%E4%BA%8EJVM/"/>
    <id>http://wjploop.github.io/2021/06/17/关于JVM/</id>
    <published>2021-06-16T16:26:33.000Z</published>
    <updated>2021-08-28T08:57:51.528Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p> 自己能说多少？</p></blockquote><p>用于执行Java字节码指令的虚拟机，相对操作系统来说，它是一个进程，一般的虚拟机都是用C/C++来写的，在不同的系统中，它的实现是不同的，作为Java程序和操作系统的中间层，通过它实现了同一套Java程序可以在不同程序中运行。</p><p>尽管被称之为Java虚拟机，但是它可以运行Kotlin、Scala语言编写的程序，是因为对于JVM来说，它能运行的是Class字节码，通过不同语言的编译器，这些语言代码都会编译成Class文件。</p><h3 id="对比Dalvik虚拟机、ART"><a href="#对比Dalvik虚拟机、ART" class="headerlink" title="对比Dalvik虚拟机、ART"></a>对比Dalvik虚拟机、ART</h3><p>首先，需要了解的是，在Android系统中，并不是直接用JVM虚拟机，早期用的Dalvik虚拟机，后面用的是ART，不过，后两者都是执行是dex文件，而非JVM的Class文件。</p><p>而class到dex的转换是在apk打包过程中进行的，dex相比于class，其重要的一个区别是，使得字节码占用的存储变小了，主要原因是在dex中常量池是复用的，而在class中，每个class文件拥有一个常量池，导致很多相同常量在class文件之间不能复用，而在字节码中，常量池占比一般是最大的。</p><p>而无论是什么字节码，都会有两部分构成，数据、指令，而常量池属于数据，常量池包括类、方法、的全限名。比如<code>Lang\java\Object</code>,还包括一些字面量，初略  的看，代码中所有的字符都是存在常量池中的，比如类名、类方法，类成员，故这些信息占的存储空间是很大的。  </p><p>对于Class字节码，其构成大致包括<br>魔数，用来标志该文件是什么文件的，Class文件的魔术是对应的16进制的CAFEBABE,其实只是一个标志而已<br>支持的版本信息，表示该Class是Java什么版本编译出来的，当前JVM会检验能不能支持运行该版本的Class文件。<br>常量池<br>方法表  </p><h3 id="凭什么dex能共用常量池，而class一个文件一个常量池？"><a href="#凭什么dex能共用常量池，而class一个文件一个常量池？" class="headerlink" title="凭什么dex能共用常量池，而class一个文件一个常量池？"></a>凭什么dex能共用常量池，而class一个文件一个常量池？</h3><p>当看到经过dex化后可以减少存储空间的占有，咋一看感觉dex字节码更胜一筹，难道真的如此吗？</p><p>个人认为，诸如类似的优化，其思想都是依据使用场景变化后调整了策略，并无高低之分。</p><p>class设计成单个文件自带一个常量池，是因为出于class可以单个文件独立加载到JVM来运行的需要，场景是可以网络下载了一个class便可以直接运行。而dex将原本多个class的常量池复用，那么就不再适用于网络加载一个class就能运行场景，得加载原来多个class得信息量，也就是加载一个dex文件才能运行。    </p><p>可以说，若是都是网络加载字节码来运行，那么一个dex包大小比class大得多。</p><p>另外，既然可以打包class，Java体系中使用了jar来打包，不知道是否相关得压缩信息量得策略。</p><p>而，Android中网络加载dex来运行得案例也是有的，比如热修复技术中thinker，就是下载一个补丁dex文件。</p><h3 id="栈式虚拟机、寄存器式虚拟机对比？"><a href="#栈式虚拟机、寄存器式虚拟机对比？" class="headerlink" title="栈式虚拟机、寄存器式虚拟机对比？"></a>栈式虚拟机、寄存器式虚拟机对比？</h3><p>JVM是栈式的，而Dalvik是寄存器式。</p><p>在于指令不同，首先，指令由操作码和操作数组成，操作码表示要干什么，操作数表述对什么数值进行操作；<br>而两者的最大区别应该是操作数的寻址方式不同，栈式的指令的寻址地址永远是栈顶的数，而寄存器式指令，指令本身包括了操作数所在的位置，比如在某个寄存器，前者寻址是隐式的，而后者是显式的。  </p><p>比如一个加法指令，栈式指令的执行过程是，先后弹出栈顶两数相加，后把结果推入栈顶。<br>而寄存器式指令，则可以是由三地址组成，即 <code>iadd dest src1 src2</code>，这样，要分配三个寄存器，首先要把操作数读取到两个源寄存器中，经过CPU计算后，将结果存放在目标寄存器中。    </p><p>这样，利用的栈的数据结构，操作地址都是隐式的，可以让指令更简练，即指令集更小。  </p><p>而表达更简练的后果是，同一个操作，需要指令更多，而CPU执行指令的过程是不断地<code>取指令</code>和<code>执行指令</code>，而当时间都耗费在取指令上时，效率就降低了。</p><h3 id="关于Java字节码指令，所知多少？"><a href="#关于Java字节码指令，所知多少？" class="headerlink" title="关于Java字节码指令，所知多少？"></a>关于Java字节码指令，所知多少？</h3><p>除了上面所提及的栈式指令整体以外，从具体的一些指令举例。<br>比如一个加载指令，为什么要区分 sload, iload, dload, lload, 即区分操作数类型。<br>指令本身的信息就得包括类型，因为这告知了CPU加载一个数，该从内存起点偏移多长，比如short类型只包括两个字节，int包括4个字节等等。</p><h3 id="关于JIT、AOT"><a href="#关于JIT、AOT" class="headerlink" title="关于JIT、AOT"></a>关于JIT、AOT</h3><p>虚拟机中的指令，最终指令还是要转换成本地机器码，即CPU能够识别的指令。    </p><p>JIT是 just in time, 即执行过程中，虚拟机在运行时将字节码指令转换成本地机器指令。<br>AOT是 ahead of time,在运行之前就将字节码转换成机器码。  </p><p>两者的区别可以理解为懒汉式和饿汉式，也可以用我们考量空间和时间区别，或时间消耗的不同。JIT可以在加载部分Class文件后便执行，可以快速的看到执行效果，AOT则是选择在APK安装中，将字节码全部转换成机器码，导致安装比较久，而且安装后占用存储空间大，这在android 7.0中体现。</p><p>而在7.0之前，全部只用JIT，导致运行性能不够高，毕竟运行时还要将时间消耗在转换字节码上。  </p><p>而在8.0之后，ART,即Android Runtime，在这两者做了平衡，而是在运行过程中，才将字节码转换，并且将转换后的字节码保存下来，而且是计算了常常使用到的字节码才做转换，保存在optimized目录下。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt; 自己能说多少？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;用于执行Java字节码指令的虚拟机，相对操作系统来说，它是一个进程，一般的虚拟机都是用C/C++
来写的，在不同的系统中，它的实现是不同的，作为Java程序和操作系统的中间层，通过它实现了
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>关于线程</title>
    <link href="http://wjploop.github.io/2021/06/11/%E5%85%B3%E4%BA%8E%E7%BA%BF%E7%A8%8B/"/>
    <id>http://wjploop.github.io/2021/06/11/关于线程/</id>
    <published>2021-06-11T13:59:45.000Z</published>
    <updated>2021-08-28T08:57:42.495Z</updated>
    
    <content type="html"><![CDATA[<h2 id="关于线程，自己能说多少呢？"><a href="#关于线程，自己能说多少呢？" class="headerlink" title="关于线程，自己能说多少呢？"></a>关于线程，自己能说多少呢？</h2><h3 id="线程是什么？"><a href="#线程是什么？" class="headerlink" title="线程是什么？"></a>线程是什么？</h3><p>线程是作为CPU调度的最小单位</p><p>表现在一个线程对应了一个程序计数器，程序计算器记录着CPU执行到了哪个指令了</p><p>一个段代码块，经过编译后，对应了一串指令，CPU执行过程就是取指令和执行指令，过程中总要记录下一个该取哪个指令，这个指令的下标就是放在PC中。</p><p>对于普通的指令，执行完后PC加一，表示取下一条指令来执行<br>对于跳转指令，执行该指令的实质就是给PC赋值，这样，在取指令的时，就可以取到对应的指令，最终实现指令跳转，而对于CPU来说，永远是取的PC中的指令来执行，可以说，CPU对于是否跳转是无感知的。</p><p>一段代码块想要被执行，就必须以线程的形式交由给虚拟机，这样才有机会获得CPU执行的可能。</p><p>以CPU视角来看，只认识线程，故，只有将代码逻辑包裹在线程中才可得以执行。</p><h3 id="对比进程？"><a href="#对比进程？" class="headerlink" title="对比进程？"></a>对比进程？</h3><p>进程是包含线程的，一个进程可以包含多个线程。进程持有了一段内存空间，用来分配给线程使用，而我们指的堆，就是在进程内存空间中的，供不同线程使用。另外，系统中的一些权限也是以进程为单位分配的。</p><h3 id="线程的状态"><a href="#线程的状态" class="headerlink" title="线程的状态"></a>线程的状态</h3><p>线程的状态有<br>刚创建好、可运行、阻塞、等待、超时等待、终止   </p><p>其中，可运行状态的是对于虚拟机来说的，为什么没有区分为在ready和running，对于操作系统中的线程，是区分这两个状态的，而对于JVM来说，却把这两个状态都归为runnable状态，表示虚拟机不关注线程是否正在被运行，JVM对于操作系统来说，它是将一个线程交付给操作系统运行的，把JVM中的线程映射到操作系统中的线程。</p><p>一个线程中在执行中 <code>serverSocket.accept()</code>方法，被称为阻塞运行，这时候检查该线程的状态，发现它是 <code>Runnable</code>，对于想JVM来说它就是在运行的，但在操作系统中，它就是在等待状态，它不会占据CPU。</p><p>对于Runnable，虚拟机认为这是可运行的线程，由操作系统调度运行，可能在等待某个资源，比如CPU、或IO</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;关于线程，自己能说多少呢？&quot;&gt;&lt;a href=&quot;#关于线程，自己能说多少呢？&quot; class=&quot;headerlink&quot; title=&quot;关于线程，自己能说多少呢？&quot;&gt;&lt;/a&gt;关于线程，自己能说多少呢？&lt;/h2&gt;&lt;h3 id=&quot;线程是什么？&quot;&gt;&lt;a href=&quot;#线程是
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>LiveData</title>
    <link href="http://wjploop.github.io/2021/05/12/LiveData/"/>
    <id>http://wjploop.github.io/2021/05/12/LiveData/</id>
    <published>2021-05-12T07:08:10.000Z</published>
    <updated>2022-09-28T06:26:36.627Z</updated>
    
    <content type="html"><![CDATA[<h3 id="LiveData"><a href="#LiveData" class="headerlink" title="LiveData"></a>LiveData</h3><p>LiveData作为一个可以观察的数据源，其目标也是实现一个消费者模式的模型，不过，它的特别之处在于其消费是具有生命周期的。</p><p>一个传统的消费者模式，就是有一个数据源,和一堆消费者，数据源维护了一个消费者集合，通过<code>订阅</code>将消费者添加到集合中，<code>取消订阅</code>就是将其从集合中删除，当数据变化时，通知集合中的消费者。</p><p>其作用是为了解耦了事件发生、和处理事件的逻辑，比如可以任意添加或删除对一个事件的处理。</p><p>传统的消费者模式在UI中通常存在一个问题，内存泄漏。消费者（Fragment, Activity）一般具有自己的生命周期，当其需要重建时，因为数据源持有它的引用，导致不能及时释放。故，一些数据层持有View层的模型，都需要手动释放View，即<code>取消订阅</code>。</p><p>而 LiveData 内部帮助我们解决了这个问题，通过将observer 绑定在 <code>lifecycleOwner</code>上，使得注册可以感应消费者的生命周期，当其内部生命周期到 <code>destroy</code>状态时便取消订阅。</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">LiveData.observe(lifecycleOwner, observer)</span><br></pre></td></tr></table></figure><p>除了感应生命周期外，LiveData 也提供了一些额外的特性：</p><p>作为数据源，也想知道自己一直分发数据是否有意义？即，都没有消费者监听数据，则无意义。有一个消费者监听，则有意义。</p><p>数据源可以被 <code>active</code> 或 <code>inActive</code>，使得在没有消费者监听时，可以及时停止分发数据。</p><p>注意，对于数据关心一个不是Fragment，而Fragment中的View，因而我们一般用viewLifecycleOwner而非Fragment本身。</p><p>因为 onStart  &gt; onResume &gt; 跳转到其他页面 &gt; onDestryView &gt; 返回重新执行 onCreateView()</p><p>跳转到其他页面，Fragment不会被销毁，只是销毁其中的View，当重新创建View后，view不能马上更新到LiveData的状态。故而，我们使用 view的生命周期，在onCreatedView后，重新将LiveData的数据重新刷新到view上。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;LiveData&quot;&gt;&lt;a href=&quot;#LiveData&quot; class=&quot;headerlink&quot; title=&quot;LiveData&quot;&gt;&lt;/a&gt;LiveData&lt;/h3&gt;&lt;p&gt;LiveData作为一个可以观察的数据源，其目标也是实现一个消费者模式的模型，不过，它的特别
      
    
    </summary>
    
    
      <category term="Android" scheme="http://wjploop.github.io/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>Android-Transition</title>
    <link href="http://wjploop.github.io/2021/04/26/Android-Transition/"/>
    <id>http://wjploop.github.io/2021/04/26/Android-Transition/</id>
    <published>2021-04-26T03:47:36.000Z</published>
    <updated>2021-06-20T07:23:07.721Z</updated>
    
    <content type="html"><![CDATA[<p>对Android Transition好奇，记录追踪代码</p><h2 id="Transition是什么？"><a href="#Transition是什么？" class="headerlink" title="Transition是什么？"></a>Transition是什么？</h2><img src="/images/transitions.gif" width="240" height="200"><p>如图，关注左上角的图块</p><p>可以看到，它先改变颜色，后平移到右下角，平移过程中大小也在变化，整体上看就是三个Animator的组合完成；现在我们看到使用Transition如何实现的</p><figure class="highlight kotlin"><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"></span><br><span class="line"><span class="keyword">val</span> transition = TransitionInflater.from(<span class="keyword">this</span>).inflateTransition(R.transition.custom_transistion)</span><br><span class="line">root.setOnClickListener &#123;</span><br><span class="line">    <span class="comment">// 每一次点击，往返切换start、end 状态</span></span><br><span class="line">    TransitionManager.beginDelayedTransition(root, transition)</span><br><span class="line">    end = !end</span><br><span class="line">    toggleView()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 区分当前状态，start or end</span></span><br><span class="line"><span class="keyword">var</span> statusEnd = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">toggleView</span><span class="params">()</span></span> &#123;</span><br><span class="line">    container.children.forEach &#123;</span><br><span class="line">        it.visibility = <span class="keyword">if</span> (statusEnd) View.INVISIBLE <span class="keyword">else</span> View.VISIBLE</span><br><span class="line">    &#125;</span><br><span class="line">    view_target.layoutParams = (view_target.layoutParams <span class="keyword">as</span> FrameLayout.LayoutParams).apply &#123;</span><br><span class="line">        gravity = <span class="keyword">if</span> (statusEnd) (Gravity.BOTTOM or Gravity.RIGHT) <span class="keyword">else</span> (Gravity.TOP or Gravity.LEFT)</span><br><span class="line">        width = <span class="keyword">if</span> (statusEnd) <span class="number">300</span> <span class="keyword">else</span> <span class="number">100</span></span><br><span class="line">        height = <span class="keyword">if</span> (statusEnd) <span class="number">300</span> <span class="keyword">else</span> <span class="number">100</span></span><br><span class="line">    &#125;</span><br><span class="line">    view_target.setBackgroundColor(<span class="keyword">if</span> (statusEnd) Color.BLUE <span class="keyword">else</span> Color.RED)</span><br><span class="line">    view_target.apply &#123;</span><br><span class="line">        alpha = <span class="keyword">if</span>(statusEnd) <span class="number">1f</span> <span class="keyword">else</span> <span class="number">0.5f</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>整体上看，依据 <code>statusEnd</code> 定义了两个状态 start，end，而依据这个状态，确定View的 visibility，position,size,color的属性。<br>切换状态，并非生硬地切换，而是在切换过程加入针对相关属性的过度动画，我想这就是Android加入这个库的用处吧。  </p><p>一个页面的两种状态，换个角度，也可以把这两种状态是视为两个页面，切换状态就是切换页面了，这里，我们将变化的页面称之为 <code>Scene</code>, 变化的过度动画交给 <code>Transition</code> 处理。</p><h3 id="Scene"><a href="#Scene" class="headerlink" title="Scene"></a>Scene</h3><p>表示一个页面，会绑定到一个实际的View中。作为一个页面，会有进入和弹出两个动作，在这两个动作发生时会执行View中某些property的动画。</p><h3 id="Transition"><a href="#Transition" class="headerlink" title="Transition"></a>Transition</h3><p>切换的涵盖了从一个旧的Scene到新的Scene的过程，包括 old scene exit 和 new scene enter.</p><p>维护一些动画信息，在Scene变化时执行持有的动画。主要做两件事：</p><ol><li>capture property value</li><li>play animations based on change to captured property value</li></ol><h2 id="还是跟着代码看看吧"><a href="#还是跟着代码看看吧" class="headerlink" title="还是跟着代码看看吧"></a>还是跟着代码看看吧</h2><p>先看看上面使用的使用到的 beginDelayedTransition()</p><blockquote><blockquote><p>感觉注释写得很好了</p></blockquote></blockquote><p>方法名为意思为 启动一个延迟的切换， 为什么时延迟的呢？ 这个切换并没有马上执行，因为一个新的scene还没有确定呢。而这个新的scene的创建过程也很有意思，它是依据下一帧与当前帧的“变化“确定的，而且它关注的只有参数中sceneRoot的ViewGroup节点下View的变化哦。本来下一帧要确定好准备绘制的，但是遇到存在Transition未执行，会将其执行玩才真正绘制最终的下一帧。</p><p>换个说法，current frame &gt;&gt; next frame 之间，提取出 change in sceneRoot, 为这些 change 执行参数中的transition,即执行动画。本质上，就是本来要绘制下一帧nextFrame的，发现有transition,则nextFrame要延后，在currentFrame与nextFrame之间插入一系列的frame，而这些frame由transition来确定。</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><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><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">beginDelayedTransition</span><span class="params">(@NonNull <span class="keyword">final</span> ViewGroup sceneRoot, @Nullable Transition transition)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 预备的执行的Transitions集合添加该某个View作为sceneRoot,</span></span><br><span class="line">    <span class="comment">// 限制了view只能能添加一次</span></span><br><span class="line">    <span class="keyword">if</span> (!sPendingTransitions.contains(sceneRoot) &amp;&amp; ViewCompat.isLaidOut(sceneRoot)) &#123;</span><br><span class="line">        sPendingTransitions.add(sceneRoot);</span><br><span class="line">        <span class="keyword">if</span> (transition == <span class="keyword">null</span>) &#123;</span><br><span class="line">            transition = sDefaultTransition;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">final</span> Transition transitionClone = transition.clone();</span><br><span class="line">        <span class="comment">// 停止当前运行的动画，记录当前帧的属性值、给scene机会执行一个可能需要exitAction</span></span><br><span class="line">        sceneChangeSetup(sceneRoot, transitionClone);</span><br><span class="line">        <span class="comment">// 将当前sense设为空，即达到退出的效果</span></span><br><span class="line">        Scene.setCurrentScene(sceneRoot, <span class="keyword">null</span>);</span><br><span class="line">        <span class="comment">// 当scene变化时执行Transition，看看怎么监听sense变化的</span></span><br><span class="line">        sceneChangeRunTransition(sceneRoot, transitionClone);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">sceneChangeRunTransition</span><span class="params">(<span class="keyword">final</span> ViewGroup sceneRoot, <span class="keyword">final</span> Transition transition)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (transition != <span class="keyword">null</span> &amp;&amp; sceneRoot != <span class="keyword">null</span>) &#123;</span><br><span class="line">        MultiListener listener = <span class="keyword">new</span> MultiListener(transition, sceneRoot);</span><br><span class="line">        <span class="comment">// 为View sceneRoot 添加attach、preDraw 状态的监听</span></span><br><span class="line">        sceneRoot.addOnAttachStateChangeListener(listener);</span><br><span class="line">        sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener);</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">    <span class="comment">// 当该View的脱离Window时先执行动画</span></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">onViewDetachedFromWindow</span><span class="params">(View v)</span> </span>&#123;</span><br><span class="line">        removeListeners();</span><br><span class="line"></span><br><span class="line">        sPendingTransitions.remove(mSceneRoot);</span><br><span class="line">        ArrayList&lt;Transition&gt; runningTransitions = getRunningTransitions().get(mSceneRoot);</span><br><span class="line">        <span class="keyword">if</span> (runningTransitions != <span class="keyword">null</span> &amp;&amp; runningTransitions.size() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">for</span> (Transition runningTransition : runningTransitions) &#123;</span><br><span class="line">                runningTransition.resume(mSceneRoot);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        mTransition.clearValues(<span class="keyword">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在此刻，视图树上所有的View都已经测量，放置好位置，只是没有绘制</span></span><br><span class="line">    <span class="comment">// 在这个时间点，用户可以选择是否满意当前的布局，return true绘制当前帧， return false可以取消当前帧的绘制，</span></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">boolean</span> <span class="title">onPreDraw</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        removeListeners();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 注意只是在第一次nextFrame操作，后续动画播放过程中的onPreDraw就不会处理了</span></span><br><span class="line">    <span class="comment">// Don't start the transition if it's no longer pending.</span></span><br><span class="line">        <span class="keyword">if</span> (!sPendingTransitions.remove(mSceneRoot)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Add to running list, handle end to remove it</span></span><br><span class="line">        <span class="keyword">final</span> ArrayMap&lt;ViewGroup, ArrayList&lt;Transition&gt;&gt; runningTransitions =</span><br><span class="line">                getRunningTransitions();</span><br><span class="line">        ArrayList&lt;Transition&gt; currentTransitions = runningTransitions.get(mSceneRoot);</span><br><span class="line">        ArrayList&lt;Transition&gt; previousRunningTransitions = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">if</span> (currentTransitions == <span class="keyword">null</span>) &#123;</span><br><span class="line">            currentTransitions = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line">            runningTransitions.put(mSceneRoot, currentTransitions);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (currentTransitions.size() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            previousRunningTransitions = <span class="keyword">new</span> ArrayList&lt;&gt;(currentTransitions);</span><br><span class="line">        &#125;</span><br><span class="line">        currentTransitions.add(mTransition);</span><br><span class="line">        mTransition.addListener(<span class="keyword">new</span> TransitionListenerAdapter() &#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">onTransitionEnd</span><span class="params">(@NonNull Transition transition)</span> </span>&#123;</span><br><span class="line">                ArrayList&lt;Transition&gt; currentTransitions = runningTransitions.get(mSceneRoot);</span><br><span class="line">                currentTransitions.remove(transition);</span><br><span class="line">                transition.removeListener(<span class="keyword">this</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        mTransition.captureValues(mSceneRoot, <span class="keyword">false</span>);</span><br><span class="line">        <span class="keyword">if</span> (previousRunningTransitions != <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">for</span> (Transition runningTransition : previousRunningTransitions) &#123;</span><br><span class="line">                runningTransition.resume(mSceneRoot);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 播放Transition,其实是 create animators and run </span></span><br><span class="line">        mTransition.playTransition(mSceneRoot);</span><br><span class="line">                <span class="comment">// 适应 对比前后，创建很多animator</span></span><br><span class="line">            &gt;&gt;  createAnimators(sceneRoot, mStartValues, mEndValues, mStartValuesList, mEndValuesList);</span><br><span class="line">            &gt;&gt;  runAnimators();</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="那启动一个Activity是如何执行Transition呢？"><a href="#那启动一个Activity是如何执行Transition呢？" class="headerlink" title="那启动一个Activity是如何执行Transition呢？"></a>那启动一个Activity是如何执行Transition呢？</h2><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></pre></td><td class="code"><pre><span class="line">// options 不为空，默认不传也会获取主题默认的，不过需要当前Window支持 Window.FEATURE_ACTIVITY_TRANSITIONS</span><br><span class="line">startActivityForResult(intent, -1, options);</span><br><span class="line">// 取消输入开始退出</span><br><span class="line">&gt;&gt; cancelInputsAndStartExitTransition</span><br><span class="line">   </span><br><span class="line"></span><br><span class="line">public void startExitOutTransition(Activity activity, Bundle options) &#123;</span><br><span class="line">    mEnterTransitionCoordinator = null;</span><br><span class="line">    if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) ||</span><br><span class="line">            mExitTransitionCoordinators == null) &#123;</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    ActivityOptions activityOptions = new ActivityOptions(options);</span><br><span class="line">    if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) &#123;</span><br><span class="line">        int key = activityOptions.getExitCoordinatorKey();</span><br><span class="line">        int index = mExitTransitionCoordinators.indexOfKey(key);</span><br><span class="line">        if (index &gt;= 0) &#123;</span><br><span class="line">            mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get();</span><br><span class="line">            mExitTransitionCoordinators.removeAt(index);</span><br><span class="line">            if (mCalledExitCoordinator != null) &#123;</span><br><span class="line">                mExitingFrom = mCalledExitCoordinator.getAcceptedNames();</span><br><span class="line">                mExitingTo = mCalledExitCoordinator.getMappedNames();</span><br><span class="line">                mExitingToView = mCalledExitCoordinator.copyMappedViews();</span><br><span class="line">                mCalledExitCoordinator.startExit();</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">public void startExit() &#123;</span><br><span class="line">    if (!mIsExitStarted) &#123;</span><br><span class="line">        backgroundAnimatorComplete();</span><br><span class="line">        mIsExitStarted = true;</span><br><span class="line">        pauseInput();</span><br><span class="line">        ViewGroup decorView = getDecor();</span><br><span class="line">        if (decorView != null) &#123;</span><br><span class="line">           // 禁止 layout乱入，layout也会导致 draw，而我们依赖着 draw前的时间点创建animator</span><br><span class="line">            decorView.suppressLayout(true);</span><br><span class="line">        &#125;</span><br><span class="line">        // 这个将共享元素移动到OverLay层，最高层哦</span><br><span class="line">        moveSharedElementsToOverlay();</span><br><span class="line">        startTransition(new Runnable() &#123;</span><br><span class="line">            @Override</span><br><span class="line">            public void run() &#123;</span><br><span class="line">                if (mActivity != null) &#123;</span><br><span class="line">                    beginTransitions();</span><br><span class="line">                &#125; else &#123;</span><br><span class="line">                    startExitTransition();</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"></span><br><span class="line">private void startExitTransition() &#123;</span><br><span class="line">    Transition transition = getExitTransition();</span><br><span class="line">    ViewGroup decorView = getDecor();</span><br><span class="line">    if (transition != null &amp;&amp; decorView != null &amp;&amp; mTransitioningViews != null) &#123;</span><br><span class="line">        setTransitioningViewsVisiblity(View.VISIBLE, false);</span><br><span class="line">        TransitionManager.beginDelayedTransition(decorView, transition);</span><br><span class="line">        setTransitioningViewsVisiblity(View.INVISIBLE, false);</span><br><span class="line">        // 请求重绘，正好对应上了，onPreDraw</span><br><span class="line">        // 否则没机会执行transition</span><br><span class="line">        decorView.invalidate();</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">        transitionStarted();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;对Android Transition好奇，记录追踪代码&lt;/p&gt;
&lt;h2 id=&quot;Transition是什么？&quot;&gt;&lt;a href=&quot;#Transition是什么？&quot; class=&quot;headerlink&quot; title=&quot;Transition是什么？&quot;&gt;&lt;/a&gt;Transiti
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>协程笔记</title>
    <link href="http://wjploop.github.io/2021/04/15/%E5%8D%8F%E7%A8%8B%E7%AC%94%E8%AE%B0/"/>
    <id>http://wjploop.github.io/2021/04/15/协程笔记/</id>
    <published>2021-04-15T02:40:24.000Z</published>
    <updated>2022-09-14T01:53:41.156Z</updated>
    
    <content type="html"><![CDATA[<p>协程在安卓中，用来处理即时任务，相对于那些延迟任务，使用Worker来处理。</p><h2 id="Job"><a href="#Job" class="headerlink" title="Job"></a>Job</h2><p>协程中的Job描述一个后台进行的任务，可以手动取消，感知自己的作用域；</p><p>Jobs 工作链之间可以构成父子关系，这样，会导致，当父节点取消时，同时会递归地取消子节点。当子节点异常结束时，也会到导致父节点立刻去结束。这些，父子之间的行为可以通过<code>SupervisorJob</code>来解决</p><p>Job实例创建的常见形式如下：</p><ul><li>直接使用 launch的协程构造器，</li><li>使用CompleteJob的工厂方法，当任务结束时会回调complete()方法</li></ul><p>概念上，Job也不产生结果，只有方法调用的副作用，关注结果的Job可以用其子类 <code>Defferd</code>   </p><h3 id="Job-states"><a href="#Job-states" class="headerlink" title="Job states"></a>Job states</h3><p>Job的状态由三个变量构成，isActive, isCompleted, isCanceled, 通过不同组合，可以形成不同的状态，000表示刚创建出来100表示进入激活状态010表示完美结束了代码块001表示正在取消，cancelling011表示已取消,cancelled这里，通常创建后就会进入active状态，不过也可以创建时指定Coroutine.Lazy，延迟激活，通过start/join方法启动一个激活的协程会一直运行，下一个状态可能是代码执行完毕，complete，或执行失败，抛异常，或被取消</p><p>抛异常会导致进入cancelling状态，Job可以调用cancel方法立刻进入cancelling状态，不过若是作为父节点，也要得子节点完成。</p><p>完成一个协议，也可以调用它的complete方法，也会等到子协程完成再完成。注意到，completing状态只是内部状态，并不对外公开，再完成子协程前，在外面观察者看到，当前协程还是active状态。</p><h3 id="Cancellation-cause"><a href="#Cancellation-cause" class="headerlink" title="Cancellation cause"></a>Cancellation cause</h3><p>协程可以当出现异常时进入canceled状态。当子节点出现异常取消时，也会导致父节点取消。实现方式上，是通过抛异常来让让协程进入到cancelled状态的。正常取消和异常取消区别于抛出的异常类型，正常取消抛出 <code>CancellationException</code>,其他类型异常则视为属于异常退出了。有趣的是，这个异常是Java基础库一致的。</p><h3 id="线程安全保证"><a href="#线程安全保证" class="headerlink" title="线程安全保证"></a>线程安全保证</h3><h3 id="实现类都是不稳定"><a href="#实现类都是不稳定" class="headerlink" title="实现类都是不稳定"></a>实现类都是不稳定</h3><p>指的是 JopSupport吧，</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;协程在安卓中，用来处理即时任务，相对于那些延迟任务，使用Worker来处理。&lt;/p&gt;
&lt;h2 id=&quot;Job&quot;&gt;&lt;a href=&quot;#Job&quot; class=&quot;headerlink&quot; title=&quot;Job&quot;&gt;&lt;/a&gt;Job&lt;/h2&gt;&lt;p&gt;协程中的Job描述一个后台进行的任务，可
      
    
    </summary>
    
    
      <category term="Kotin" scheme="http://wjploop.github.io/tags/Kotin/"/>
    
  </entry>
  
  <entry>
    <title>刷题中对一些算法思想的理解</title>
    <link href="http://wjploop.github.io/2021/03/31/%E5%88%B7%E9%A2%98%E4%B8%AD%E5%AF%B9%E4%B8%80%E4%BA%9B%E7%AE%97%E6%B3%95%E6%80%9D%E6%83%B3%E7%9A%84%E7%90%86%E8%A7%A3/"/>
    <id>http://wjploop.github.io/2021/03/31/刷题中对一些算法思想的理解/</id>
    <published>2021-03-31T13:34:30.000Z</published>
    <updated>2021-06-16T18:30:47.210Z</updated>
    
    <content type="html"><![CDATA[<p>刷题过程中，对一些大家熟知的概念有了自己的体会，这是一个满足的体验啊。比如，理解一些思路为何能上升为”思想”，如分治思想。</p><p>以排序的快排、归并为例，最开始我们了解它们一般都是先接触它们的思路，   </p><h3 id="快排的思路"><a href="#快排的思路" class="headerlink" title="快排的思路"></a>快排的思路</h3><p>在一个序列中选择一个数为中轴 <code>pivot</code>，将小于等于中轴数的放到左边，大于它的放在右边；<br>不断对左边和右边的子序列作以上操作。</p><h3 id="归并思路"><a href="#归并思路" class="headerlink" title="归并思路"></a>归并思路</h3><p>排序一个序列，先不断的划分为两部分别排序，再将左右两边的有序序列合并；不断对左右两边分别以上操作。</p><p>从归并再反思快排，我们发现，归并比快排要”懒”一些啊~    </p><p>快排一次划分将序列分成了三部分，且将三者排好序，而归并一次划分仅将序列分为两部分，两者没有急着排序，而是优哉游哉将排序放到不可划分之后。  </p><p>快排像一个勤奋或急功近利的年轻人，每次操作，便急着将中轴数放到最终的位置，付出的”比较汗水”很多，每个数会比较n-1次；  </p><p>归并像一个深谋远虑的老者，不急不躁，将比较的操作放到”合并”流程中，使得每次”比较”的结果都能给后面的”比较”助力，为什么这么说呢？<br>比如合并 [1,3,5] 与[2,4,6]，左边的1与右边2比较后，发现1比2小，且右边是递增的有序序列，故1也一定比2之后的数据小，也就是无需再与2之后的数比较，那么至少省了2次比较呀。  </p><p>而且啊，基于快排”排序不稳定”，归并”排序稳定”，它们年轻人、老者的形象似乎更加立体了。  </p><p>在不同的场景下，它们各有千秋，在现实的世界中，无疑还是年轻人干活的场景更多一点呀。  </p><p>在啰嗦完它们不同点之后，我们来找它们的共同点，为何他俩能在众多排序算法中更加惹人注目呢？ 我想，它俩相比与一些暴力蛮干的排序相比，它俩都散发着”分治”智慧。遇到”排列好一堆数”的复杂问题，能寻见其间的共通，将复杂问题化为共通部分的叠加子问题，求解简单的子问题，便能可求得原问题之解。</p><p>具体上，再看上面提及的两个排序思路，都是”分治思想”应用，仅是划分角度的不同。<br>快排，将问题化为，排序一个数和两个子序列简单子问题，排序一个数与两个序列可省力多了，毕竟只要花O(n)；<br>归并，似乎”分为治之”更为明显，遇到问题，若非问题规模为1，即1个数无需排序，否则统统将问题规模减半，令人惊艳的在于，合并的过程中，子问题的答案能够为父问题求解提供帮助，从而在整体求解上起飞。  </p><p>分而治之，基于问题本就由一个个子问题构成的客观事实，拆解问题便是我们认识世界的过程，可以说，分而治之是我们与生俱来解决问题思路。</p><p>另外，若能将问题拆分后，若子问题之间不依赖，并发执行，也是可以提高效率吧。</p><p>前天遇到了一道题，<a href="https://leetcode-cn.com/problems/reverse-bits/" target="_blank" rel="noopener">颠倒二进制</a>, 引出上文~</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;刷题过程中，对一些大家熟知的概念有了自己的体会，这是一个满足的体验啊。比如，理解一些思路为何能上升为”思想”，如分治思想。&lt;/p&gt;
&lt;p&gt;以排序的快排、归并为例，最开始我们了解它们一般都是先接触它们的思路，   &lt;/p&gt;
&lt;h3 id=&quot;快排的思路&quot;&gt;&lt;a href=&quot;#快
      
    
    </summary>
    
    
      <category term="code" scheme="http://wjploop.github.io/tags/code/"/>
    
  </entry>
  
  <entry>
    <title>Fragment Transactions &amp; Activity State Loss (译)</title>
    <link href="http://wjploop.github.io/2021/03/05/Fragment-Transactions-Activity-State-Loss-%E8%AF%91/"/>
    <id>http://wjploop.github.io/2021/03/05/Fragment-Transactions-Activity-State-Loss-译/</id>
    <published>2021-03-05T03:13:31.000Z</published>
    <updated>2021-06-16T18:30:47.210Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p><a href="https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html" target="_blank" rel="noopener">原文</a></p></blockquote><h1 id="Fragment-Transactions-和-Activity-状态丢失"><a href="#Fragment-Transactions-和-Activity-状态丢失" class="headerlink" title="Fragment Transactions 和 Activity 状态丢失"></a>Fragment Transactions 和 Activity 状态丢失</h1><p>自Android3.0后，以下报错信息在StackOverflow困扰众人许久了</p><blockquote><p>java.lang.IllegalStateException: Can not perform this action after onSaveInstanceStateat android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)</p></blockquote><p>本文将要解释该异常为什么会出现，以及何时会出现，随便给出几个建议来处理该问题。</p><h2 id="该异常为什么会发生？"><a href="#该异常为什么会发生？" class="headerlink" title="该异常为什么会发生？"></a>该异常为什么会发生？</h2><p>该异常抛出的原因是，在Activity状态已经保存后仍试图提交一个FragmentTransaction，其导致了一个现象被称之为状态丢失。在探究其中的各种细节之前，我们先来看看<code>onSaveInstanceState()</code>方法调用时发生了什么。正如在我的上一篇文章<a href="https://www.androiddesignpatterns.com/2013/08/binders-death-recipients.html" target="_blank" rel="noopener">Binders &amp; Death Recipients</a> 所说,在Android的运行环境中，App对自己的命运掌控甚少。Android系统在内存不足的情况下可以在杀死任意进程，导致后台Activity在未收到任何通知下被杀掉。为了避免用户察觉的到这种不稳定的问题（切换到后台的App，被杀或不被杀，当切回到前台时表现会不一样；不被杀，数据正常，被杀掉了，数据跟关闭前的不一样），在Activity可能被销毁的情况下，系统给给了它一个机会，能使用<code>onSaveInstanceState()</code>方法先来保存好当前的状态。当App由后台切换到前台时，恢复之前保存的状态，这样，无论App是否被杀与否，用户体验都是一样的。</p><p>当系统回调<code>onSaveInstanceState()</code>方法时，它将一个<code>Bundle</code>对象传给Activity用于保存Dialog,Fragment,Views的状态，在该方法返回后，系统序列化该Bundle数据（通过parcel序列化）通过Binder传给系统服务进程（System Server process）,使其能安全保存着。之后当系统重建创建Activity时，又将该Bundle传回给应用使其恢复原来的状态。</p><p>那为什么该异常会抛出呢？该问题源自于该Bundle数据代表Activity在调用<code>onSaveInstance()</code>这一时刻的快照，这意味着在<code>onSaveInstance()</code>之后调用<code>FragemntTransaction#commit()</code>，这个transaction将不会被保存。而从用户的角度上看，会导致UI的数据混乱。为了保证用户体验，Android只好抛异常来避免状态丢失了（默认数据正确性优先）。</p><h2 id="何时该异常会抛出？"><a href="#何时该异常会抛出？" class="headerlink" title="何时该异常会抛出？"></a>何时该异常会抛出？</h2><p>假如你之前遇到该异常，你可能会注意到该异常抛出的时间点在不同Android版本上会有所不同。比如，你可能注意到在老设备上更容易抛出该异常，或是当使用支持库（android.support.Fragment）而非官方库(android.app.Fragment)时你的应用可能更容易崩溃。这些细小的不一致行为导致很多人认为支持库是存在bug而不能被信任，而这些观点，通常是错误的。</p><p>这些不一致行为，原因是在在Android3.0之后，Activity的生命周期方法发生了一些重大改变。3.0之前，Activity是在paused之后就可以被系统认为是”可杀的”，意味着在<code>onPause()</code>方法之前会保证调用<code>onSaveInstance()</code>。而在3.0之后，Activity是要在<code>onStop()</code>后才会被认为是可杀的，也就是会在<code>stoped</code>之前保证保存状态。</p><p>由于Activity生命周期的变化，支持库需要兼容不同的平台版本。比如，该异常会在<code>onSaveInstanceState()</code>方法后执行<code>commit()</code>便会抛出，以此提示开发者状态丢失了。而<code>onSaveInstanceState()</code>调用时机在3.0之前更早一些，也就导致低版本更容易状态丢失。为了能够在支持不同版本，Android团队做了妥协：允许在低版本上Android中，在<code>onPause()</code>和<code>onStop()</code>之间提交commit会导致异常。支持库在不同版本表现如下表格：</p><table><thead><tr><th></th><th align="center">3.0前</th><th align="center">3.0后</th></tr></thead><tbody><tr><td>在 onPause()前 commit()</td><td align="center">OK</td><td align="center">OK</td></tr><tr><td>在 onPause() 和 onStop() 之间commit()</td><td align="center">状态丢失</td><td align="center">OK</td></tr><tr><td>在 onStop() 之后commit()</td><td align="center">Exception</td><td align="center">Exception</td></tr></tbody></table><h2 id="如何避免该异常"><a href="#如何避免该异常" class="headerlink" title="如何避免该异常"></a>如何避免该异常</h2><p>一旦理解状态丢失是如何发生之后，避免该异常就容易多了。若是你在阅读本文之前就理解了，也希望你可以通过本文知道支持库是如何工作的以及为什么App中避免状态丢失如此重要。若是你在搜索一个快速的解决方案而看到本文，这里有几个建议希望能够对你处理FragmentTransaction有帮助。</p><h3 id="在Activity生命周期提交事务（commit-transaction）时需要谨慎"><a href="#在Activity生命周期提交事务（commit-transaction）时需要谨慎" class="headerlink" title="在Activity生命周期提交事务（commit transaction）时需要谨慎"></a>在Activity生命周期提交事务（commit transaction）时需要谨慎</h3><p>大部分应用只会在onCreate()方法中或是响应用户操作时才会提交事务，这不会出现什么问题。然而，在其它的生命周期方法中提交事务时，事情就会变得复杂了，比如<code>onActivityResult()</code>,<code>onStart()</code>,<code>onResume()</code>. 比如，你不应该在<code>onResume()</code>方法中提交事务，因为有可能此时Activity的状态没有恢复原来保存的状态（restored）,详见<a href="https://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume()" target="_blank" rel="noopener">文档</a>,</p><blockquote><p>文档内容（译者加）：</p><ul><li>Activity#onResume()<br>分发 onResume() 方法给Fragment。注意，为了更好的兼容低版本平台，该activity中attached Fragment并没有进入resumed。这意味着Activity原来保存的状态（若是以前保存有）没有恢复（原来的状态还是在bundle中，而非在当前state中），当前是不允许提交事务修改状态的，你应该在<code>onResumeFragments()</code>方法中提交事务修改。</li></ul></blockquote><p>如果你需要在onCreate()之外的生命周期中提交事务，应该在<code>FragmentActivity#onResumeFragemnts()</code>或<code>Activity#onPostResume()</code>。这两个方法保证原来的状态已经正确的恢复，因此可以避免状态丢失的可能性。（若是想要在<code>Activity#onActivityResult()</code>方法中提交事务，可以参看我的StackOverFlow中的<a href="https://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult" target="_blank" rel="noopener">回答</a> ）</p><h3 id="避免在异步的回调方法中提交事务"><a href="#避免在异步的回调方法中提交事务" class="headerlink" title="避免在异步的回调方法中提交事务"></a>避免在异步的回调方法中提交事务</h3><p>常见的异步回调方法，比如 <code>AsyncTask#onPoastExecute()</code>和<code>LoadManager.LoaderCallback#onLoadFinished()</code>，当在这些方法提交事务时，我们并不知道当前Activity所处的状态。如下展示异常出现的过程。</p><ol><li>Activity中开始执行一个<code>AsyncTask</code></li><li>用户点击Home键，导致Activity的<code>onSaveStateInstance()</code>和<code>onStop()</code>执行</li><li><code>AsyncTask</code>任务完成执行<code>onPostExecutes()</code>,并不意识到Activity已经 stopped</li><li>在<code>onPostExecutes()</code>中提交事务，导致异常抛出</li></ol><p>通常来说，最好不在异步回调中提交事务。谷歌工程师认同这一原则，在Android开发团队的一篇<a href="https://groups.google.com/d/msg/android-developers/dXZZjhRjkMk/QybqCW5ukDwJ" target="_blank" rel="noopener">文章</a> 中，认为在异步回调中执行提交事务会使得界面突然切换，这会造成糟糕的用户体验。若是你的App一定要在异步回调中提交事务，并没有容易的方法来保证提交事务在保存状态前执行，你可能需要使用<code>commitAllowStateLoss()</code>，但这需要自己处理状态能会丢失的情况。(StackOverFlow有两篇帖子可以参考，<a href="https://stackoverflow.com/questions/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused" target="_blank" rel="noopener">帖子1</a><a href="https://stackoverflow.com/questions/7992496/how-to-handle-asynctask-onpostexecute-when-paused-to-avoid-illegalstateexception" target="_blank" rel="noopener">帖子2</a>）</p><h3 id="使用commitAllowStateLoss-应当作为最后选项"><a href="#使用commitAllowStateLoss-应当作为最后选项" class="headerlink" title="使用commitAllowStateLoss 应当作为最后选项"></a>使用commitAllowStateLoss 应当作为最后选项</h3><p><code>commit()</code>和<code>commitAllowStateLoss()</code>两者唯一区别在于后者在状态状态可能丢失时，不会抛异常。通常你也不该使用该方法，因为这意味这要承受状态丢失的可能。最好的解决方案当然是保证commit在保存状态前执行，这保证了良好的用户体验。除非状态丢失无法避免，否则<code>commitAllowStateLoss</code>不该使用。</p><p>希望这些建议能够解决你遇到的问题。若是你仍遇到问题，在StackOverFlow提交问题并在下面评论区留下链接，我会帮忙看看滴。</p><p>总之，感谢你的阅读。若是有问题欢迎评论，别忘了点赞分享~</p><blockquote><p>上次更新于2014-1-8<br>译者翻译于2021-3-5，哭了~，陈年好文啊</p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html&quot; target=&quot;_blank&quot; rel=&quot;noo
      
    
    </summary>
    
    
      <category term="翻译" scheme="http://wjploop.github.io/tags/%E7%BF%BB%E8%AF%91/"/>
    
  </entry>
  
  <entry>
    <title>朴素的思路-俄罗斯套娃</title>
    <link href="http://wjploop.github.io/2021/03/05/%E6%9C%B4%E7%B4%A0%E7%9A%84%E6%80%9D%E8%B7%AF-%E4%BF%84%E7%BD%97%E6%96%AF%E5%A5%97%E5%A8%83/"/>
    <id>http://wjploop.github.io/2021/03/05/朴素的思路-俄罗斯套娃/</id>
    <published>2021-03-05T03:06:52.000Z</published>
    <updated>2022-09-14T01:53:41.156Z</updated>
    
    <content type="html"><![CDATA[<h2 id="题意提取"><a href="#题意提取" class="headerlink" title="题意提取"></a>题意提取</h2><p>信封有两属性，宽（w），高（h）;<br>信封a套入信封b的要求:w[a] &lt; w[b] &amp;&amp; h[a] &lt; h[b]</p><p>求，在一堆信封中，最多能有多少个个信封可以套在一起？</p><h2 id="解题思路"><a href="#解题思路" class="headerlink" title="解题思路"></a>解题思路</h2><h3 id="基于生活经验的朴素思路："><a href="#基于生活经验的朴素思路：" class="headerlink" title="基于生活经验的朴素思路："></a>基于生活经验的朴素思路：</h3><p>既然想要套更多的信封，那么先找最小的信封出来，准没错，找出宽高最小的信封a。</p><p>然后，再选一个 “最贴近a，又比a大” 的信封。重复该步骤，积累的信封个数就是题目所求了。</p><p>写代码时，排序，按宽优先升序排序,这样，a的下一个信封，就是“最贴近a，又比a大”的信封。</p><p>结果发现，像这样（排序后）的用例是没问题的。</p><blockquote><p>[1,1], [2,3], [3,4]</p></blockquote><p>但是，如下的用例就出现问题了 </p><blockquote><p>[1,7], [2,3], [3,4]</p></blockquote><p>按照写法，我们会直接选择，[1,7]，之后的信封都不能继续套入，故结果为1，而更优的选择为选后两个信封，结果为2。</p><p>问题出在哪里了呢？</p><p>我们平白无故的添加一个比较条件，按<code>宽</code>优先排序，比如 [1,7] &lt; [2,3]，我<code>宽</code>小于你，可我<code>高</code>大于你，凭啥我就低你一等呢？</p><p>原来，信封[1,7]，[2,3] 之间并没有所谓的大小关系，故我们朴素思路问题出现在了这里。</p><blockquote><p>感慨，生活中很多时候，自己遇到比较复杂的问题时，会想当然的添加某些条件，使其合理化。比如，为啥自己工作那么久还在小公司颠簸，安慰自己说，进大厂的人毕竟是少数，自己是野鸡大学的嘛。而这问题出现在哪呢？</p></blockquote><p>回归本题，我们添加这个“按宽优先升序”条件后，虽然不能直接用朴素的思路来解决，但似乎有点用到，如，比信封i大的信封一定在其后面了，故基于此继续思考。</p><p>面对排序好的信封 envelopes，如何选择第一封信封呢？如</p><blockquote><p>[1,7], [2,3], [3,4] </p></blockquote><p>我们选择了[2,3]作为第一个信封，因为选择它之后，我们可以再套一个[3,4]，而其它两个信封着都没有这样的“额外好处”，或者说其他两信封给的“额外好处”为0，可以说，我们是根据它们给的“好处值”的大小来做出选择的，我们定义每个信封的好处值<code>goods[i]</code>为，选择它之后最终可以有多少个信封。如以上用例的好处值分别为：</p><blockquote><p>1, 2, 1</p></blockquote><p>那么我们问题所求 ans = maxOf(goods[0], goods[1]… goods[n-1])</p><p>我们试图分别求每个goods[i]值，让人难以理解的一点是，求goods[i]似乎是跟求原问题是类似的…</p><p>发现求goods[i] 依赖后面信封的好处值和大小，而求最后一个信封的好处值是最容易的，必须等于1.</p><p>goods[n-1] = 1 </p><p>goods[k] = 在后面的信封中，选择比envelopes[k]大的集合中选择好处值最大的 good+1</p><blockquote><p>啊，发现描述起来好难~</p></blockquote><p>代码：</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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">maxEnvelopes</span><span class="params">(<span class="keyword">int</span>[][] envelopes)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (envelopes.length == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">int</span> n = envelopes.length;</span><br><span class="line">        <span class="comment">// 将信封按宽优先升序排序</span></span><br><span class="line">        Arrays.sort(envelopes, (o1, o2) -&gt; o1[<span class="number">0</span>] == o2[<span class="number">0</span>] ? Integer.compare(o1[<span class="number">1</span>], o2[<span class="number">1</span>]) : Integer.compare(o1[<span class="number">0</span>], o2[<span class="number">0</span>]));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">int</span>[] goods = <span class="keyword">new</span> <span class="keyword">int</span>[n];</span><br><span class="line">        goods[n - <span class="number">1</span>] = <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">int</span> res = <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = n - <span class="number">2</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="keyword">int</span> good = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> j = n - <span class="number">1</span>; j &gt; i; j--) &#123;</span><br><span class="line">                <span class="keyword">if</span> (envelopes[j][<span class="number">0</span>] &gt; envelopes[i][<span class="number">0</span>]) &#123;   <span class="comment">// 比较宽</span></span><br><span class="line">                    <span class="keyword">if</span> (envelopes[j][<span class="number">1</span>] &gt; envelopes[i][<span class="number">1</span>]) &#123;    <span class="comment">// 比较高</span></span><br><span class="line">                        good = Math.max(good, goods[j]);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;   <span class="comment">// 寻找比信封i大的信封，信封j不可能时，前面的信封宽更小，</span></span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            goods[i] = good + <span class="number">1</span>;</span><br><span class="line">            res = Math.max(res, good);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;题意提取&quot;&gt;&lt;a href=&quot;#题意提取&quot; class=&quot;headerlink&quot; title=&quot;题意提取&quot;&gt;&lt;/a&gt;题意提取&lt;/h2&gt;&lt;p&gt;信封有两属性，宽（w），高（h）;&lt;br&gt;信封a套入信封b的要求:
w[a] &amp;lt; w[b] &amp;amp;&amp;amp; h[
      
    
    </summary>
    
    
      <category term="算法" scheme="http://wjploop.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>Android Single Activity design (译)</title>
    <link href="http://wjploop.github.io/2021/02/26/Android-Single-Activity-design-%E8%AF%91/"/>
    <id>http://wjploop.github.io/2021/02/26/Android-Single-Activity-design-译/</id>
    <published>2021-02-26T03:28:40.000Z</published>
    <updated>2021-06-20T07:23:07.720Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p><a href="(https://proandroiddev.com/part-3-single-activity-architecture-514791724172)">原文</a></p></blockquote><h1 id="Android-单一Activity设计思路"><a href="#Android-单一Activity设计思路" class="headerlink" title="Android: 单一Activity设计思路"></a>Android: 单一Activity设计思路</h1><p>在19年Google I/O Talk上，单一Activity设计原则伴随着 Jetpack Navigation 被提及，现在Google更推荐<code>单一Activity</code>设计作为首选架构。</p><blockquote><p>我们又有造新名词了吗？”SAAs(Single Activity Applications)” 关于”单一Activity”，这个设计思路，即使Android开发圈子也并不是新鲜事物。</p></blockquote><p>单一Activity设计，可以类比web开发中<code>单页面应用</code>,<code>单页面应用</code>设计思路在现代web开发框架中非常流行。</p><p>本文我们将讨论以下几点：</p><ul><li><code>单一Activity</code>设计解决了什么问题？</li><li>何时该采用<code>单一Activity</code>设计?</li><li>对于现有的项目我们能做什么？</li></ul><p>在讨论这几点之前，先简单介绍下Activity和Fragment  </p><h2 id="Activity是什么？"><a href="#Activity是什么？" class="headerlink" title="Activity是什么？"></a>Activity是什么？</h2><p>Activity作为Android应用的入口，显然在任何一个应用中至少需要一个Activity。</p><blockquote><p>Activity作为四大组件之一，聚焦于一点，描述用户能做什么。几乎所有的Activity都与用户交互，故<code>Activity</code>类负责创建<code>Widnow</code>展示内容给用户，通过<code>setContentView(View)</code>方法我们可以放置所要展示的内容。尽管<code>Activity</code>通常用来展示全屏页面，但是也是可以用于其他方式…</p><p>摘自官方文档 <a href="https://developer.android.com/reference/android/app/Activity" target="_blank" rel="noopener">https://developer.android.com/reference/android/app/Activity</a></p></blockquote><h2 id="使用Activity有什么问题？"><a href="#使用Activity有什么问题？" class="headerlink" title="使用Activity有什么问题？"></a>使用Activity有什么问题？</h2><p>嗯…并没有出现多大的问题，以致你一定要使用单一Activity设计。  </p><p>而且，Android设计的理念，一个页面对应了一个Activity。至少，在引入Fragment之前是这样的，这种理念从Android诞生之后就一直被这么采用。</p><h2 id="Fragment是什么？"><a href="#Fragment是什么？" class="headerlink" title="Fragment是什么？"></a>Fragment是什么？</h2><blockquote><p>Fragment能替代Activity，因为Activity能做的，Fragment也能做。</p></blockquote><p>引入Fragment主要的目的，是让我们描述UI的代码可以复用，支持动态灵活的UI设计，因为Activity之间不能嵌套。（LocalActivityManager已经永远地被遗弃了）。</p><p>Fragment的APIs推出后已经改进很多了。最初，Fragment只是拥有与Activity相似的生命周期，如今，Fragment已经支持自己<code>后退栈</code>，并使用FragmentManager来管理。</p><p>在以下场景中，Fragment显得非常强大:</p><ul><li>支持多屏适配</li><li>使用ViewPager，这要求一定要使用Fragment</li><li>创建一个包含UI库，暴露一个Fragment而不是一个Activity意义就大很多了</li></ul><h2 id="Fragment存在什么问题"><a href="#Fragment存在什么问题" class="headerlink" title="Fragment存在什么问题?"></a>Fragment存在什么问题?</h2><p>Fragment曾存在很多问题，但随着Jetpack架构架构组件的引入，特别是<code>ViewModel</code>和<code>LiveData</code>的引入，很多问题都得到了解决。</p><p>实际上，抛开view-base框架，我们也毫无选择而必须使用Fragment。</p><blockquote><p>在本文，我不会去讨论其他避免使用Fragment的方法，比如：<a href="https://github.com/bluelinelabs/Conductor" target="_blank" rel="noopener">Conductor</a></p></blockquote><p>Fragment有一个优势：它现在不是Android框架的一部分，而是存在于Jetpack中。这样，很容易利于Android团队修复问题和添加新特性。</p><p>因此，对于我们自己的app，若是想要展示UI，我们必须声明一个Activity作为入口，但对于实现其他页面，我们可以存在很多方案。</p><h2 id="方案1：每一个页面作为独立的Activity"><a href="#方案1：每一个页面作为独立的Activity" class="headerlink" title="方案1：每一个页面作为独立的Activity"></a>方案1：每一个页面作为独立的Activity</h2><p>就如同Fragment未曾出现过一样。</p><p>比如，我们设计一个”购物”流程。我们可能会需要三个页面，购物清单清点页面，购物车详情页面，支付页面。这样就会有三个Activity需要被创建：</p><ol><li>OrderListReviewActivity</li><li>ShippingDetailActivity</li><li>PaymentActivity</li></ol><h2 id="方案2：每一个流程（模块）独立为一个Activity"><a href="#方案2：每一个流程（模块）独立为一个Activity" class="headerlink" title="方案2：每一个流程（模块）独立为一个Activity"></a>方案2：每一个流程（模块）独立为一个Activity</h2><p>App中每个模块独立为一个Activity，模块中的子页面使用Fragment来实现。</p><p>如此，将会有下列需实现：</p><ol><li>CheckoutActivity</li><li>OrderListReviewFragment</li><li>ShippingDetailFragment</li><li>PaymentFragment</li></ol><h2 id="方案3：单一Activity"><a href="#方案3：单一Activity" class="headerlink" title="方案3：单一Activity"></a>方案3：单一Activity</h2><p>将只有一个Activity作为应用入口，所有的页面使用Fragment实现，这个Activity作为宿主负责存放和管理Fragment。</p><p>给个例子，当前流程跨平台框架都在使用该方案，比如 Xamarin, Ionic, Flutter, Reactive Native, 它们表现都很好。</p><p>通常，我看到的情况是，开始会选择方案1，只用Activity，慢慢地Fragment被引入，绝大数是因为：</p><ol><li>需要支持手机和平板</li><li>需要复用UI</li><li>需要使用ViewPager，这个强制使用Fragment</li><li>使用了一些第三方库，其使用了Fragment作为暴露UI的方式，如:Google Maps</li></ol><p>理论上，我们可能处于方案1和方案2之间，而方案2切换到单一Activity设计差别不大。</p><h2 id="单一Activity设计解决了什么问题？"><a href="#单一Activity设计解决了什么问题？" class="headerlink" title="单一Activity设计解决了什么问题？"></a>单一Activity设计解决了什么问题？</h2><h3 id="1-不同版本Activity表现不一致"><a href="#1-不同版本Activity表现不一致" class="headerlink" title="1. 不同版本Activity表现不一致"></a>1. 不同版本Activity表现不一致</h3><p>Activity作为Android框架一部分，其行为和支持特性绑定了Android版本。添加的新特性和修复的bug并不能保证在低版本系统中可用。或许，你可能选择兼容库如 ActivityCompat,ActivityOptionCompat等来解决那些边角问题，但这很痛苦。</p><p>多个Activity不仅增加了开发时间，也增加了测试时间。表现不一致的问题，也同样存在于不同设备和不同版本。</p><p>###2. 在Activity功能共享数据</p><p>在Activity之间，想要共享数据，只能将该数据放在Application级别作用域中。然而，在该作用域中，其他的Android组件也能获取到了，如Service，BroadcastReceiver,ContentProvider.</p><p>理想中，应该存在一个独立的作用域，用于存放几个页面共享的的数据，最好是在Activity级别的。</p><p>###3. 糟糕的用户体验</p><p>当切换Activity时，整个窗口都会被替换。因而，Toolbar/ActionBar也将会替换。我个人认为，Toolbar不应该被替换，而是应该更新相关的内容即可。如同桌面应用一样，Toolbar永远不变，变的仅有下面的内容。</p><p>而且，由于不同版本的Activity表示不一致，其场景切换动画也会在不同设备不同版本表现不一致。</p><p>###4. 开发体验</p><p>当使用多个Activity时: </p><ul><li><p>每添加一个页面都要同步添加该Activity到Manifest文件中，仔细想想是不是有点奇怪。</p></li><li><p>某个控制功能需要在多个页面都需要实现时，将会耗费额外的精力。比如NavigationDrawer，底部栏，通用的菜单栏。</p></li><li><p>检测应用是否正在运行将变得困难，会发现在同一个时间，会有多个Activity在栈中。</p></li></ul><p>单一Activity设计看来是一个很好的架构设计。但，直到现在，这样的架构缺少相关的框架，实现是困难的。目前存在一些支持单一Activity的框架比如Conductor和Scoop,但它们不支持Fragment。</p><p>随着Navigation组件的引入，现在已经很容易实现单一Activity的架构了。</p><h2 id="什么时候该采用单一Activity设计"><a href="#什么时候该采用单一Activity设计" class="headerlink" title="什么时候该采用单一Activity设计"></a>什么时候该采用单一Activity设计</h2><p>采用单一Activity设计遇到的问题，Navigation组件已经为我们解决大部分了，所以，若是你现在开始新的项目，你应该毫无疑问的采用该设计。</p><p>Navigation组件提供了容易理解的API，类storyboard（IOS开发）的编辑器和详尽的文档和入门教程。</p><p>Navigation支持以下：</p><ol><li>处理<a href="https://developer.android.com/training/app-links/deep-linking" target="_blank" rel="noopener">deep link</a></li><li>简单可靠的转场动画</li><li>更易用处理Toolbar</li><li>支持动态特性模块</li></ol><h2 id="对于现有的项目我们能做什么？"><a href="#对于现有的项目我们能做什么？" class="headerlink" title="对于现有的项目我们能做什么？"></a>对于现有的项目我们能做什么？</h2><p>理论上，对于那些已经混合了Activity和Fragment的现有项目，逐渐迭代到单一Activity设计是有意义的。开始，可以选择一个小模块进行单一Activity多Fragment切换。  </p><p>若是有人拥有迭代现有项目到单一Activity设计的经验，我原意与之一起讨论其中的细节。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>单一Activity设计不是新鲜的概念了</li><li>诸如Conductor和Scoop基于View-based实现单一Activity的框架不支持Fragment</li><li>在Navigation推出之前，使用Fragment实现单一Activity设计不划算</li><li>Navigation组件可实现基于Fragment的单一Activity</li><li>对于新项目，应该毫无疑问使用单一Activity设计了</li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;(https://proandroiddev.com/part-3-single-activity-architecture-514791724172)&quot;&gt;原文&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;Andr
      
    
    </summary>
    
    
      <category term="翻译" scheme="http://wjploop.github.io/tags/%E7%BF%BB%E8%AF%91/"/>
    
  </entry>
  
  <entry>
    <title>失效的技巧</title>
    <link href="http://wjploop.github.io/2021/02/20/%E5%A4%B1%E6%95%88%E7%9A%84%E6%8A%80%E5%B7%A7/"/>
    <id>http://wjploop.github.io/2021/02/20/失效的技巧/</id>
    <published>2021-02-20T02:47:32.000Z</published>
    <updated>2022-09-14T01:53:41.156Z</updated>
    
    <content type="html"><![CDATA[<p>开工了，2021年~</p><p>过年在V站看到一个帖子<a href="https://www.v2ex.com/t/753483" target="_blank" rel="noopener">有些技巧以前很重要，现在越来越没用了</a> ，感慨很多。以前学的很多技巧，用来解决某个问题，而当问题不在之后，这个技巧也就失效了。</p><p>对于失效的技巧，发现大家的反应会分为两种</p><ul><li>感性派（源于技巧的关键字，开始怀念过去）  </li><li>理性派（反思为什么会这样子，如何避免花过多精力于技巧）  </li></ul><h2 id="我想我是感性派"><a href="#我想我是感性派" class="headerlink" title="我想我是感性派"></a>我想我是感性派</h2><p>其中有个人提及的</p><blockquote><p>斯凯 MTK 应用。通过<em>#220807#、</em>#777755999#打开国产山寨手机的 mrp 应用列表入口，可能需要安装 applist.mrp yyrj.mrp dsm_gm.mrp 三个文件，然后把其他 mrp 格式的应用放到对应位置就可以打开了。当年 mrp 软件和游戏还是有很多精品的，可惜现在 mrp 这个关键词都搜不到了。</p></blockquote><p>提及“MTK”、”MRP“、那都是满满的青春回忆~   *#220807# 应用入口命令，再熟悉不过了，看到一个新的山寨机，便会不由自主的按下试试支不支持安装应用。<br>对了，还有一个文件夹名称，“mythroad”，支持安装应用的手机，会在SD卡根目录下生成该文件夹。当时英文不好，只是记着这7个单词，现在回头看，发现这个可以是 “myth road” 神话之路，突然能感受取这个名字的用意了。</p><p>其实，我咋看以为是 “my throat(d)” 我的喉咙，啊，感觉到不对劲，查了下，才发人家的原意，现在的英文也还不行，不过，这提示我们命名要用驼峰或下划线啊~</p><h2 id="假装是理性派"><a href="#假装是理性派" class="headerlink" title="假装是理性派"></a>假装是理性派</h2><p>为什么技巧会失效？</p><p>技巧是是针对某个问题的巧妙方法，是解决问题的方式，问题消失了，我们技巧也就没用了。比如，我怀念的山寨机应用神话之路，当今已经没有山寨机，这种技巧也就没有用武之地了。  </p><p>如何避免技巧会失效？</p><p>避免技巧失效，依据失效的原因，就是要避免问题会消失。而问题会消失吗？有点的会，有的不会，有些技巧会一生有用，比如，沟通时需照顾对方感受时采取换位思考，有的技巧注定会失效，比如记住那个*#220807#应用入口命令。我们凭什么认为换位思考的技巧一生有效？深究起来似乎也站不住脚，假如人与人不需要沟通了呢？去掉假如，我与之沟通的不是人，而是一台机器呢？啊，别抬杠自己了。与人交流这个需求，在我的认知里面永远会存在。</p><p>技巧失效的场景，想到了职业问题，为什么医生会越老越吃香，而程序员老了就没人要了。是不是存在一个原因是，两者工作的对象，人的变化相对机器要少很多，故而，医生掌握的技巧对有效性更久一些。</p><p>我们试图寻求在不变的问题，从而将技巧的有效性延长。我们所遇的问题中，很多问题是相通的，不同的问题是存在相同部分，我们解决的问题选择分治法，将问题拆分，逐一解决，解决子问题时使用掌握的技巧。这样，可以说，技巧是存在延续性的。根据这点，我们能所做的是，平时遇到问题后，三思而行，将问题拆分逐一解决，积累解决通用问题的工具包。</p><p>感觉这种策略是普遍的，大家也都是这么做的。突然想到了数学的一些名词，定义，性质，推论，我们有限的小脑子要掌握哪些东西呢？定义永远是优先掌握的吧，性质用来让我们如何更加立体理解定义，推论用来解决我们的问题。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;开工了，2021年~&lt;/p&gt;
&lt;p&gt;过年在V站看到一个帖子&lt;a href=&quot;https://www.v2ex.com/t/753483&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;有些技巧以前很重要，现在越来越没用了&lt;/a&gt; ，感慨很多。
以前学的很多技
      
    
    </summary>
    
    
      <category term="杂谈" scheme="http://wjploop.github.io/tags/%E6%9D%82%E8%B0%88/"/>
    
  </entry>
  
</feed>
