<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>HelloVass</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://hellovass.github.io/</id>
  <link href="https://hellovass.github.io/" rel="alternate"/>
  <link href="https://hellovass.github.io/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, HelloVass</rights>
  <subtitle>起风了，唯有努力生存</subtitle>
  <title>HelloVass's Blog</title>
  <updated>2026-04-19T11:40:53.352Z</updated>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<p>她曾在人海里，看见我的光，快步走向我，怕我被岁月淹没。</p><span id="more"></span><p>这一次，她带着所有的勇敢与温柔，历经辛苦，把我们的孩子带到人间。</p><p>&nbsp;</p><p>思而后越，行稳致远；</p><p>越而有勇，蓄力而发。</p><p>故名思越。</p><p>&nbsp;</p><p>一念思吴越，一念念长久。</p><p>生逢四月九，恰应九郎缘。</p><p>&nbsp;</p><p>从此，我们仨。</p><p>&nbsp;</p><p>致我最温柔最勇敢的老婆。</p><p>&nbsp;</p><p>欢迎你，小九。</p>]]>
    </content>
    <id>https://hellovass.github.io/2026/04/19/%E6%AC%A2%E8%BF%8E%E4%BD%A0%EF%BC%8C%E5%B0%8F%E4%B9%9D/</id>
    <link href="https://hellovass.github.io/2026/04/19/%E6%AC%A2%E8%BF%8E%E4%BD%A0%EF%BC%8C%E5%B0%8F%E4%B9%9D/"/>
    <published>2026-04-19T15:00:00.000Z</published>
    <summary>
      <![CDATA[<p>她曾在人海里，看见我的光，快步走向我，怕我被岁月淹没。</p>]]>
    </summary>
    <title>欢迎你，小九</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><p>疫情至今已经肆虐了 3 年，互联网也变得比以往任何时候更加寒冷，即使这样，我都忍不住想问自己一句：今年有没有可能是往后 10 年最好的一年？！</p><div style="position: relative; width: 100%; height: 0; padding-bottom: 75%;">    <iframe src="//player.bilibili.com/player.html?aid=559274238&bvid=BV1Ue4y1m7H6&cid=867575195&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;">    </iframe></div><span id="more"></span><h2 id="第一次给爹猪过了一个充满仪式感的生日"><a href="#第一次给爹猪过了一个充满仪式感的生日" class="headerlink" title="第一次给爹猪过了一个充满仪式感的生日"></a>第一次给爹猪过了一个充满仪式感的生日</h2><p>说道这个真的非常惭愧，这么多年都没有给我的爹猪过个充满仪式感的生日，还是我的大猪儿细心周到，给我爹猪过了一次难忘的生日。</p><p>我和大猪给爹猪选的生日蛋糕：<br><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/2022%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/%E7%94%9F%E6%97%A5%E8%9B%8B%E7%B3%95.jpeg" alt="选的生日蛋糕"></p><p>我、大猪、我妈给爹猪庆祝：<br><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/2022%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/%E7%8C%AA%E5%84%BF%E5%90%88%E7%85%A7.jpeg" alt="给爹猪庆祝生日"></p><h2 id="No-Game-No-Life"><a href="#No-Game-No-Life" class="headerlink" title="No Game No Life"></a>No Game No Life</h2><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/2022%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/%E6%B8%B8%E6%88%8F%E6%88%AA%E5%9B%BE.png"></p><blockquote><p>感谢 steam，真细心，还帮我<a href="https://store.steampowered.com/replay/76561198180679247/2022?l=schinese">总结</a>了一下</p></blockquote><p>已经不记得是啥时候跟风买的「奥日与黑暗森林」了，对奥日的印象还停留在独立游戏、画面精美、动作&amp;场景设计一流上，然后就是玩了几个小时，就没有然后了。后来，有阵子静下心来细细再体验了一段时间，才发现这简直是一款称得上是艺术品的游戏，深陷其中不可自拔，仿佛自己就是那个飞天遁地的奥日精灵。</p><p>但，故事总有完结的时候。火，也总需要有人传下去。</p><p>4.24，全收集、100% 全支线通关了。</p><h2 id="和猪儿相识一周年纪念日"><a href="#和猪儿相识一周年纪念日" class="headerlink" title="和猪儿相识一周年纪念日"></a>和猪儿相识一周年纪念日</h2><p>每次看到这张图，再看看已经成为我老婆的猪儿，不得不感慨，时间真是过得好快啊。</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/2022%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/%E7%BB%93%E5%A9%9A%E7%85%A701.jpg"></p><h2 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h2><h3 id="王鹏老师馈赠"><a href="#王鹏老师馈赠" class="headerlink" title="王鹏老师馈赠"></a>王鹏老师馈赠</h3><p>也是造化弄人，当年在抖音的时候，都没咋接触过大佬。反而是，出来之后，和大佬的接触才多了起来。</p><p><a href="https://live.csdn.net/room/wl5875/N7AkbqPg">线上参加 I&#x2F;O Extended 杭州 Android 专场活动</a>，认识了前同事王鹏老师，顺便白嫖了一本鹏佬的新书，嘿嘿。</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/2022%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/%E7%BB%93%E5%A9%9A%E7%85%A702.jpg"></p><h3 id="在著名公众号-kotlin-出道（bushi"><a href="#在著名公众号-kotlin-出道（bushi" class="headerlink" title="在著名公众号 kotlin 出道（bushi"></a>在著名公众号 kotlin 出道（bushi</h3><p><a href="https://mp.weixin.qq.com/s/FyL4Fmtl3VDmNfeOq3kdLg">放弃 KotlinPoet 基于模版引擎生成 Dependency 的 Gradle Plugin</a>。</p><p>几年前开始关注霍老师的 kotlin 微信公众号，当时，还误以为是 jetbrain 官方的公众号呢。发现公众号里的内容质量不错之后，反手又是一波关注了霍老师的 B站账号，简直就是打开了新天地，没想到霍佬是真的就六边形战士。</p><h3 id="Booster"><a href="#Booster" class="headerlink" title="Booster"></a>Booster</h3><ul><li><a href="https://github.com/didi/booster/issues/368">https://github.com/didi/booster/issues/368</a></li><li><a href="https://github.com/didi/booster/issues/387">https://github.com/didi/booster/issues/387</a></li></ul><h2 id="我和大猪结婚了"><a href="#我和大猪结婚了" class="headerlink" title="我和大猪结婚了"></a>我和大猪结婚了</h2><p>11.11，和猪儿一起去民政局登记领证。刚进门的时候保安出于抗疫政策的大义坚决不让我们的随行跟拍——我爹猪进来，哎，真是闹心。<br>好在领证过程非常顺利，不得不感慨一下，现在领证的效率在数字化信息时代是真的高效得离谱，差不多 100% 自助那种，旁边有个工作人员稍微辅助下就完事了。<br>但，如果还是想要一些结婚的仪式感，也不是不可以。工作人员会把我们领到一个小房间，过一下结婚的流程，然后顺便帮我们拍一下照+视频，<br>有点可惜的是，持相机的工作人员对焦一直没对好，就有点遗憾。</p><p>对我俩来说，毕竟是个大日子，今天总得发点什么吧。看了许多小红书的文案，还是觉得差那么点意思，这时候，突然想到了要不问问<a href="https://hellovass.info/2016/12/02/%E3%80%8E%E5%90%9B%E5%90%8D%E3%80%8F%C2%B7%E8%B0%81%E5%9C%A8%E8%A2%AB%E8%99%90/#more">过去的自己</a>吧，于是，便有了以下的朋友圈文案。</p><p>谢谢，6 年前那个孤独的、不安的，期盼爱情与另一半的我。</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/2022%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/%E5%86%99%E7%9C%9F%E7%85%A7.jpeg"></p><h2 id="照相"><a href="#照相" class="headerlink" title="照相"></a>照相</h2><p>不得不说，在大猪的严厉教导下，我对光与影的掌握逐渐开始熟练。以下请欣赏由我这名绝地学徒带来的作品——「大猪与小猪」：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/2022%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/%E5%90%88%E7%85%A7.jpg"></p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2>]]>
    </content>
    <id>https://hellovass.github.io/2022/12/28/2022%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/</id>
    <link href="https://hellovass.github.io/2022/12/28/2022%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
    <published>2022-12-28T10:41:52.000Z</published>
    <summary>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><p>疫情至今已经肆虐了 3 年，互联网也变得比以往任何时候更加寒冷，即使这样，我都忍不住想问自己一句：今年有没有可能是往后 10 年最好的一年？！</p>


<div style="position: relative; width: 100%; height: 0; padding-bottom: 75%;">
    <iframe src="//player.bilibili.com/player.html?aid=559274238&bvid=BV1Ue4y1m7H6&cid=867575195&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;">
    </iframe>
</div>]]>
    </summary>
    <title>2022 年终总结</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><p>苦难与惊喜的一年！</p><span id="more"></span>]]>
    </content>
    <id>https://hellovass.github.io/2021/12/31/2021%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/</id>
    <link href="https://hellovass.github.io/2021/12/31/2021%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
    <published>2021-12-31T10:36:28.000Z</published>
    <summary>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><p>苦难与惊喜的一年！</p>]]>
    </summary>
    <title>2021 年终总结</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<div style="position: relative; width: 100%; height: 0; padding-bottom: 75%;">    <iframe width="560" height="315" src="https://www.youtube.com/embed/MkKDe_Rp2f8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><span id="more"></span>]]>
    </content>
    <id>https://hellovass.github.io/2021/08/02/%E4%B8%89%E4%BA%9A%E4%B9%8B%E6%97%85/</id>
    <link href="https://hellovass.github.io/2021/08/02/%E4%B8%89%E4%BA%9A%E4%B9%8B%E6%97%85/"/>
    <published>2021-08-02T02:30:25.000Z</published>
    <summary>
      <![CDATA[<div style="position: relative; width: 100%; height: 0; padding-bottom: 75%;">
    <iframe width="560" height="315" src="https://www.youtube.com/embed/MkKDe_Rp2f8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>]]>
    </summary>
    <title>三亚之旅</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="折腾" scheme="https://hellovass.github.io/categories/tinkering/"/>
    <category term="DIY" scheme="https://hellovass.github.io/tags/DIY/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>记得那只猩猩问屏幕前的我来不来的时候，我选择了加入，于是 15 年的那个夏天，一下班就跑去网吧开机。没办法鸭，那时候穷，根本组不起一台可以畅玩守望先锋的设备，那时候公司给实习生福利的那个小出租屋真的是又破又小，刚好能容纳下一张床板和柜子。当时经常光顾的那家网咖，用的还是 iMac，性能也就刚刚好满足中画质 60 帧。后来，进入第一家公司正式工作之后，在川神和熊桑的诱惑下，我分期买了人生中第一台 itx 主机…</p><span id="more"></span><h2 id="第一台-itx-主机"><a href="#第一台-itx-主机" class="headerlink" title="第一台 itx 主机"></a>第一台 itx 主机</h2><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/itx%E6%94%92%E6%9C%BA%E4%B9%8B%E6%97%85/%E7%AC%AC%E4%B8%80%E5%8F%B0itx%E4%B8%BB%E6%9C%BA.jpg" alt="第一台 itx 主机"><br><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/itx%E6%94%92%E6%9C%BA%E4%B9%8B%E6%97%85/%E7%AC%AC%E4%B8%80%E5%8F%B0itx%E4%B8%BB%E6%9C%BA%E9%85%8D%E7%BD%AE.jpg" alt="第一台 itx 主机配置"></p><p>当时这台主机是某宝买的，第一眼看到这个造型就觉得是它了，但现在回过头来看里面的配置清单，就觉得，当时硬件小白的自己真傻，居然做了整机烈士，被奸商狠宰了一波鸭。而且，最重要的是，这根本没有 itx 的灵魂!</p><ul><li>非模块化的电源</li><li>几倍正常 itx 机箱的体积</li><li>140 的信仰水冷，还美其名曰自主研发，醉了</li><li>可怜到容量只有 256G 的 SSD</li><li>…</li></ul><p>虽然吐槽了这么多，也正是因为第一次接触了 itx 这个垂直细分领域，我打开了新世界的大门，从此在 itx 的攒机路上一发不可收拾，哈哈！</p><h2 id="ITX-攒机之旅"><a href="#ITX-攒机之旅" class="headerlink" title="ITX 攒机之旅"></a>ITX 攒机之旅</h2><h2 id="涅槃重生"><a href="#涅槃重生" class="headerlink" title="涅槃重生"></a>涅槃重生</h2><p>因为第一台 itx 主机实在太过于庞大，不够便携（指搬家），于是，我在保留了一部分硬件（指 CPU）的前提下，又买了一些新的硬件，将整个主机翻新了一下：</p><table><thead><tr><th>硬件</th><th>型号</th><th>价格（单位：人民币）</th></tr></thead><tbody><tr><td>CPU</td><td><a href="https://item.jd.com/26337195805.html">i7-6700k</a></td><td>2299</td></tr><tr><td>内存</td><td><a href="https://item.jd.com/2939894.html#crumb-wrap">海盗船 DDR4-2400 复仇者 8G*2</a></td><td>539</td></tr><tr><td>散热</td><td><a href="https://detail.tmall.com/item.htm?id=575223803461&spm=a1z09.2.0.0.26262e8dBaLZxk&_u=2ve3o448e63">利民 thermalright AXP-100 纯铜 CPU散热器 58mm 高度</a></td><td>529</td></tr><tr><td>主板</td><td><a href="https://item.taobao.com/item.htm?spm=a1z09.2.0.0.26262e8dBaLZxk&id=598907977353&_u=2ve3o44236d">华硕B250</a></td><td>699</td></tr><tr><td>显卡</td><td><a href="https://item.jd.com/100002821714.html">rtx2060</a></td><td>2999</td></tr><tr><td>硬盘</td><td><a href="https://item.jd.com/40561723363.html">海康威视 C2000Pro 1R M.2 SSD</a></td><td>999</td></tr><tr><td>电源</td><td><a href="https://item.jd.com/2654964.html">海盗船 SF600</a></td><td>899</td></tr><tr><td>定制线</td><td><a href="https://item.taobao.com/item.htm?spm=a1z09.2.0.0.2a762e8dW43RCE&id=44474132780&_u=2ve3o44fb86">YY定制电源模组线材</a></td><td>190</td></tr><tr><td>机箱</td><td><a href="https://item.taobao.com/item.htm?spm=a1z09.2.0.0.2a762e8dW43RCE&id=572307637638&_u=2ve3o44e4f2">SeaDamaged定制碳纤维机箱轻量化SEMmini5.57Lsf600便携小钢炮mod</a></td><td>1568</td></tr></tbody></table><blockquote><p>总计：9887￥，重点强调一下这个机箱，碳纤维材质的，非常便携，质量只有500g左右，体积5.57L！</p></blockquote><h2 id="办公"><a href="#办公" class="headerlink" title="办公"></a>办公</h2><p>这台主机，和上面那台的思路差不多，将之前在淘宝买的一台工作用的主机 itx 化了，保留了 CPU、内存，其他的都升级了一下：</p><table><thead><tr><th>硬件</th><th>型号</th><th>价格（单位：人民币）</th></tr></thead><tbody><tr><td>CPU</td><td><a href="https://item.jd.com/18794427126.html#crumb-wrap">i7-8700k</a></td><td>2699</td></tr><tr><td>内存</td><td><a href="https://item.jd.com/2939894.html#crumb-wrap">威刚 DDR4-2400  16G*2</a></td><td>918</td></tr><tr><td>散热</td><td><a href="https://item.jd.com/4567820.html">酷冷至尊B240水冷散热</a></td><td>299</td></tr><tr><td>主板</td><td><a href="https://item.jd.com/100000544940.html">华擎Z390</a></td><td>1599</td></tr><tr><td>显卡</td><td><a href="https://item.taobao.com/item.htm?spm=a230r.1.14.14.4415635aXEtYqk&id=584519175160&ns=1&abbucket=12#detail">gtx-1070</a></td><td>1999</td></tr><tr><td>硬盘</td><td><a href="https://item.jd.com/40561723363.html">海康威视 C2000Pro 1R M.2 SSD</a></td><td>999</td></tr><tr><td>电源</td><td><a href="https://item.jd.com/2654964.html">海盗船 SF600</a></td><td>899</td></tr><tr><td>定制线</td><td><a href="https://item.taobao.com/item.htm?spm=a1z09.2.0.0.2a762e8dW43RCE&id=44474132780&_u=2ve3o44fb86">YY定制电源模组线材</a></td><td>190</td></tr><tr><td>机箱</td><td><a href="https://item.taobao.com/item.htm?spm=a1z09.2.0.0.26262e8dBaLZxk&id=572918958857&_u=2ve3o44a61c">sunmilo机箱定制:T03 A4水冷itx 钢化玻璃侧透全铝10L</a></td><td>939</td></tr></tbody></table><blockquote><p>总计：10121￥，机箱是淘宝的孙总定制款机箱，比 5.57L 的那台大了不少，不过也还行 10L 体积，关键之颜值高鸭！</p></blockquote><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>嗯，这这篇文章就是想告诉各位对 itx 攒机感兴趣的盆友，itx 的乐趣在于尽可能地利用有限的空间来发挥压榨机器最大的性能，在自己的腰包承受范围内，选择最好的硬件，然后开动自己的双手和大脑，克服一个又一个装机时的麻烦问题，攒出自己觉得最棒的 itx 主机哈哈！</p>]]>
    </content>
    <id>https://hellovass.github.io/2020/06/05/%E8%BD%B2%E7%88%B7%E7%9A%84%20itx%20%E6%94%92%E6%9C%BA%E4%B9%8B%E6%97%85/</id>
    <link href="https://hellovass.github.io/2020/06/05/%E8%BD%B2%E7%88%B7%E7%9A%84%20itx%20%E6%94%92%E6%9C%BA%E4%B9%8B%E6%97%85/"/>
    <published>2020-06-05T08:39:42.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>记得那只猩猩问屏幕前的我来不来的时候，我选择了加入，于是 15 年的那个夏天，一下班就跑去网吧开机。没办法鸭，那时候穷，根本组不起一台可以畅玩守望先锋的设备，那时候公司给实习生福利的那个小出租屋真的是又破又小，刚好能容纳下一张床板和柜子。当时经常光顾的那家网咖，用的还是 iMac，性能也就刚刚好满足中画质 60 帧。后来，进入第一家公司正式工作之后，在川神和熊桑的诱惑下，我分期买了人生中第一台 itx 主机…</p>]]>
    </summary>
    <title>轲爷的 itx 攒机之旅</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="面试" scheme="https://hellovass.github.io/categories/interview/"/>
    <category term="面试" scheme="https://hellovass.github.io/tags/%E9%9D%A2%E8%AF%95/"/>
    <content>
      <![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><blockquote><p>兄dei，Gradle 生命周期有了解过嘛？<br>emmmmmm….<br>那么，来说说 Configuration 阶段 Gradle 干了什么吧<br>emmmmmm….</p></blockquote><span id="more"></span><h1 id="Gradle-生命周期"><a href="#Gradle-生命周期" class="headerlink" title="Gradle 生命周期"></a>Gradle 生命周期</h1><p>Gradle 在编译项目时有着它自己的生命周期，从编译开始编译完毕 Gradle 一共要经历 3 个阶段：</p><ul><li>Initialization</li></ul><p>初始化阶段，即执行 Gradle 的初始化配置选项，即执行项目中的 settings.gradle 脚本。</p><ul><li>Configuration</li></ul><p>解析每个 Project 中的 build.gradle 脚本，即解析所有 Project 中的编译选项。解析完成后，Gradle 就生成了一张有向关系图——taskgraph，这面包含了整个 Task 的依赖关系。</p><ul><li>Build</li></ul><p>该阶段是最后的编译运行阶段，即按照 taskgraph 执行编译。</p><p>这三个阶段是宏观把握 Gradle 的核心所在，运行任何 Gradle 指令都会经过这样三个阶段。</p><p>默认的输出是在配置阶段，这时候 Gradle 会执行所有 Task 的配置脚本，即使你执行一个指定的 Task，也有到 Build 阶段，才能够开始执行。</p><h1 id="Gradle-生命周期的监听"><a href="#Gradle-生命周期的监听" class="headerlink" title="Gradle 生命周期的监听"></a>Gradle 生命周期的监听</h1><h2 id="Gradle-耗时-Task-统计"><a href="#Gradle-耗时-Task-统计" class="headerlink" title="Gradle 耗时 Task 统计"></a>Gradle 耗时 Task 统计</h2><p>统计 Task 耗时，收集耗时超过100ms 的 Task，排序之后输出到 build 目录下的xxx.txt 文件中。</p><h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 分析 Task，收集耗时超过 100ms 的task，并将日志输出到 build 文件夹下</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> Gradle.<span class="title">analyzeTask</span><span class="params">()</span></span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">val</span> clock = Clock()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">val</span> execution = mutableListOf&lt;TaskInfo&gt;()</span><br><span class="line"></span><br><span class="line">    addTaskExecutionLifecycle &#123;</span><br><span class="line">        _beforeExecute &#123; task -&gt;</span><br><span class="line">            task.extensions.add(<span class="string">&quot;startAt&quot;</span>, System.currentTimeMillis())</span><br><span class="line">        &#125;</span><br><span class="line">        _afterExecute &#123; task, _ -&gt;</span><br><span class="line">            <span class="comment">// 计算 Task 的执行时间</span></span><br><span class="line">            <span class="keyword">val</span> duration = System.currentTimeMillis() - (task.extensions[<span class="string">&quot;startAt&quot;</span>] <span class="keyword">as</span> <span class="built_in">Long</span>)</span><br><span class="line">            <span class="comment">// 收集超过耗时超过 100ms 的 Task</span></span><br><span class="line">            <span class="keyword">if</span> (duration &gt;= <span class="number">100</span>) &#123;</span><br><span class="line">                execution += TaskInfo(task, duration)</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">    addBuildLifecycle &#123;</span><br><span class="line">        _buildFinished &#123;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 没有超过 100ms 的 Task，忽略</span></span><br><span class="line">            <span class="keyword">if</span> (execution.isEmpty())</span><br><span class="line">                <span class="keyword">return</span><span class="symbol">@_buildFinished</span></span><br><span class="line"></span><br><span class="line">            <span class="comment">// 按照耗时排序</span></span><br><span class="line">            execution.sortBy &#123; taskInfo -&gt;</span><br><span class="line">                taskInfo.duration</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">val</span> outputStr = buildString &#123;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 构建总耗时</span></span><br><span class="line">                <span class="keyword">val</span> buildTimeInSeconds = clock.timesInMs / <span class="number">1000</span></span><br><span class="line">                <span class="keyword">val</span> minutes = buildTimeInSeconds / <span class="number">60</span></span><br><span class="line">                <span class="keyword">val</span> seconds = buildTimeInSeconds % <span class="number">60</span></span><br><span class="line"></span><br><span class="line">                <span class="comment">// 构建字符串模板</span></span><br><span class="line">                <span class="keyword">val</span> timeStr = <span class="keyword">when</span> (minutes) &#123;</span><br><span class="line">                    <span class="number">0L</span> -&gt;</span><br><span class="line">                        <span class="string">&quot;<span class="subst">$&#123;seconds&#125;</span>s&quot;</span></span><br><span class="line">                    <span class="keyword">else</span> -&gt;</span><br><span class="line">                        <span class="string">&quot;<span class="subst">$&#123;minutes&#125;</span>m <span class="subst">$&#123;seconds&#125;</span>s (<span class="subst">$&#123;buildTimeInSeconds&#125;</span>s)&quot;</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">                append(<span class="string">&quot;Build finished in <span class="subst">$&#123;timeStr&#125;</span>\n&quot;</span>)</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 添加耗时超过 100ms 的 Task</span></span><br><span class="line">                execution.forEach &#123; taskInfo -&gt;</span><br><span class="line">                    append(</span><br><span class="line">                        String.format(</span><br><span class="line">                            <span class="string">&quot;%7sms %s\n&quot;</span>,</span><br><span class="line">                            taskInfo.duration,</span><br><span class="line">                            taskInfo.task.path</span><br><span class="line">                        )</span><br><span class="line">                    )</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 创建文件，并将内容写入</span></span><br><span class="line">            <span class="keyword">val</span> file = File(</span><br><span class="line">                gradle.rootProject.buildDir.absolutePath,</span><br><span class="line">                <span class="string">&quot;build_time_records_<span class="subst">$&#123;Date().format(<span class="string">&quot;yyyy-MM-dd-HH-mm-ss&quot;</span>)&#125;</span>.txt&quot;</span></span><br><span class="line">            )</span><br><span class="line">            file.parentFile.mkdirs()</span><br><span class="line">            file.write(outputStr)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>监听 TaskExecutionListener 的 beforeExecute 和 afterExecute，计算每一个 Task 的耗时，并且将耗时大于 100ms 的 Task 收集到 execution 中</li><li>监听 BuildListener 的 buildFinished， build 结束之后将 execution 排序，并且计算总耗时</li><li>最后，将信息写入到 build 目录下的 xxx.txt 文件</li></ol><blockquote><p>PS:这里使用 kotlin 带接收器的扩展函数特性对 addListener 做了简化，让代码看起来更 DSL 一些</p></blockquote><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li>Android 群英传 神兵利器<ul><li>4.8章，Gradle 思考</li></ul></li><li><a href="https://github.com/Blankj/AucFrameTemplate">Blankj Auc 中统计 Task 耗时的代码</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2020/04/03/Interview%C2%B7Gradle%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E7%AF%87/</id>
    <link href="https://hellovass.github.io/2020/04/03/Interview%C2%B7Gradle%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E7%AF%87/"/>
    <published>2020-04-03T08:28:11.000Z</published>
    <summary>
      <![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><blockquote>
<p>兄dei，Gradle 生命周期有了解过嘛？<br>emmmmmm….<br>那么，来说说 Configuration 阶段 Gradle 干了什么吧<br>emmmmmm….</p>
</blockquote>]]>
    </summary>
    <title>写给面试的·Gradle 生命周期篇</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Kotlin" scheme="https://hellovass.github.io/tags/Kotlin/"/>
    <content>
      <![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>从刚接触 Android 的时候有一些业务场景真的让人蛋疼，虽说不是无解，但问题是解决了，但看着这堆代码总觉得哪里怪怪的。</p><p>那么，是什么问题呢？</p><div style="position: relative; width: 100%; height: 0; padding-bottom: 75%;">    <iframe src="//player.bilibili.com/player.html?aid=73040039&bvid=BV1hE41127pY&cid=124939237&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;">    </iframe></div><span id="more"></span><h1 id="State-Management"><a href="#State-Management" class="headerlink" title="State Management"></a>State Management</h1><p>React 的理念里有这么一个公式：</p><blockquote><p>UI &#x3D; render(data)</p></blockquote><p>UI 即用户看到的界面，是一个 <code>render</code>（函数）的执行结果，只接受 <code>data</code>（数据）作为参数。这个函数是一个<strong>纯函数</strong>，所谓纯函数，指的是没有任何副作用，输出完全依赖于输入的函数，两次函数调用，如果输入相同，得到的结果也绝对相同。如此一来，最终的用户界面，在 <code>render</code> 函数确定的情况下完全取决于输入数据。</p><p>对于开发者来说，重要的是区分开哪些属于 <code>data</code>，哪些属于 <code>render</code>，想要更新用户界面，要做的就是更新 <code>data</code>，用户界面自然会做出响应，所以 React 实践的也是“响应式编程”（Reactive Programming）的思想。</p><h2 id="React-的-state"><a href="#React-的-state" class="headerlink" title="React 的 state"></a>React 的 state</h2><p>驱动组件渲染过程的除了 prop，还有 state， state 代表组件的内部状态。由于 React 组件不能修改传入的 prop，所以需要记录自身数据变化，就要使用 state。</p><h2 id="状态分类"><a href="#状态分类" class="headerlink" title="状态分类"></a>状态分类</h2><p>状态分 2 种：</p><ul><li>局部状态：这种由 Component 管理，比如：<ul><li>Progressbar 的当前进度</li><li>TabIndicator 的当前选中 tab</li></ul></li></ul><p>这种状态不需要使用复杂的状态管理方案，靠 Component 维护就好了。</p><ul><li>全局状态：需要在2个及以上的地方共享的状态。比如：<ul><li>用户信息</li><li>用户设置</li><li>购物车</li><li>…</li></ul></li></ul><p>而这些状态的划分，emmm，没有一个清晰的界限，主要还是靠程序员的经验。但是，单靠 Component 的 setState() 来管理所有的状态是有<a href="https://weread.qq.com/web/reader/a0b327005d185ca0b5a7803k6ea321b021d6ea9ab1ba605">极限</a>的！</p><p>上述的状态管理方案，对前端童鞋来说已经 8 是问题了，并且还给出了许多优秀的开源框架：</p><ul><li>redux</li><li>mobx</li><li>vuex</li><li>…</li></ul><h2 id="Android-的状态管理方案"><a href="#Android-的状态管理方案" class="headerlink" title="Android 的状态管理方案"></a>Android 的状态管理方案</h2><p>严格来说，从事 Android 开发的童鞋似乎很少在官方文档或者社区上看到<strong>State Management</strong>相关的介绍，实践就更少了，首先，Android 是命令式的（imperative），通过 <code>setText()</code> 类似的命令式 API 来改变 UI，所以大部分 Android 看到上述 <code>UI = render(data)</code> 这样的公式，都会有点摸不着头脑。然后，在那个还没有 Kotlin 的年代，Android 的主流开发语言是 Java，大家最熟悉的就是 OOP 编程， 什么函数式编程，什么纯函数啊，不可变特性啊。。。天顶星人科技？最后的结果就是，大家在这方面思考的少，而现有的系统API、第三方库甚至自己稍微捣鼓捣鼓，也能解决，所以，Android 的 UI 开发体验一直都不如前端。而我本人，也在近些年的工作学习中不断质疑（接触了前端之后），为毛 Android 这些业务代码写起来就是没有前端的香呢？</p><p>虽然 Android 上没有<strong>状态管理</strong>，但是有类似的东东！</p><h3 id="组件间通信"><a href="#组件间通信" class="headerlink" title="组件间通信"></a>组件间通信</h3><p>Android 采取的思路是基于<strong>模块化</strong>的分冶思路，即 App 只是一个壳，按照业务划分为多个子模块，业务与业务之间隔离。在组件化的工程中，由于我们的业务模块间是物理隔离（模块间无法在编译期间访问各自的代码）的，模块间想获取不属于自己的数据（状态）时就会有问题，例如：</p><ul><li><strong>模块A</strong>里的某个 <code>Activity/Fragment</code> 想获取到<strong>模块B</strong>购物车的信息</li><li><strong>模块C</strong>里的某个 <code>Activity</code> 想跳转到<strong>模块A</strong>的某个 <code>Activity</code></li><li><strong>模块D</strong>里的某个 <code>Activity</code> 想调用<strong>模块E</strong>里的某个<strong>方法</strong></li><li>。。。</li></ul><p>Android 里面对的和”状态管理“类似的往往是这些问题。</p><h4 id="路由库"><a href="#路由库" class="headerlink" title="路由库"></a>路由库</h4><p>下面以阿里的 <a href="https://github.com/alibaba/ARouter">ARouter</a> 为🌰具体进行说明是如何解决<strong>组件间通信</strong>的问题：</p><h5 id="发起路由请求"><a href="#发起路由请求" class="headerlink" title="发起路由请求"></a>发起路由请求</h5><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 跳转并携带参数</span></span><br><span class="line">ARouter.getInstance().build(<span class="string">&quot;/test/1&quot;</span>)</span><br><span class="line">            .withLong(<span class="string">&quot;key1&quot;</span>, <span class="number">666L</span>)</span><br><span class="line">            .withString(<span class="string">&quot;key3&quot;</span>, <span class="string">&quot;888&quot;</span>)</span><br><span class="line">            .withObject(<span class="string">&quot;key4&quot;</span>, <span class="keyword">new</span> <span class="title class_">Test</span>(<span class="string">&quot;Jack&quot;</span>, <span class="string">&quot;Rose&quot;</span>))</span><br><span class="line">            .navigation();</span><br></pre></td></tr></table></figure><h5 id="通过服务注册-发现"><a href="#通过服务注册-发现" class="headerlink" title="通过服务注册&amp;发现"></a>通过服务注册&amp;发现</h5><ol><li>通过依赖注入解耦:服务管理(一) 暴露服务</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 声明接口,其他组件通过接口来调用服务</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">HelloService</span> <span class="keyword">extends</span> <span class="title class_">IProvider</span> &#123;</span><br><span class="line">    String <span class="title function_">sayHello</span><span class="params">(String name)</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="meta">@Route(path = &quot;/yourservicegroupname/hello&quot;, name = &quot;测试服务&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">HelloService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">sayHello</span><span class="params">(String name)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;hello, &quot;</span> + name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">(Context context)</span> &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>通过依赖注入解耦：服务管理(二) 发现服务</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    HelloService helloService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired(name = &quot;/yourservicegroupname/hello&quot;)</span></span><br><span class="line">    HelloService helloService2;</span><br><span class="line"></span><br><span class="line">    HelloService helloService3;</span><br><span class="line"></span><br><span class="line">    HelloService helloService4;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Test</span><span class="params">()</span> &#123;</span><br><span class="line">    ARouter.getInstance().inject(<span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testService</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 1. (推荐)使用依赖注入的方式发现服务,通过注解标注字段,即可使用，无需主动获取</span></span><br><span class="line">    <span class="comment">// Autowired注解中标注name之后，将会使用byName的方式注入对应的字段，不设置name属性，会默认使用byType的方式发现服务(当同一接口有多个实现的时候，必须使用byName的方式发现服务)</span></span><br><span class="line">    helloService.sayHello(<span class="string">&quot;Vergil&quot;</span>);</span><br><span class="line">    helloService2.sayHello(<span class="string">&quot;Vergil&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 2. 使用依赖查找的方式发现服务，主动去发现服务并使用，下面两种方式分别是byName和byType</span></span><br><span class="line">    helloService3 = ARouter.getInstance().navigation(HelloService.class);</span><br><span class="line">    helloService4 = (HelloService) ARouter.getInstance().build(<span class="string">&quot;/yourservicegroupname/hello&quot;</span>).navigation();</span><br><span class="line">    helloService3.sayHello(<span class="string">&quot;Vergil&quot;</span>);</span><br><span class="line">    helloService4.sayHello(<span class="string">&quot;Vergil&quot;</span>);</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>以 <a href="https://github.com/greenrobot/EventBus">EventBus</a> 为代表的事件总线库，简化了使用原生通信的复杂度：</p><ul><li>startActivityForResult &amp; onActivityResult</li><li>Broadcast</li><li>LocalBroadcast</li><li>…</li></ul><p>也看似让我们的代码得到解耦？但是，使用不当的同时也为项目埋下了一颗<a href="https://zhuanlan.zhihu.com/p/26160377">定时💣</a>。</p><h2 id="Android-端-redux-实现"><a href="#Android-端-redux-实现" class="headerlink" title="Android 端 redux 实现"></a>Android 端 redux 实现</h2><p>Android 组件间通信的方案给人的感觉更偏向<strong>通信</strong>而没有<strong>前端状态管理</strong>的那个味儿。那，能不能。。。</p><p>能！</p><p>受到 <a href="https://github.com/johnpryan/redux.dart">redux</a> 这个项目的启发，我用 <code>kotlin&amp;rxjava</code> 也实现了一个 <code>redux</code>，也可以认为和 Android 平台无关的 redux（没有用到 Android 的类），当然之后也可以像 <a href="https://github.com/brianegan/flutter_redux">flutter_redux</a> 一样，包一层 Android 相关的类，实现一个 Android 平台的 redux。</p><h3 id="原理简述"><a href="#原理简述" class="headerlink" title="原理简述"></a>原理简述</h3><p>首先非常感谢 kotlin 和 rxjava，kotlin 和 dart 还是蛮相近的，看下 dart 版的 redux 大概就能想出如果用 kotlin 咋实现的，而对着 JavaScript 写有时候就会😳。而 rxjava 基本可以等价于 flutter stream api，所以，实现一个 kt 版的 redux，难度系数大大降低！</p><ol><li>定义函数</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// store.kt</span></span><br><span class="line"><span class="keyword">typealias</span> Middleware&lt;State&gt; = (store: IStore&lt;State&gt;, action: Any, next: NextDispatcher) -&gt; Any</span><br><span class="line"></span><br><span class="line"><span class="keyword">typealias</span> NextDispatcher = (action: Any) -&gt; Any</span><br><span class="line"></span><br><span class="line"><span class="keyword">typealias</span> Reducer&lt;State&gt; = (state: State, action: Any) -&gt; State</span><br></pre></td></tr></table></figure><ol start="2"><li>定义 Store 接口</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// store.kt</span></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">IStore</span>&lt;<span class="type">State</span>&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> _state: State</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> reducer: Reducer&lt;State&gt;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> middleware: List&lt;Middleware&lt;State&gt;&gt;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> changeController: Subject&lt;State&gt;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">val</span> state: State</span><br><span class="line"></span><br><span class="line">    <span class="keyword">val</span> onChange: Observable&lt;State&gt;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">dispatch</span><span class="params">(action: <span class="type">Any</span>)</span></span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意这几点：</p><ul><li>changeController 使用 Subject<State> 类型</li><li>onChange 使用 Observeable<State> 类型，其实这里我犯了个错，最早的版本用的 aac 里的 LiveData，因为之前一直以为 <del>LiveData≈Subject</del> 。但后来看到了<a href="https://juejin.im/post/5cdff0de5188252f5e019bea">把 LiveData 用于事件传递那些坑</a>，结合源码后，我意识到，出事了，这个场景下 LiveData 是不适用的，它会丢失事件，它就不是被设计出来干这件事的！</li></ul><ol start="3"><li>Store 实现</li></ol><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// store.kt</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Store</span>&lt;<span class="type">State</span>&gt; <span class="keyword">private</span> <span class="keyword">constructor</span>(</span><br><span class="line">    <span class="keyword">override</span> <span class="keyword">var</span> _state: State,</span><br><span class="line">    <span class="keyword">override</span> <span class="keyword">var</span> reducer: Reducer&lt;State&gt;,</span><br><span class="line">    <span class="keyword">override</span> <span class="keyword">var</span> middleware: List&lt;Middleware&lt;State&gt;&gt;,</span><br><span class="line">    <span class="keyword">override</span> <span class="keyword">var</span> changeController: Subject&lt;State&gt;</span><br><span class="line">) : IStore&lt;State&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="keyword">val</span> state: State</span><br><span class="line">        <span class="keyword">get</span>() = _state</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="keyword">val</span> onChange: Observable&lt;State&gt;</span><br><span class="line">        <span class="keyword">get</span>() = changeController</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">var</span> dispatchers: List&lt;NextDispatcher&gt; = createDispatchers(</span><br><span class="line">        middleware, createReduceAndNotify()</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">dispatch</span><span class="params">(action: <span class="type">Any</span>)</span></span> &#123;</span><br><span class="line">        dispatchers[<span class="number">0</span>](action)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">createDispatchers</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">        middleware: <span class="type">List</span>&lt;<span class="type">Middleware</span>&lt;<span class="type">State</span>&gt;&gt;,</span></span></span><br><span class="line"><span class="params"><span class="function">        reduceAndNotify: <span class="type">NextDispatcher</span></span></span></span><br><span class="line"><span class="params"><span class="function">    )</span></span>: List&lt;NextDispatcher&gt; &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">val</span> dispatchers = mutableListOf(reduceAndNotify)</span><br><span class="line"></span><br><span class="line">        middleware.reversed()</span><br><span class="line">            .map &#123; nextMiddleware: Middleware&lt;State&gt; -&gt;</span><br><span class="line">                <span class="keyword">val</span> next = dispatchers.last()</span><br><span class="line">                dispatchers += &#123; action: Any -&gt;</span><br><span class="line">                    nextMiddleware.invoke(</span><br><span class="line">                        <span class="keyword">this</span>,</span><br><span class="line">                        action,</span><br><span class="line">                        next</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="keyword">return</span> dispatchers.reversed()</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">createReduceAndNotify</span><span class="params">()</span></span>: NextDispatcher &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123; action: Any -&gt;</span><br><span class="line">            <span class="keyword">val</span> state = reducer.invoke(_state, action)</span><br><span class="line">            _state = state</span><br><span class="line">            changeController.onNext(state)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">companion</span> <span class="keyword">object</span> &#123;</span><br><span class="line">        <span class="function"><span class="keyword">fun</span> <span class="type">&lt;State&gt;</span> <span class="title">create</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">            initialState: <span class="type">State</span>,</span></span></span><br><span class="line"><span class="params"><span class="function">            reducer: <span class="type">Reducer</span>&lt;<span class="type">State</span>&gt;,</span></span></span><br><span class="line"><span class="params"><span class="function">            middleware: <span class="type">List</span>&lt;<span class="type">Middleware</span>&lt;<span class="type">State</span>&gt;&gt; = emptyList()</span></span>,</span><br><span class="line">            changeController: Subject&lt;State&gt; = PublishSubject.create()</span><br><span class="line">        ): IStore&lt;State&gt; = Store(</span><br><span class="line">            _state = initialState,</span><br><span class="line">            reducer = reducer,</span><br><span class="line">            middleware = middleware,</span><br><span class="line">            changeController = changeController</span><br><span class="line">        )</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>得益于 Kotlin 的函数式特性，这段实现和原版的 dart 相比也没有啰嗦多少😀。这里将 Store 设计为一个不可继承的 class，并且将构造函数的车门也焊死了，创建这个 Store 实例的方法只有 <code>Store#create</code> 函数，通过命名参数，使用者能很清晰的知道传些啥值，不过还是要介绍下 reducer 这个参数。</p><p>reducer:List<Reducer> 合并成的一个 Reducer 对象，这里参考了 JavaScript版 redux 的 combine 函数，贴下代码：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// reducer.kt</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="type">&lt;State&gt;</span> <span class="title">combineReducers</span><span class="params">(reducers: <span class="type">List</span>&lt;<span class="type">Reducer</span>&lt;<span class="type">State</span>&gt;&gt;)</span></span>: Reducer&lt;State&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; state: State, action: Any -&gt;</span><br><span class="line">        reducers.fold(initial = state) &#123; acc: State, func: Reducer&lt;State&gt; -&gt;</span><br><span class="line">            <span class="keyword">return</span><span class="symbol">@fold</span> func(acc, action)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="Demo"><a href="#Demo" class="headerlink" title="Demo"></a>Demo</h1><p>有空再补，算了，还是先补上一些吧。</p><h2 id="AppStore"><a href="#AppStore" class="headerlink" title="AppStore"></a>AppStore</h2><p>创建一个单例 Store（因为该 Store 里的状态是全局的），配置好对应的参数：</p><ul><li>reducer，之前介绍过，略</li><li>middleware，中间件，这里只添加了一个日志中间件</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// AppStore.kt</span></span><br><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">createStore</span><span class="params">()</span></span>: IStore&lt;AppState&gt; = Store.create(</span><br><span class="line">    initialState = AppState(),</span><br><span class="line">    reducer = combineReducers(listOf(countReducer)),</span><br><span class="line">    middleware = listOf(loggerMiddleware)</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AppStore</span> <span class="keyword">private</span> <span class="keyword">constructor</span>(</span><br><span class="line">    store: IStore&lt;AppState&gt; = createStore()</span><br><span class="line">) : IStore&lt;AppState&gt; <span class="keyword">by</span> store &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">companion</span> <span class="keyword">object</span> &#123;</span><br><span class="line">        <span class="keyword">val</span> INSTANCE: AppStore <span class="keyword">by</span> lazy &#123; AppStore() &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li><a href="https://github.com/johnpryan/redux.dart">redux</a></li><li><a href="https://github.com/brianegan/flutter_redux">flutter_redux</a></li><li><a href="https://juejin.im/post/5cdff0de5188252f5e019bea">把 LiveData 用于事件传递那些坑</a></li><li><a href="https://www.cnblogs.com/mengdd/p/flutter-state-management.html">为了弄懂Flutter的状态管理, 我用10种方法改造了counter app</a></li><li><a href="https://weread.qq.com/web/reader/a0b327005d185ca0b5a7803k6ea321b021d6ea9ab1ba605">深入浅出 React 和 Redux</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2020/03/30/kotlin%E5%AE%9E%E7%8E%B0Redux/</id>
    <link href="https://hellovass.github.io/2020/03/30/kotlin%E5%AE%9E%E7%8E%B0Redux/"/>
    <published>2020-03-30T09:19:34.000Z</published>
    <summary>
      <![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>从刚接触 Android 的时候有一些业务场景真的让人蛋疼，虽说不是无解，但问题是解决了，但看着这堆代码总觉得哪里怪怪的。</p>
<p>那么，是什么问题呢？</p>


<div style="position: relative; width: 100%; height: 0; padding-bottom: 75%;">
    <iframe src="//player.bilibili.com/player.html?aid=73040039&bvid=BV1hE41127pY&cid=124939237&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;">
    </iframe>
</div>]]>
    </summary>
    <title>kotlin 实现 redux</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="算法" scheme="https://hellovass.github.io/categories/algorithm/"/>
    <category term="算法" scheme="https://hellovass.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p><a href="https://leetcode-cn.com/problems/valid-parentheses/">有效的括号</a></p><span id="more"></span><h2 id="String-isValid"><a href="#String-isValid" class="headerlink" title="String#isValid"></a>String#isValid</h2><p>定义扩展属性 <code>String#isValid</code>:</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">isValid</span><span class="params">()</span></span> &#123;</span><br><span class="line">    assert(<span class="string">&quot;()&quot;</span>.isValid)</span><br><span class="line">    assert(<span class="string">&quot;()[]&#123;&#125;&quot;</span>.isValid)</span><br><span class="line">    assert(!<span class="string">&quot;(]&quot;</span>.isValid)</span><br><span class="line">    assert(!<span class="string">&quot;([)]&quot;</span>.isValid)</span><br><span class="line">    assert(<span class="string">&quot;&#123;[]&#125;&quot;</span>.isValid)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h2><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="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">isValid</span><span class="params">(s: <span class="type">String</span>)</span></span>: <span class="built_in">Boolean</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> s.isValid</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="解法"><a href="#解法" class="headerlink" title="解法"></a>解法</h1><h2 id="辅助栈法"><a href="#辅助栈法" class="headerlink" title="辅助栈法"></a>辅助栈法</h2><p>这个方法大二的时候数据结构课老师上课有讲过，解这道题非常好用的。</p><ul><li>初始化辅助站 helper</li><li>依次处理表达式的每个括号</li><li>如果遇到开括号，入栈</li><li>如果遇到的是闭括号，出栈</li><li>如果辅助栈不为空，则意味表达式无效</li></ul><p>代码：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 字符串是否有效</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">val</span> String.isValid: <span class="built_in">Boolean</span></span><br><span class="line">    <span class="keyword">get</span>() &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">val</span> helper = Stack&lt;<span class="built_in">Char</span>&gt;()</span><br><span class="line"></span><br><span class="line">        forEach &#123; letter -&gt;</span><br><span class="line">            <span class="keyword">when</span> &#123;</span><br><span class="line">                letter == <span class="string">&#x27;(&#x27;</span> -&gt;</span><br><span class="line">                    helper.push(<span class="string">&#x27;)&#x27;</span>)</span><br><span class="line">                letter == <span class="string">&#x27;[&#x27;</span> -&gt;</span><br><span class="line">                    helper.push(<span class="string">&#x27;]&#x27;</span>)</span><br><span class="line">                letter == <span class="string">&#x27;&#123;&#x27;</span> -&gt;</span><br><span class="line">                    helper.push(<span class="string">&#x27;&#125;&#x27;</span>)</span><br><span class="line">                helper.isEmpty() || letter != helper.pop() -&gt;</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> helper.isEmpty()</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://hellovass.github.io/2020/03/19/leetcode%C2%B7valid-parentheses/</id>
    <link href="https://hellovass.github.io/2020/03/19/leetcode%C2%B7valid-parentheses/"/>
    <published>2020-03-19T15:34:23.000Z</published>
    <summary>
      <![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p><a href="https://leetcode-cn.com/problems/valid-parentheses/">有效的括号</a></p>]]>
    </summary>
    <title>valid-parentheses#20</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="算法" scheme="https://hellovass.github.io/categories/algorithm/"/>
    <category term="算法" scheme="https://hellovass.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p><a href="https://leetcode-cn.com/problems/longest-common-prefix/">最长公共前缀#14</a></p><span id="more"></span><h2 id="Array-findLongestCommonPrefix"><a href="#Array-findLongestCommonPrefix" class="headerlink" title="Array#findLongestCommonPrefix"></a>Array<String>#findLongestCommonPrefix</h2><p>定义扩展方法<code>Array&lt;String&gt;#findLongestCommonPrefix</code>:</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">findLongestCommonPrefix</span><span class="params">()</span></span>: String&#123;</span><br><span class="line">    <span class="comment">// 具体实现</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h2><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">longestCommonPrefix</span><span class="params">()</span></span> &#123;</span><br><span class="line">    assert(arrayOf(<span class="string">&quot;flower&quot;</span>, <span class="string">&quot;flow&quot;</span>, <span class="string">&quot;flight&quot;</span>).findLongestCommonPrefix() == <span class="string">&quot;fl&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="解法"><a href="#解法" class="headerlink" title="解法"></a>解法</h1><ul><li>当字符串数组为空时，直接返回 “”</li><li>令公共字符串前缀 <code>prefix</code> 为 <code>first()</code></li><li>遍历从 1 到 <code>count()</code> 的字符串 <code>cur</code>，然后找出 <code>cur</code> 与 <code>prefix</code> 公共前缀，最终结果即为最长公共前缀</li><li>如果查找过程中 <code>prefix</code> 为空，则不存在公共前缀，直接返回 “”</li><li>时间复杂度：O(s)，s 为所有字符串长度之和</li></ul><p>代码：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 找出最长公共前缀</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> Array<span class="type">&lt;String&gt;</span>.<span class="title">findLongestCommonPrefix</span><span class="params">()</span></span>: String &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (isEmpty())</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> prefix: String = first()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (index <span class="keyword">in</span> <span class="number">1</span> until count()) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">val</span> cur = <span class="keyword">get</span>(index)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (cur.indexOf(prefix) == -<span class="number">1</span>) &#123;</span><br><span class="line"></span><br><span class="line">            prefix = prefix.substring(<span class="number">0</span>, prefix.count() - <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (prefix.isEmpty())</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> prefix</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li><a href="https://leetcode-cn.com/problems/longest-common-prefix/solution/hua-jie-suan-fa-14-zui-chang-gong-gong-qian-zhui-b/">精选题解</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2020/03/19/leetcode%C2%B7longest-common-prefix/</id>
    <link href="https://hellovass.github.io/2020/03/19/leetcode%C2%B7longest-common-prefix/"/>
    <published>2020-03-19T14:42:47.000Z</published>
    <summary>
      <![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p><a href="https://leetcode-cn.com/problems/longest-common-prefix/">最长公共前缀#14</a></p>]]>
    </summary>
    <title>longest-common-prefix#14</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="算法" scheme="https://hellovass.github.io/categories/algorithm/"/>
    <category term="算法" scheme="https://hellovass.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p><a href="https://leetcode-cn.com/problems/roman-to-integer/">罗马数字转整数#13</a></p><span id="more"></span><h2 id="String-roman2Int"><a href="#String-roman2Int" class="headerlink" title="String#roman2Int"></a>String#roman2Int</h2><p>给 <code>String</code> 定义一个私有的扩展方法 <code>roman2Int</code>：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> String.<span class="title">roman2Int</span><span class="params">()</span></span>:<span class="built_in">Int</span>&#123;</span><br><span class="line">    <span class="comment">// 具体实现</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h2><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">romanToInt</span><span class="params">()</span></span> &#123;</span><br><span class="line">    assert(<span class="string">&quot;III&quot;</span>.roman2Int() == <span class="number">3</span>)</span><br><span class="line">    assert(<span class="string">&quot;IV&quot;</span>.roman2Int() == <span class="number">4</span>)</span><br><span class="line">    assert(<span class="string">&quot;IX&quot;</span>.roman2Int() == <span class="number">9</span>)</span><br><span class="line">    assert(<span class="string">&quot;LVIII&quot;</span>.roman2Int() == <span class="number">58</span>)</span><br><span class="line">    assert(<span class="string">&quot;MCMXCIV&quot;</span>.roman2Int() == <span class="number">1994</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="解法"><a href="#解法" class="headerlink" title="解法"></a>解法</h1><p>由题目可以得出：</p><ul><li>罗马数字由 <code>I、V、X、L、C、D、M</code> 构成</li><li>当小值在大值得左边，则减小值</li><li>当小值在大值的右边，则加小值</li></ul><blockquote><p>由以上结论可以得出，右值永远为正，最后一位必为正</p></blockquote><p>码表：</p><table><thead><tr><th align="left">key</th><th>value</th></tr></thead><tbody><tr><td align="left">I</td><td>1</td></tr><tr><td align="left">V</td><td>5</td></tr><tr><td align="left">X</td><td>10</td></tr><tr><td align="left">L</td><td>50</td></tr><tr><td align="left">C</td><td>100</td></tr><tr><td align="left">D</td><td>500</td></tr><tr><td align="left">M</td><td>1000</td></tr></tbody></table><p>代码实现小技巧，从第1位开始遍历，比较 <code>cur</code> 和 <code>pre</code> 的大小，从而确定是做加法还是减法，然后更新 pre 的值。只有最后一位时，做加法。</p><p>代码如下：</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><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 class="comment">/**</span></span><br><span class="line"><span class="comment"> * 罗马数转换为十进制</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> String.<span class="title">roman2Int</span><span class="params">()</span></span>: <span class="built_in">Int</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">val</span> dictionary = hashMapOf(</span><br><span class="line">        <span class="string">&#x27;I&#x27;</span> to <span class="number">1</span>,</span><br><span class="line">        <span class="string">&#x27;V&#x27;</span> to <span class="number">5</span>,</span><br><span class="line">        <span class="string">&#x27;X&#x27;</span> to <span class="number">10</span>,</span><br><span class="line">        <span class="string">&#x27;L&#x27;</span> to <span class="number">50</span>,</span><br><span class="line">        <span class="string">&#x27;C&#x27;</span> to <span class="number">100</span>,</span><br><span class="line">        <span class="string">&#x27;D&#x27;</span> to <span class="number">500</span>,</span><br><span class="line">        <span class="string">&#x27;M&#x27;</span> to <span class="number">1000</span></span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> sum = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> prev: <span class="built_in">Int</span> = dictionary[first()] ?: <span class="keyword">throw</span> IllegalStateException()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (index <span class="keyword">in</span> <span class="number">1</span> until length) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">val</span> cur = dictionary[<span class="keyword">get</span>(index)] ?: <span class="keyword">throw</span> IllegalStateException()</span><br><span class="line"></span><br><span class="line">        <span class="keyword">when</span> &#123;</span><br><span class="line">            cur &gt; prev -&gt;</span><br><span class="line">                sum -= prev</span><br><span class="line">            <span class="keyword">else</span> -&gt;</span><br><span class="line">                sum += prev</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        prev = cur</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    sum += prev</span><br><span class="line">    <span class="keyword">return</span> sum</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li><a href="https://leetcode-cn.com/problems/roman-to-integer/solution/yong-shi-9993nei-cun-9873jian-dan-jie-fa-by-donesp/">题解精选</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2020/03/16/leetcode%C2%B7roman-to-integer/</id>
    <link href="https://hellovass.github.io/2020/03/16/leetcode%C2%B7roman-to-integer/"/>
    <published>2020-03-16T14:43:30.000Z</published>
    <summary>
      <![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p><a href="https://leetcode-cn.com/problems/roman-to-integer/">罗马数字转整数#13</a></p>]]>
    </summary>
    <title>roman-to-integer#13</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="算法" scheme="https://hellovass.github.io/categories/algorithm/"/>
    <category term="算法" scheme="https://hellovass.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p><a href="https://leetcode-cn.com/problems/palindrome-number/">回文数#2</a></p><span id="more"></span><h2 id="Int-isPalindrome"><a href="#Int-isPalindrome" class="headerlink" title="Int#isPalindrome"></a>Int#isPalindrome</h2><p>给 <code>Int</code> 定义一个私有的扩展方法 <code>isPalindrome</code>:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Solution9.kt</span></span><br><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="built_in">Int</span>.<span class="title">isPalindrome1</span><span class="params">()</span></span>: <span class="built_in">Boolean</span> &#123;</span><br><span class="line">    <span class="comment">// 具体实现</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h2><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">test</span><span class="params">()</span></span> &#123;</span><br><span class="line">    assert(<span class="number">1234321.</span>isPalindrome1())</span><br><span class="line">    assert(!(-<span class="number">121</span>).isPalindrome2())</span><br><span class="line">    assert(!<span class="number">10.</span>isPalindrome3())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="解法"><a href="#解法" class="headerlink" title="解法"></a>解法</h1><h2 id="暴力解法"><a href="#暴力解法" class="headerlink" title="暴力解法"></a>暴力解法</h2><p>通过 kotlin 提供的 api，我们可以这样：</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="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="built_in">Int</span>.<span class="title">isPalindrome3</span><span class="params">()</span></span>: <span class="built_in">Boolean</span> &#123;</span><br><span class="line">       <span class="keyword">return</span> toString().let &#123;</span><br><span class="line">           it == it.reversed()</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><h2 id="解法2"><a href="#解法2" class="headerlink" title="解法2"></a>解法2</h2><p>通过取整和取余操作获取整数对应的数字逐一进行比较。</p><p>举🌰，1221：</p><ul><li>1221 &#x2F; 1000，得到首位 1</li><li>1221 % 10，得到末尾 1</li><li>首位和末尾比较</li><li>再将 22 取出来继续上述操作</li></ul><p>动画：</p><p><img src="https://pic.leetcode-cn.com/6df9cbf08ef47a1761e7426aab48228a8dcfc9c5f89c82b44148ad0e24efe511-file_1558924390360" alt="动画演示"></p><p>代码：</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><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 解法 1</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="built_in">Int</span>.<span class="title">isPalindrome1</span><span class="params">()</span></span>: <span class="built_in">Boolean</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">this</span> &lt; <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> num = <span class="keyword">this</span></span><br><span class="line">    <span class="keyword">var</span> div = <span class="number">1</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (num / div &gt;= <span class="number">10</span>)</span><br><span class="line">        div *= <span class="number">10</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (num &gt; <span class="number">0</span>) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">val</span> left = num / div</span><br><span class="line">        <span class="keyword">val</span> right = num % <span class="number">10</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (left != right)</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line">        num = (num % div) / <span class="number">10</span></span><br><span class="line">        div /= <span class="number">100</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="解法2的进阶版——解法3"><a href="#解法2的进阶版——解法3" class="headerlink" title="解法2的进阶版——解法3"></a>解法2的进阶版——解法3</h2><p>回文数，将数字对折后看能否一一对应。</p><p>所以这个解法的操作就是<strong>取出后半段数字进行翻转</strong></p><p>注意，回文数的位数存在奇数和偶数的情况：</p><ul><li>如果长度为偶数，对折过来应该是相等的</li><li>如果长度为奇数，对折过来后，长的需要去掉一位数（除以 10 并取整）</li></ul><p>具体做法如下：</p><ul><li>每次进行取余操作（%10），取出最低位：<code>y = x % 10</code></li><li>将最低位数字加到取出数的末尾：<code>revertNum = revertNum * 10 + y</code></li><li>每取一个最低位数字，<code>x</code> 都要除以 10</li><li>判断 <code>x</code> 是不是小于 <code>revertNum</code>，如果小于，则说明数字已经对半或者过半了</li><li>最后，判断奇偶情况：如果是偶数，<code>revertNum</code> 和 <code>x</code> 相等；否则，最中间的数字就在 <code>revertNum</code> 的最低位上，将它除以 10 后应该和 <code>x</code> 相等</li></ul><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 解法2</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="built_in">Int</span>.<span class="title">isPalindrome2</span><span class="params">()</span></span>: <span class="built_in">Boolean</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((<span class="keyword">this</span> &lt; <span class="number">0</span>) || (<span class="keyword">this</span> != <span class="number">0</span> &amp;&amp; <span class="keyword">this</span> % <span class="number">10</span> == <span class="number">0</span>))</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> num = <span class="keyword">this</span></span><br><span class="line">    <span class="keyword">var</span> revertedNum = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (num &gt; revertedNum) &#123;</span><br><span class="line">        revertedNum = revertedNum * <span class="number">10</span> + num % <span class="number">10</span></span><br><span class="line">        num /= <span class="number">10</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> num == revertedNum || num == revertedNum / <span class="number">10</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li><a href="https://leetcode-cn.com/problems/palindrome-number/solution/dong-hua-hui-wen-shu-de-san-chong-jie-fa-fa-jie-ch/">精选题解</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2020/03/16/leetcode%C2%B7palindrome-number/</id>
    <link href="https://hellovass.github.io/2020/03/16/leetcode%C2%B7palindrome-number/"/>
    <published>2020-03-16T07:50:15.000Z</published>
    <summary>
      <![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p><a href="https://leetcode-cn.com/problems/palindrome-number/">回文数#2</a></p>]]>
    </summary>
    <title>palindrome-number#9</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="算法" scheme="https://hellovass.github.io/categories/algorithm/"/>
    <category term="算法" scheme="https://hellovass.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p><a href="https://leetcode-cn.com/problems/reverse-integer/">整数反转#7</a></p><span id="more"></span><h2 id="Int-reverse"><a href="#Int-reverse" class="headerlink" title="Int#reverse"></a>Int#reverse</h2><p>给<code>Int</code>定义一个私有的扩展方法<code>reverse</code>：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Solution7.kt</span></span><br><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="built_in">Int</span>.<span class="title">reverse</span><span class="params">()</span></span>: <span class="built_in">Int</span> &#123;</span><br><span class="line">    <span class="comment">// 具体实现</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h2><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Soulution7.kt</span></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">reverse</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="comment">// &quot;123&quot; 反转之后为 &quot;321&quot;</span></span><br><span class="line">    assert(<span class="number">123.</span>reverse() == <span class="number">321</span>)</span><br><span class="line">    <span class="comment">// &quot;-123&quot; 反转之后为 &quot;-321&quot;</span></span><br><span class="line">    assert((-<span class="number">123</span>).reverse() == -<span class="number">321</span>)</span><br><span class="line">    <span class="comment">// &quot;120&quot; 反转之后为 &quot;21&quot;</span></span><br><span class="line">    assert((<span class="number">120</span>).reverse() == <span class="number">21</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="解法"><a href="#解法" class="headerlink" title="解法"></a>解法</h1><p>反转整数可以与反转字符串进行类比。</p><p>我们想重复<strong>弹出</strong><code>num</code>的最后一位数字，并将它<strong>推入</strong>到<code>result</code>的后面。最后，<code>result</code>与<code>num</code>相反。</p><p>要在没有辅助堆栈&#x2F;数组的帮助下<strong>弹出</strong>和<strong>推入</strong>数字，我们可以使用数学方法。</p><h2 id="数学方法"><a href="#数学方法" class="headerlink" title="数学方法"></a>数学方法</h2><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// pop</span></span><br><span class="line">pop = num % <span class="number">10</span></span><br><span class="line">num /= <span class="number">10</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// push</span></span><br><span class="line">temp = result * <span class="number">10</span> + pop</span><br><span class="line">result = temp</span><br></pre></td></tr></table></figure><p>但是，这种方法很危险，因为当<code>temp = result*10 + pop</code>时会导致溢出。</p><p>假设<code>result</code>是正整数：</p><ul><li>如果<code>temp = result*10 + pop</code>导致溢出，那么<code>result &gt;= Int.MAX_VALUE/10</code></li><li>如果<code>result &gt; Int.MAX_VALUE/10</code>，那么<code>temp = result * 10 + pop</code>一定会溢出</li><li>如果<code>result = Int.MAX_VALUE/10</code>，那么只要<code>pop &gt; 7，temp = result * 10 + pop</code>就会溢出</li></ul><p>当<code>result</code>为负整数时同理。</p><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><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><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 整数反转</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="built_in">Int</span>.<span class="title">reverse</span><span class="params">()</span></span>: <span class="built_in">Int</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 要反转的整数</span></span><br><span class="line">    <span class="keyword">var</span> value = <span class="keyword">this</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 结果</span></span><br><span class="line">    <span class="keyword">var</span> result = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (value != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 计算进位，注意 pop 是带符号的</span></span><br><span class="line">        <span class="keyword">val</span> pop = value % <span class="number">10</span></span><br><span class="line">        <span class="keyword">when</span> &#123;</span><br><span class="line">            <span class="comment">// 7 是 Int.MAX_VALUE 的第2位</span></span><br><span class="line">            (result &gt; <span class="built_in">Int</span>.MAX_VALUE / <span class="number">10</span>) || (result == <span class="built_in">Int</span>.MAX_VALUE / <span class="number">10</span> &amp;&amp; pop &gt; <span class="number">7</span>) -&gt;</span><br><span class="line">                <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">            <span class="comment">// 8 是 Int.MIN_VALUE 的第2位</span></span><br><span class="line">            (result &lt; <span class="built_in">Int</span>.MIN_VALUE / <span class="number">10</span>) || (result == <span class="built_in">Int</span>.MIN_VALUE / <span class="number">10</span> &amp;&amp; pop &lt; -<span class="number">8</span>) -&gt;</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">        result = result * <span class="number">10</span> + pop</span><br><span class="line">        value /= <span class="number">10</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> result</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li><a href="https://leetcode-cn.com/problems/reverse-integer/solution/hua-jie-suan-fa-7-zheng-shu-fan-zhuan-by-guanpengc/">题解1</a></li><li><a href="https://leetcode-cn.com/problems/reverse-integer/solution/zheng-shu-fan-zhuan-by-leetcode/">题解2</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2020/03/15/leetcode%C2%B7reverse-integer/</id>
    <link href="https://hellovass.github.io/2020/03/15/leetcode%C2%B7reverse-integer/"/>
    <published>2020-03-15T15:16:41.000Z</published>
    <summary>
      <![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p><a href="https://leetcode-cn.com/problems/reverse-integer/">整数反转#7</a></p>]]>
    </summary>
    <title>reverse-integer#7</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="算法" scheme="https://hellovass.github.io/categories/algorithm/"/>
    <category term="算法" scheme="https://hellovass.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p><a href="https://leetcode-cn.com/problems/add-two-numbers/">两数相加#2</a></p><span id="more"></span><h2 id="addTwoNumbers"><a href="#addTwoNumbers" class="headerlink" title="addTwoNumbers"></a>addTwoNumbers</h2><p>定义方法<code>#addTwoNumbers</code>:</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">addTwoNumbers</span><span class="params">(l1: <span class="type">ListNode</span>?, l2: <span class="type">ListNode</span>?)</span></span>: ListNode? &#123;</span><br><span class="line">    <span class="comment">// 具体实现</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> num1 = listOf(<span class="number">2</span>, <span class="number">4</span>, <span class="number">3</span>).toLinkedList()</span><br><span class="line"><span class="keyword">val</span> num2 = listOf(<span class="number">5</span>, <span class="number">6</span>, <span class="number">4</span>).toLinkedList()</span><br><span class="line"><span class="keyword">val</span> sum = addTwoNumbers(num1, num2)</span><br><span class="line">assert(<span class="string">&quot;<span class="subst">$&#123;sum.toList()&#125;</span>&quot;</span> == <span class="string">&quot;[7, 0, 8]&quot;</span>)</span><br></pre></td></tr></table></figure><h1 id="解法"><a href="#解法" class="headerlink" title="解法"></a>解法</h1><ul><li>将 2个链表看成长度相同的链表进行遍历，如果一个链表较短，则在前面补 0，比如 <code>987 + 23 = 987 + 023 = 1010</code></li><li>每一位的计算考虑上一位的进位，当前位计算结束后更新进位</li><li>两个链表遍历完成后，进位为 1，则在新链表最前放添加节点 1</li></ul><blockquote><p>小技巧：对于链表问题，返回结果为头结点时，通常需要先初始化一个预指针 pre，该指针的下一个节点指向真正的头节点 head。<br>使用预指针的目的在于，链表初始化时无可用节点，而链表构造过程需要移动指针，进而导致头指针丢失，无法返回结果。</p></blockquote><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><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><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 两数相加</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">addTwoNumbers</span><span class="params">(l1: <span class="type">ListNode</span>?, l2: <span class="type">ListNode</span>?)</span></span>: ListNode? &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 初始化一个预指针</span></span><br><span class="line">    <span class="keyword">val</span> pre: ListNode? = ListNode(<span class="number">0</span>)</span><br><span class="line">    <span class="keyword">var</span> cur: ListNode? = pre</span><br><span class="line">    <span class="keyword">var</span> carry = <span class="number">0</span> <span class="comment">// 进位</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> num1 = l1</span><br><span class="line">    <span class="keyword">var</span> num2 = l2</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (num1 != <span class="literal">null</span> || num2 != <span class="literal">null</span>) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">val</span> x = <span class="keyword">when</span> (num1 == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="literal">true</span> -&gt; <span class="number">0</span></span><br><span class="line">            <span class="keyword">else</span> -&gt; num1.`<span class="keyword">val</span>`</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">val</span> y = <span class="keyword">when</span> (num2 == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="literal">true</span> -&gt; <span class="number">0</span></span><br><span class="line">            <span class="keyword">else</span> -&gt; num2.`<span class="keyword">val</span>`</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">var</span> sum = x + y + carry</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 计算进位</span></span><br><span class="line">        carry = sum / <span class="number">10</span></span><br><span class="line">        <span class="comment">// 计算和</span></span><br><span class="line">        sum %= <span class="number">10</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 将结果串联</span></span><br><span class="line">        cur?.next = ListNode(sum)</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 移动 cur 指针</span></span><br><span class="line">        cur = cur?.next</span><br><span class="line"></span><br><span class="line">        num1?.let &#123;</span><br><span class="line">            num1 = it.next</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        num2?.let &#123;</span><br><span class="line">            num2 = it.next</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (carry == <span class="number">1</span>) &#123;</span><br><span class="line">        cur?.next = ListNode(carry)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回 pre 的下一个节点</span></span><br><span class="line">    <span class="keyword">return</span> pre?.next</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li><a href="https://leetcode-cn.com/problems/add-two-numbers/solution/hua-jie-suan-fa-2-liang-shu-xiang-jia-by-guanpengc/">精选题解</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2020/03/11/leetcode%C2%B7add-two-numbers/</id>
    <link href="https://hellovass.github.io/2020/03/11/leetcode%C2%B7add-two-numbers/"/>
    <published>2020-03-11T16:28:06.000Z</published>
    <summary>
      <![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p><a href="https://leetcode-cn.com/problems/add-two-numbers/">两数相加#2</a></p>]]>
    </summary>
    <title>add-two-numbers#2</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="算法" scheme="https://hellovass.github.io/categories/algorithm/"/>
    <category term="算法" scheme="https://hellovass.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p><a href="https://leetcode-cn.com/problems/reverse-linked-list/">反转单链表#206</a></p><span id="more"></span><h1 id="工程化"><a href="#工程化" class="headerlink" title="工程化"></a>工程化</h1><p><code>leetcode</code>提交运行等待结果是一件很漫长的事情，而我们从编写代码到<code>debug</code>成功期间可能会失败 N次，所以本地有一个方便我们运行测试的环境还是蛮重要的。</p><p>这里我选择建立了一个<a href="https://github.com/HelloVass/leetcode_resolver">工程</a>，将题解代码放在里面。</p><p><code>leetcode</code>题解调试过程中自定义的数据结构、方法有很多都是可以复用的，</p><p>所以，我们尽可能地复（tou）用（lan）。</p><h2 id="节点类定义"><a href="#节点类定义" class="headerlink" title="节点类定义"></a>节点类定义</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ListNode</span>(<span class="keyword">var</span> `<span class="keyword">val</span>`: <span class="built_in">Int</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> next: ListNode? = <span class="literal">null</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>吐槽，<code>val</code>与<code>kotlin</code>的关键字冲突，所以要通过` `来转义(😢)。</p></blockquote><h2 id="ListNode-reverse"><a href="#ListNode-reverse" class="headerlink" title="ListNode?#reverse"></a>ListNode?#reverse</h2><p>kotlin 基操，给<code>ListNode?</code>定义一个扩展方法<code>reverse</code>，将具体的实现写在这里。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ListNode.kt</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> ListNode?.<span class="title">reverse</span><span class="params">()</span></span>: ListNode? &#123;</span><br><span class="line">    <span class="comment">// 这里给出具体的代码实现</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h2><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Solution206.kt</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution206</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">reverse</span><span class="params">()</span></span> &#123;</span><br><span class="line">        <span class="comment">// 从顺序表生成一个单链表</span></span><br><span class="line">        <span class="keyword">val</span> head = listOf(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>).toLinkedList()</span><br><span class="line">        <span class="comment">// 反转单链表，得到新的头结点</span></span><br><span class="line">        <span class="keyword">val</span> newHead = head.reverse()</span><br><span class="line">        <span class="comment">// 打印链表</span></span><br><span class="line">        newHead.toList().forEach &#123;</span><br><span class="line">            print(<span class="string">&quot;<span class="variable">$it</span>&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里用到了两个我自定义的扩展方法：</p><ul><li>List#toLinkedList，将<code>List</code>转换为单链表</li><li>ListNode#toList，将单链表转换为<code>List</code></li></ul><blockquote><p>PS: 目前题解还不多，日后完善了可能会开源回馈社区</p></blockquote><h1 id="解法"><a href="#解法" class="headerlink" title="解法"></a>解法</h1><h2 id="双指针迭代法"><a href="#双指针迭代法" class="headerlink" title="双指针迭代法"></a>双指针迭代法</h2><p>要反转链表，核心就是将节点的<code>next</code>指针指向前驱<br>但当前节点无法访问<strong>过去的节点</strong>（前驱）<br>因此需要保存前驱节点</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8/%E5%8F%8D%E8%BD%AC%E5%8D%95%E9%93%BE%E8%A1%A8.gif" alt="反转单链表"></p><p>申请两个指针：</p><ul><li><code>prev</code>，最初指向<code>NULL</code>；<code>cur</code>，最初指向<code>HEAD</code></li><li>不断遍历<code>cur</code>，每次迭代到<code>cur</code>，将<code>cur.next -&gt; prev</code>（指针反指），然后<code>prev</code>和<code>cur</code>前进一格</li><li><code>cur</code>迭代完成（<code>cur</code>为<code>NULL</code>），<code>prev</code>所在的就是最后一个节点</li></ul><p>核心代码如下：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span>(cur != <span class="literal">null</span>)&#123;</span><br><span class="line">    <span class="comment">// 先保存`cur`的下一个节点，否则&quot;prev&quot;、&quot;cur&quot;前进的时找不到下个节点</span></span><br><span class="line">    <span class="keyword">val</span> next = cur.next</span><br><span class="line">    cur.next = prev</span><br><span class="line">    prev = cur</span><br><span class="line">    cur = next</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="reverse-实现"><a href="#reverse-实现" class="headerlink" title="reverse 实现"></a>reverse 实现</h3><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> ListNode?.<span class="title">reverse</span><span class="params">()</span></span>: ListNode? &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">this</span> == <span class="literal">null</span> || next == <span class="literal">null</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> prev: ListNode? = <span class="literal">null</span></span><br><span class="line">    <span class="keyword">var</span> cur: ListNode? = <span class="keyword">this</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (cur != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">val</span> next = cur.next</span><br><span class="line">        cur.next = prev</span><br><span class="line">        prev = cur</span><br><span class="line">        cur = next</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> prev</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="边界情况"><a href="#边界情况" class="headerlink" title="边界情况"></a>边界情况</h3><p><code>kotlin</code> 在语言层面支持空安全，结合<code>IDE</code>的提醒，时刻提醒我们注意边界！</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 如果当前链表为空或者只有一个节点，直接返回自身</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">this</span> == <span class="literal">null</span> || next == <span class="literal">null</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span></span><br></pre></td></tr></table></figure><h2 id="递归解法"><a href="#递归解法" class="headerlink" title="递归解法"></a>递归解法</h2><p>TODO</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li><a href="https://leetcode-cn.com/problems/reverse-linked-list/solution/dong-hua-yan-shi-206-fan-zhuan-lian-biao-by-user74/">题解1</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2020/03/10/leetcode%C2%B7reverse-linked-list/</id>
    <link href="https://hellovass.github.io/2020/03/10/leetcode%C2%B7reverse-linked-list/"/>
    <published>2020-03-10T15:00:46.000Z</published>
    <summary>
      <![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p><a href="https://leetcode-cn.com/problems/reverse-linked-list/">反转单链表#206</a></p>]]>
    </summary>
    <title>reverse-linked-list#206</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="面试" scheme="https://hellovass.github.io/categories/interview/"/>
    <category term="面试" scheme="https://hellovass.github.io/tags/%E9%9D%A2%E8%AF%95/"/>
    <content>
      <![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>面试中经常会被问到这样一个问题或者它的类似问题。</p><blockquote><p>你如何用<strong>程序的方式检测</strong>导致<strong>UI卡顿</strong>的问题，并且<strong>定位</strong>？</p></blockquote><span id="more"></span><h1 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h1><p>我个人觉得这其实是一个还不错的综合体，考察点也还算多，也可以衍生出一些题目。</p><p>先来拆解一下关键词，<strong>程序的方式检测</strong>、<strong>UI卡顿</strong>、<strong>定位</strong>：</p><ul><li>程序的方式检测：不是通过图形工具、不是通过交互来检测，而是通过编写工具类在应用运行时来检测</li><li>UI卡顿：UI线程做了一些耗时的操作</li><li>定位：通过日志打印出耗时超过阈值的方法</li></ul><h1 id="技术补完"><a href="#技术补完" class="headerlink" title="技术补完"></a>技术补完</h1><h2 id="StackTrace-StackTraceElement"><a href="#StackTrace-StackTraceElement" class="headerlink" title="StackTrace&amp;StackTraceElement"></a>StackTrace&amp;StackTraceElement</h2><p>先来<code>堆栈追踪</code>部分的知识点，因为这块 api 用的多，但了解少。</p><h3 id="StackTrace"><a href="#StackTrace" class="headerlink" title="StackTrace"></a>StackTrace</h3><p>StackTrace（堆栈跟踪），用栈的形式保存方法的调用信息。</p><p>这个其实我们一点也不陌生，在处理异常的时候，经常会这么写一句：<code>e.printStackTrace()</code>，即打印调用异常的堆栈信息，方便我们在异常发生的时候能定位到具体的问题。</p><h3 id="StackTraceElement"><a href="#StackTraceElement" class="headerlink" title="StackTraceElement"></a>StackTraceElement</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * An element in a stack trace, as returned by &#123;<span class="doctag">@link</span></span></span><br><span class="line"><span class="comment"> * Throwable#getStackTrace()&#125;.  Each element represents a single stack frame.</span></span><br><span class="line"><span class="comment"> * All stack frames except for the one at the top of the stack represent</span></span><br><span class="line"><span class="comment"> * a method invocation.  The frame at the top of the stack represents the</span></span><br><span class="line"><span class="comment"> * execution point at which the stack trace was generated.  Typically,</span></span><br><span class="line"><span class="comment"> * this is the point at which the throwable corresponding to the stack trace</span></span><br><span class="line"><span class="comment"> * was created.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span>  1.4</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Josh Bloch</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">StackTraceElement</span> <span class="keyword">implements</span> <span class="title class_">java</span>.io.Serializable &#123;</span><br><span class="line">    <span class="comment">// 省略 N行</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>获取<code>StackTraceElement</code>的方法有2种，返回值为<code>StackTraceElement[]</code>。</p><ul><li>Thread#getStackTrace()，常见代码<code>Thread.currentThread().getStackTrace()</code></li><li>Throwable#getStackTrace()</li></ul><p><code>StackTraceElement[]</code>包含了<code>StackTrace</code>的内容，遍历它可以得到方法见的调用过程，即可以得到当前方法以及其调用者的方法名、调用行数等信息。</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><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// main 函数入口</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    funA()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法A</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">funA</span><span class="params">()</span></span> &#123;</span><br><span class="line">    println(<span class="string">&quot;---&gt;&gt;&gt; enter funA&quot;</span>)</span><br><span class="line">    funB()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法B</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">funB</span><span class="params">()</span></span> &#123;</span><br><span class="line">    println(<span class="string">&quot;---&gt;&gt;&gt; enter funB&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">val</span> elements = Thread.currentThread().stackTrace</span><br><span class="line"></span><br><span class="line">    elements.forEachIndexed &#123; index, e -&gt;</span><br><span class="line">        <span class="keyword">val</span> className = e.className</span><br><span class="line">        <span class="keyword">val</span> funcName = e.methodName</span><br><span class="line">        <span class="keyword">val</span> fileName = e.fileName</span><br><span class="line">        <span class="keyword">val</span> lineNum = e.lineNumber</span><br><span class="line">        println(</span><br><span class="line">            <span class="string">&quot;---&gt;&gt;&gt; StackTraceElement index=&gt;<span class="variable">$index</span>,&quot;</span> +</span><br><span class="line">                    <span class="string">&quot; fileName=&gt;<span class="variable">$fileName</span>, &quot;</span> +</span><br><span class="line">                    <span class="string">&quot;className=&gt;<span class="variable">$className</span>, &quot;</span> +</span><br><span class="line">                    <span class="string">&quot;funcName=&gt;<span class="variable">$funcName</span>, &quot;</span> +</span><br><span class="line">                    <span class="string">&quot;lineNum=&gt;<span class="variable">$lineNum</span>&quot;</span></span><br><span class="line">        )</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出，如图：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/StackTrace%E8%A1%8D%E7%94%9F%E7%AF%87/StackTraceElement%E4%BD%BF%E7%94%A8.png" alt="StackTraceElement使用"></p><h2 id="Looper"><a href="#Looper" class="headerlink" title="Looper"></a>Looper</h2><p>主线程里有个<code>Looper</code>，在<code>loop</code>方法中会不断取出<code>Message</code>，调用其绑定的 Handler 在主线程执行。</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></pre></td><td class="code"><pre><span class="line">loop()&#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">Looper</span> <span class="variable">me</span> <span class="operator">=</span> myLooper();</span><br><span class="line">    <span class="keyword">final</span> <span class="type">MessageQueue</span> <span class="variable">queue</span> <span class="operator">=</span> me.mQueue;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略 N行</span></span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// might block</span></span><br><span class="line">        <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> queue.next(); </span><br><span class="line"></span><br><span class="line">        <span class="comment">// This must be in a local variable, in case a UI event sets the logger</span></span><br><span class="line">        <span class="keyword">final</span> <span class="type">Printer</span> <span class="variable">logging</span> <span class="operator">=</span> me.mLogging;</span><br><span class="line">        <span class="keyword">if</span> (logging != <span class="literal">null</span>) &#123;</span><br><span class="line">            logging.println(<span class="string">&quot;&gt;&gt;&gt;&gt;&gt; Dispatching to &quot;</span> + msg.target + <span class="string">&quot; &quot;</span> +</span><br><span class="line">                msg.callback + <span class="string">&quot;: &quot;</span> + msg.what);</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">        msg.target.dispatchMessage(msg);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (logging != <span class="literal">null</span>) &#123;</span><br><span class="line">            logging.println(<span class="string">&quot;&lt;&lt;&lt;&lt;&lt; Finished to &quot;</span> + msg.target + <span class="string">&quot; &quot;</span> + msg.callback);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 省略 N行</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关键点，我们只要有办法检测：<code>msg.target.dispatchMessage(msg)</code>的执行时间，判断执行时间是否大于我们设置的某个阈值就可以判断UI线程是否有耗时操作了。此代码执行前后，如果设置了<code>logging</code>，会分别打印出:<code>&gt;&gt;&gt;&gt;&gt; Dispatching to</code>和 <code>&lt;&lt;&lt;&lt;&lt; Finished to </code> 这样的日志。</p><h1 id="实操"><a href="#实操" class="headerlink" title="实操"></a>实操</h1><h2 id="BlockCanary"><a href="#BlockCanary" class="headerlink" title="BlockCanary"></a>BlockCanary</h2><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">val</span> START = <span class="string">&quot;&gt;&gt;&gt;&gt;&gt; Dispatching&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">val</span> END = <span class="string">&quot;&lt;&lt;&lt;&lt;&lt; Finished&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BlockCanary</span> <span class="keyword">private</span> <span class="keyword">constructor</span>() &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">companion</span> <span class="keyword">object</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">fun</span> <span class="title">start</span><span class="params">()</span></span> &#123;</span><br><span class="line">            Looper.getMainLooper()</span><br><span class="line">                .setMessageLogging &#123; str: String -&gt;</span><br><span class="line">                    <span class="keyword">when</span> (str) &#123;</span><br><span class="line">                        START -&gt;</span><br><span class="line">                            LoggerMonitor.INSTANCE.start()</span><br><span class="line">                        END -&gt;</span><br><span class="line">                            LoggerMonitor.INSTANCE.stop()</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></pre></td></tr></table></figure><h2 id="LoggerMonitor"><a href="#LoggerMonitor" class="headerlink" title="LoggerMonitor"></a>LoggerMonitor</h2><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><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">val</span> THRESHOLD = <span class="number">1</span> * <span class="number">1000L</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LoggerMonitor</span> <span class="keyword">private</span> <span class="keyword">constructor</span>() &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">val</span> loggerThread: HandlerThread = HandlerThread(<span class="string">&quot;logger&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">val</span> ioHandler: Handler</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">val</span> runnable = Runnable &#123;</span><br><span class="line">        <span class="keyword">val</span> log = buildString &#123;</span><br><span class="line">            <span class="keyword">val</span> stackTrace = Looper.getMainLooper().thread.stackTrace</span><br><span class="line">            stackTrace.forEach &#123; e: StackTraceElement -&gt;</span><br><span class="line">                append(<span class="string">&quot;<span class="variable">$e</span>\n&quot;</span>)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        Log.e(<span class="string">&quot;TAG&quot;</span>, log)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">val</span> isMonitor: <span class="built_in">Boolean</span></span><br><span class="line">        <span class="keyword">get</span>() = ioHandler.hasCallbacks(runnable)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">init</span> &#123;</span><br><span class="line">        loggerThread.start()</span><br><span class="line">        ioHandler = Handler(loggerThread.looper)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">companion</span> <span class="keyword">object</span> &#123;</span><br><span class="line">        <span class="keyword">val</span> INSTANCE <span class="keyword">by</span> lazy &#123; LoggerMonitor() &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">start</span><span class="params">()</span></span> &#123;</span><br><span class="line">        ioHandler.postDelayed(runnable, THRESHOLD)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">stop</span><span class="params">()</span></span> &#123;</span><br><span class="line">        ioHandler.removeCallbacks(runnable)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>假设我们的阈值设置为 1000ms，当<code>str</code>匹配到<code>&gt;&gt;&gt; Dispatching</code>时，调用<code>LoggerMonitor.INSTANCE.start()</code>，在 1000ms后执行一个任务，打印出UI线程的堆栈信息（这次在子线程中执行）。正常情况，我们的<code>msg.target.dispatchMessage(msg)</code>执行耗时小于 1000ms，所以<code>str</code>匹配到<code>&lt;&lt;&lt;&lt;&lt; Finished</code>，就会调用<code>LoggerMonitor.INSTANCE.remove()</code>移除任务。</p><p>这里还用到了 HandlerThread，也利用了 Looper，只不过这个 Looper 在子线程中。</p><h2 id="测试环节"><a href="#测试环节" class="headerlink" title="测试环节"></a>测试环节</h2><h3 id="Application-onCreate"><a href="#Application-onCreate" class="headerlink" title="Application#onCreate"></a>Application#onCreate</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">DemoApplication</span> : <span class="type">Application</span>() &#123;</span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onCreate</span><span class="params">()</span></span> &#123;</span><br><span class="line">        <span class="keyword">super</span>.onCreate()</span><br><span class="line">        BlockCanary.start()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="耗时模拟"><a href="#耗时模拟" class="headerlink" title="耗时模拟"></a>耗时模拟</h3><figure class="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></pre></td><td class="code"><pre><span class="line">btn_test.setOnClickListener &#123;</span><br><span class="line">           <span class="keyword">try</span> &#123;</span><br><span class="line">               Thread.sleep(<span class="number">2000L</span>)</span><br><span class="line">           &#125; <span class="keyword">catch</span> (e: Throwable) &#123;</span><br><span class="line">               e.printStackTrace()</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;</span><br></pre></td></tr></table></figure><p>点击按钮，打印出 log：</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 class="number">2020</span>-<span class="number">03</span>-<span class="number">10</span> <span class="number">01</span>:<span class="number">40</span>:<span class="number">10.509</span> <span class="number">4740</span>-<span class="number">4765</span>/info.hellovass.build_example E/TAG: java.lang.Thread.sleep(Native Method)</span><br><span class="line">    java.lang.Thread.sleep(Thread.java:<span class="number">373</span>)</span><br><span class="line">    java.lang.Thread.sleep(Thread.java:<span class="number">314</span>)</span><br><span class="line">    info.hellovass.build_example.MainActivity$onCreate$<span class="number">1.</span>onClick(MainActivity.kt:<span class="number">15</span>)</span><br><span class="line">    android.view.View.performClick(View.java:<span class="number">6597</span>)</span><br><span class="line">    android.view.View.performClickInternal(View.java:<span class="number">6574</span>)</span><br><span class="line">    android.view.View.access$<span class="number">3100</span>(View.java:<span class="number">778</span>)</span><br><span class="line">    android.view.View$PerformClick.run(View.java:<span class="number">25885</span>)</span><br><span class="line">    android.os.Handler.handleCallback(Handler.java:<span class="number">873</span>)</span><br><span class="line">    android.os.Handler.dispatchMessage(Handler.java:<span class="number">99</span>)</span><br><span class="line">    android.os.Looper.loop(Looper.java:<span class="number">193</span>)</span><br><span class="line">    android.app.ActivityThread.main(ActivityThread.java:<span class="number">6669</span>)</span><br><span class="line">    java.lang.reflect.Method.invoke(Native Method)</span><br><span class="line">    com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:<span class="number">493</span>)</span><br><span class="line">    com.android.internal.os.ZygoteInit.main(ZygoteInit.java:<span class="number">858</span>)</span><br></pre></td></tr></table></figure><p>会打印出耗时相关的代码信息，然后可以通过日志定位到耗时的方法。</p><h1 id="Choreographer"><a href="#Choreographer" class="headerlink" title="Choreographer???"></a>Choreographer???</h1><blockquote><p>2020-03-10 01:40:11.516 4740-4740&#x2F;info.hellovass.build_example I&#x2F;Choreographer: Skipped 119 frames!  The application may be doing too much work on its main thread.</p></blockquote><p>这个日志似乎怎么感觉那么熟悉呢？其实日常开发中，如果我们操作不当（UI线程里执行耗时的操作）就能看到。而这个<code>Choreographer</code>，好像和啥 <code>Android</code>、<code>16ms绘制一帧</code>等关键词有着说不清的关系。</p><p>没错，正是在下。</p><p>Android 系统每隔 16ms 发出 VSYNC 信号，触发对 UI 的渲染。Android SDK 包含了一个相关类，以及相应的回调。理论上来说，两次回调的时间周期应该在 16ms以内，超过了，我们则认为是一次造成卡顿的耗时操作，于是：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">BlockCanary2</span> <span class="keyword">private</span> <span class="keyword">constructor</span>() &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">companion</span> <span class="keyword">object</span> &#123;</span><br><span class="line">        <span class="function"><span class="keyword">fun</span> <span class="title">start</span><span class="params">()</span></span> &#123;</span><br><span class="line">            Choreographer.getInstance()</span><br><span class="line">                .postFrameCallback(<span class="keyword">object</span> : Choreographer.FrameCallback &#123;</span><br><span class="line">                    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">doFrame</span><span class="params">(frameTimeNanos: <span class="type">Long</span>)</span></span> &#123;</span><br><span class="line">                        <span class="keyword">if</span> (LoggerMonitor.INSTANCE.isMonitor)&#123;</span><br><span class="line">                            LoggerMonitor.INSTANCE.stop()</span><br><span class="line">                        &#125;</span><br><span class="line">                        LoggerMonitor.INSTANCE.start()</span><br><span class="line">                        Choreographer.getInstance()</span><br><span class="line">                            .postFrameCallback(<span class="keyword">this</span>)</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></pre></td></tr></table></figure><p>第一次的时候开始检测，如果超过阈值则输出相关堆栈信息；否则移除。</p><h1 id="衍生"><a href="#衍生" class="headerlink" title="衍生"></a>衍生</h1><p>这就完了嘛？兄dei，想多了，如果生产环境的性能检测工具这么写，那真的就是面向离职编程了2333。</p><p>实际要考虑的问题简直不要太多！</p><ul><li>Handler#hasCallbacks 方法仅支持 Android Q 及以上</li><li>阈值为1000ms，第一个方法耗时980ms，第二个方法耗时20ms，这时候打印的堆栈是20ms的方法的堆栈</li><li>阈值还是为1000ms，第一个方法耗时500ms，第二个方法耗时500ms，这时候只打印了第二个方法的堆栈</li><li>怎么把这些堆栈信息同步到远端</li><li>…</li></ul><h1 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h1><p>突然想起好久之前看到过<a href="https://github.com/markzhai">@markzhai</a>发布过一个检测UI卡顿的<a href="https://github.com/markzhai/AndroidPerformanceMonitor">工具</a>，但当时年轻不懂其中的奥秘，连随手star都忘了。</p><p>这次一定!</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li><a href="https://blog.csdn.net/lmj623565791/article/details/58626355">Android UI性能优化 检测应用中的UI卡顿</a></li><li><a href="https://blog.csdn.net/lfdfhl/article/details/39499569">StackTrace简述以及StackTraceElement使用实例</a></li><li><a href="https://blog.csdn.net/u012874222/article/details/79400154">卡顿监测之真正轻量级的卡顿监测工具BlockDetectUtil（仅一个类）</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2020/03/09/Interview%C2%B7StackTrace%E8%A1%8D%E7%94%9F%E7%AF%87/</id>
    <link href="https://hellovass.github.io/2020/03/09/Interview%C2%B7StackTrace%E8%A1%8D%E7%94%9F%E7%AF%87/"/>
    <published>2020-03-09T04:50:59.000Z</published>
    <summary>
      <![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>面试中经常会被问到这样一个问题或者它的类似问题。</p>
<blockquote>
<p>你如何用<strong>程序的方式检测</strong>导致<strong>UI卡顿</strong>的问题，并且<strong>定位</strong>？</p>
</blockquote>]]>
    </summary>
    <title>写给面试的·StackTrace衍生篇</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="面试" scheme="https://hellovass.github.io/categories/interview/"/>
    <category term="面试" scheme="https://hellovass.github.io/tags/%E9%9D%A2%E8%AF%95/"/>
    <content>
      <![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>大厂面试比较喜欢问，看起来简单，没梳理过就会踩坑的题</p><span id="more"></span><h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><ul><li>onStart 和 onResume，onPause 和 onStop 从描述上来看差不多，对开发者来说有什么实质性的不同呢？</li><li>假设当前 Activity 为 A，如果这时用户打开一个新 Activity B，那么 B 的 onResume 和 A 的 onPause 哪个先执行呢？</li></ul><h2 id="问题1"><a href="#问题1" class="headerlink" title="问题1."></a>问题1.</h2><p>从实际使用过程来说，onStart 和 onResume、onPause 和 onStop 看起来的确差不多，甚至我们可以只保留其中一对，比如只保留 onStart 和 onStop。既然如何，那为什么系统还要提供看起来重复的接口呢？</p><p>这两个配对的接口表示的意义不同：</p><ul><li>onStart 和 onStop 从 Activity 是否可见</li><li>onResume 和 onPause 从 Activity 是否位于前台</li></ul><h2 id="问题2"><a href="#问题2" class="headerlink" title="问题2."></a>问题2.</h2><p>涉及到 Activity 的启动流程，启动流程实际上非常复杂，包含 Instrumentation、ActivityThread 和 ActivityManagerService（经常说的AMS就是它了）。</p><p>简述流程：</p><ul><li>启动 <code>Activity</code> 的请求由 <code>Instrumentation</code> 处理，然后它通过 Binder 向 AMS 发送请求</li><li>AMS 内部维护了一个 ActivityStack 并负责栈内的 <code>Activity</code> 的状态同步，AMS 通过 <code>ActivityThread</code> 去同步 <code>Activity</code> 的状态从而完成生命周期方法的调用</li></ul><p>在 <code>ActivityStack</code> 中的 <code>resumeTopActivityInnerLocked</code> 关键代码：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous activity</span></span><br><span class="line"><span class="comment">// to be paused, while at the same time resuming the new resume activity only if the</span></span><br><span class="line"><span class="comment">// previous activity can&#x27;t go into Pip since we want to give Pip activities a chance to</span></span><br><span class="line"><span class="comment">// enter Pip before resuming the next activity.</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">boolean</span> <span class="variable">resumeWhilePausing</span> <span class="operator">=</span> (next.info.flags &amp; FLAG_RESUME_WHILE_PAUSING) != <span class="number">0</span></span><br><span class="line">        &amp;&amp; !lastResumedCanPip;</span><br><span class="line"></span><br><span class="line"><span class="type">boolean</span> <span class="variable">pausing</span> <span class="operator">=</span> getDisplay().pauseBackStacks(userLeaving, next, <span class="literal">false</span>);</span><br><span class="line"><span class="keyword">if</span> (mResumedActivity != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (DEBUG_STATES) Slog.d(TAG_STATES,</span><br><span class="line">            <span class="string">&quot;resumeTopActivityLocked: Pausing &quot;</span> + mResumedActivity);</span><br><span class="line">    pausing |= startPausingLocked(userLeaving, <span class="literal">false</span>, next, <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从注释里可以看到，在新 Activity 启动之前，栈顶的 Activity 需要先 onPause，新 Activity 才能启动。最终，在 ActivityStackSupervisor 中的 realStartActivityLocked 方法会调用如下代码：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Schedule transaction.</span></span><br><span class="line">mService.getLifecycleManager().scheduleTransaction(clientTransaction);</span><br><span class="line"></span><br><span class="line"><span class="comment">// frameworks/base/services/core/java/com/android/server/am/ClientLifecycleManager.java </span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">scheduleTransaction</span><span class="params">(ClientTransaction transaction)</span> <span class="keyword">throws</span> RemoteException &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">IApplicationThread</span> <span class="variable">client</span> <span class="operator">=</span> transaction.getClient();</span><br><span class="line">    transaction.schedule();</span><br><span class="line">    <span class="comment">// 省略N行</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// frameworks/base/core/java/android/app/servertransaction/ClientTransaction.java</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">schedule</span><span class="params">()</span> <span class="keyword">throws</span> RemoteException &#123;</span><br><span class="line">    mClient.scheduleTransaction(<span class="built_in">this</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>ClientTransaction#schedule</code>方法中的<code>mClient</code>是一个<code>IApplicationThread</code>接口，<code>ActivityThread$ApplicationThread</code>派生这个接口并实现了对应的方法。所以直接到<code>ApplicationThread#scheduleTransaction</code>方法。<code>ActivityThread</code> 类中没有定义 <code>scheduleTransaction</code> 方法，所以调用的是他父类的 <code>ClientTransactionHandler#scheduleTransaction</code> 方法。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/base/core/java/android/app/ActivityThread.java</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">class</span> <span class="title class_">ApplicationThread</span> <span class="keyword">extends</span> <span class="title class_">IApplicationThread</span>.Stub &#123;</span><br><span class="line">    ..</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">scheduleTransaction</span><span class="params">(ClientTransaction transaction)</span> <span class="keyword">throws</span> RemoteException &#123;</span><br><span class="line">        ActivityThread.<span class="built_in">this</span>.scheduleTransaction(transaction);</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">// frameworks/base/core/java/android/app/ClientTransactionHandler.java</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">scheduleTransaction</span><span class="params">(ClientTransaction transaction)</span> &#123;</span><br><span class="line">    transaction.preExecute(<span class="built_in">this</span>);</span><br><span class="line">    sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 ClientTransactionHandler#scheduleTransaction 中调用了 sendMessage 方法，该方法是一个抽象方法，实现在 ClientTransactionHandler 的派生类 ActivityThread 中，ActivityThread#sendMessage 方法会把消息发给内部名为<code>H</code>的<code>Handler</code>。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/base/core/java/android/app/ActivityThread.java</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">sendMessage</span><span class="params">(<span class="type">int</span> what, Object obj)</span> &#123;</span><br><span class="line">    sendMessage(what, obj, <span class="number">0</span>, <span class="number">0</span>, <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">sendMessage</span><span class="params">(<span class="type">int</span> what, Object obj, <span class="type">int</span> arg1)</span> &#123;</span><br><span class="line">    sendMessage(what, obj, arg1, <span class="number">0</span>, <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">sendMessage</span><span class="params">(<span class="type">int</span> what, Object obj, <span class="type">int</span> arg1, <span class="type">int</span> arg2)</span> &#123;</span><br><span class="line">    sendMessage(what, obj, arg1, arg2, <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">sendMessage</span><span class="params">(<span class="type">int</span> what, Object obj, <span class="type">int</span> arg1, <span class="type">int</span> arg2, <span class="type">boolean</span> async)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (DEBUG_MESSAGES) Slog.v(</span><br><span class="line">        TAG, <span class="string">&quot;SCHEDULE &quot;</span> + what + <span class="string">&quot; &quot;</span> + mH.codeToString(what)</span><br><span class="line">        + <span class="string">&quot;: &quot;</span> + arg1 + <span class="string">&quot; / &quot;</span> + obj);</span><br><span class="line">    <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> Message.obtain();</span><br><span class="line">    msg.what = what;</span><br><span class="line">    msg.obj = obj;</span><br><span class="line">    msg.arg1 = arg1;</span><br><span class="line">    msg.arg2 = arg2;</span><br><span class="line">    <span class="keyword">if</span> (async) &#123;</span><br><span class="line">        msg.setAsynchronous(<span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    mH.sendMessage(msg);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ActivityThread$H</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleMessage</span><span class="params">(Message msg)</span> &#123;</span><br><span class="line">    <span class="comment">// 省略 N行</span></span><br><span class="line">    <span class="keyword">case</span> EXECUTE_TRANSACTION: </span><br><span class="line">        <span class="keyword">final</span> <span class="type">ClientTransaction</span> <span class="variable">transaction</span> <span class="operator">=</span> (ClientTransaction) msg.obj;</span><br><span class="line">        mTransactionExecutor.execute(transaction);</span><br><span class="line">        <span class="comment">// 省略 N行</span></span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="comment">// 省略 N行</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>H 的实例接收到 EXECUTE_TRANSACTION 消息后，调用<code>TransactionExecutor#execute</code>方法切换 Activity 的状态。<code>TransactionExecutor#execute</code> 方法里先执行 callbacks（如果不为null），然后改变 Activity 当前的生命周期状态。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/base/core/java/android/app/servertransaction/TransactionExecutor.java</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">execute</span><span class="params">(ClientTransaction transaction)</span> &#123;</span><br><span class="line">    <span class="comment">// 省略 N行</span></span><br><span class="line">    executeCallbacks(transaction);</span><br><span class="line">    executeLifecycleState(transaction);</span><br><span class="line">    <span class="comment">// 省略 N行</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/** Transition to the final state if requested by the transaction. */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">executeLifecycleState</span><span class="params">(ClientTransaction transaction)</span> &#123;</span><br><span class="line">    <span class="comment">// 省略 N行</span></span><br><span class="line">    <span class="comment">// Cycle to the state right before the final requested state.</span></span><br><span class="line">    cycleToPath(r, lifecycleItem.getTargetState(), <span class="literal">true</span> <span class="comment">/* excludeLastState */</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Execute the final transition with proper parameters.</span></span><br><span class="line">    lifecycleItem.execute(mTransactionHandler, token, mPendingActions);</span><br><span class="line">    lifecycleItem.postExecute(mTransactionHandler, token, mPendingActions);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>在<code>executeLifecycleState</code>方法里:</p><ul><li>先调用<code>TransactionExecutor#cycleToPath</code>方法执行当前生命周期状态之前的状态</li><li>然后执行<code>ActivityLifecycleItem#execute</code>方法，由于是从<code>ON_RESUME</code>状态到<code>ON_PAUSE</code>状态切换，中间没有其他状态，cycleToPath 这个情况下没有做什么实质性的事情，直接执行<code>lifecycleItem.execute</code>方法</li><li>之前在<code>ActivityStack.startPausingLocked</code>方法里面<code>scheduleTransaction</code>传递的是<code>PauseActivityItem</code>对象，所以<code>executeLifecycleState</code>方法里的<code>lifecycleItem</code>实际是<code>PauseActivityItem</code>对象</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/base/core/java/android/app/servertransaction/PauseActivityItem.java</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">execute</span><span class="params">(ClientTransactionHandler client, IBinder token,</span></span><br><span class="line"><span class="params">           PendingTransactionActions pendingActions)</span> &#123;</span><br><span class="line">       Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, <span class="string">&quot;activityPause&quot;</span>);</span><br><span class="line">       client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, pendingActions,</span><br><span class="line">               <span class="string">&quot;PAUSE_ACTIVITY_ITEM&quot;</span>);</span><br><span class="line">       Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p><code>PauseActivityItem#execute</code>方法中传入的<code>client</code>实际上是<code>ActivityThread</code>对象，所以我们又回到了<code>ActivityThread</code>。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/base/core/java/android/app/ActivityThread.java</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handlePauseActivity</span><span class="params">(IBinder token, <span class="type">boolean</span> finished, <span class="type">boolean</span> userLeaving,</span></span><br><span class="line"><span class="params">        <span class="type">int</span> configChanges, PendingTransactionActions pendingActions, String reason)</span> &#123;</span><br><span class="line">        <span class="comment">// 省略 N行</span></span><br><span class="line">        performPauseActivity(r, finished, reason, pendingActions);</span><br><span class="line">        <span class="comment">// 省略 N行</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> Bundle <span class="title function_">performPauseActivity</span><span class="params">(IBinder token, <span class="type">boolean</span> finished, String reason,</span></span><br><span class="line"><span class="params">        PendingTransactionActions pendingActions)</span> &#123;</span><br><span class="line">    <span class="type">ActivityClientRecord</span> <span class="variable">r</span> <span class="operator">=</span> mActivities.get(token);</span><br><span class="line">    <span class="keyword">return</span> r != <span class="literal">null</span> ? performPauseActivity(r, finished, reason, pendingActions) : <span class="literal">null</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="comment"> * Pause the activity.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> Saved instance state for pre-Honeycomb apps if it was saved, &#123;<span class="doctag">@code</span> null&#125; otherwise.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> Bundle <span class="title function_">performPauseActivity</span><span class="params">(ActivityClientRecord r, <span class="type">boolean</span> finished, String reason,</span></span><br><span class="line"><span class="params">        PendingTransactionActions pendingActions)</span> &#123;</span><br><span class="line">    <span class="comment">// 省略 N行</span></span><br><span class="line">    <span class="keyword">if</span> (shouldSaveState) &#123;</span><br><span class="line">        callActivityOnSaveInstanceState(r);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    performPauseActivityIfNeeded(r, reason);</span><br><span class="line">    <span class="comment">// 省略 N行</span></span><br><span class="line">    <span class="keyword">return</span> shouldSaveState ? r.state : <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">performPauseActivityIfNeeded</span><span class="params">(ActivityClientRecord r, String reason)</span> &#123;</span><br><span class="line">    <span class="comment">// 省略 N行</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        r.activity.mCalled = <span class="literal">false</span>;</span><br><span class="line">        mInstrumentation.callActivityOnPause(r.activity);</span><br><span class="line">        <span class="comment">// 省略 N行</span></span><br><span class="line">    &#125; <span class="keyword">catch</span> (SuperNotCalledException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> e;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!mInstrumentation.onException(r.activity, e)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Unable to pause activity &quot;</span></span><br><span class="line">                    + safeToComponentShortString(r.intent) + <span class="string">&quot;: &quot;</span> + e.toString(), e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    r.setState(ON_PAUSE);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>离终点总算不远了，<code>Instrumentation#callActivityOnPause</code> 方法中调用了<code>Activity#performPause</code>，在<code>performPause</code>中我们终于看到了熟悉的钩子函数<code>onPause</code>，至此栈顶<code>Activity</code>的<code>pause</code>流程完毕。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这里的源码是根据 Android 9.0 来分析的，跟 5.0 相比，真的复杂了很多，看到一位老哥的 Activity启动流程源码分析写的不错，把 pause 这块的流程分析稍加整理放到了这道题目下，有兴趣的话可以自己翻阅源码。</p><p>思考，9.0 的源码里似乎多了很多<code>lifecycle</code>、状态切换相关的字眼，如果是熟悉<code>aac架构</code>的老哥们一定不陌生了，9.0 这块改动这么大应该就是加入了 LifeCycle 的特性（确信）。</p><p>综上，新启一个<code>Activity</code>的时候，旧<code>Activity</code>的<code>onPause</code>会先执行，然后才会启动新的<code>Activity</code>。</p><p>官网对 onPause 的解释：</p><blockquote><p>The foreground lifetime of an activity happens between a call to onResume() until a corresponding call to onPause(). During this time the activity is in visible, active and interacting with the user. An activity can frequently go between the resumed and paused states – for example when the device goes to sleep, when an activity result is delivered, when a new intent is delivered – so the code in these methods should be fairly lightweight.</p></blockquote><p>别在 onPause 里做重量级操作，因为 onPause 执行完之后，新 Activity 才能 Resume！</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li><a href="https://blog.csdn.net/lj19851227/article/details/82562115">（Android 9.0）Activity启动流程源码分析</a></li><li><a href="https://item.jd.com/11760209.html">Android开发艺术探索</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2020/03/08/Interview%C2%B7Activity%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E7%AF%87/</id>
    <link href="https://hellovass.github.io/2020/03/08/Interview%C2%B7Activity%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E7%AF%87/"/>
    <published>2020-03-08T08:19:34.000Z</published>
    <summary>
      <![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>大厂面试比较喜欢问，看起来简单，没梳理过就会踩坑的题</p>]]>
    </summary>
    <title>写给面试的·Activity生命周期篇</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Android" scheme="https://hellovass.github.io/tags/Android/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>自从入职上家之后，一年多的时间里就基本没有碰过多渠道了（叹息）。</p><p>总之，回顾下吧。</p><span id="more"></span><h2 id="多渠道"><a href="#多渠道" class="headerlink" title="多渠道"></a>多渠道</h2><p>多渠道也叫多渠道打包。</p><p>为什么需要多渠道打包呢？一个包能解决的事情为毛这么麻烦呢？产出一个包不也能发布到各个应用市场嘛。</p><p>如果所在的公司没有以下需求：</p><ul><li>统计哪个渠道用户变多</li><li>哪个渠道用户黏性强</li><li>哪个渠道需要个性化的设计</li><li>…</li></ul><p>确实不需要！</p><p>我们通过 <code>Android</code> 提供的 <code>API</code> 可以获取到的信息有：</p><ul><li>应用的版本号</li><li>版本名称</li><li>系统版本</li><li>机型</li><li>…</li></ul><p>但是唯独渠道，即应用商店的信息是没有办法从系统中获取到的。只能人为地在 apk 中添加<strong>渠道信息</strong>。</p><p>多渠道打包，需要关注的事情：</p><ul><li>将渠道信息写入 apk</li><li>将 apk 中的渠道信息传输到后端</li></ul><p>打包会经过签名这个步骤，而 Android 签名有 2种不同的方式：</p><ul><li>Android 7.0 之前，使用 v1 签名，是 jar signature，源自 JDK</li><li>Android 7.0 之后，引入 v2 签名，是 Android 特有的 apk signature</li></ul><p>使用区别：</p><p>使用 v2 方式签名，7.0 以下的系统安装应用会失败<br>使用 v1 方式签名，7.0 和 7.0 以下的都 OK</p><p>Android Studio 的图形界面可以同时选择两种签名方式，7.0 以下使用 v1，7.0 以上选择 v2，如图：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%A4%9A%E6%B8%A0%E9%81%93%E5%9B%9E%E9%A1%BE/%E5%9B%BE%E5%BD%A2%E5%8C%96%E7%AD%BE%E5%90%8D.png" alt="Android Studio 图形化签名"></p><p>以下是 apk 安装过程中签名验证的流程，如图：</p><p><img src="https://source.android.google.cn/security/images/apk-v2-validation.png?hl=zh-cn" alt="apk 安装过程签名校验"></p><ul><li>UNCHANGED 代表 Android 7.0以下流程</li><li>NEW 代表 Android 7.0以上流程</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DefaultSigningConfig</span> <span class="keyword">implements</span> <span class="title class_">SigningConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略 N行</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">mV1SigningEnabled</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">mV2SigningEnabled</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br></pre></td></tr></table></figure><p>v1、v2 签名默认都是启用的，如果想禁用，可以在对应的地方加上：</p><figure class="highlight groovy"><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">signingConfigs&#123;</span><br><span class="line">    release&#123;</span><br><span class="line">        v2SigningEnabled <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>v1、v2 有啥区别呢？</p><p>本质区别就在于签名的方式，首先 apk 本身是 zip 格式文件。</p><p>v2 签名后和签名前的差异：</p><p><img src="https://source.android.google.cn/security/images/apk-before-after-signing.png?hl=zh-cn" alt="v2 和 v1签名对比"></p><p>签名前：</p><ul><li>压缩的文件内容源数据</li><li>压缩的目录源数据</li><li>目录结束标识结构</li></ul><p>签名后：</p><ul><li>压缩的文件内容源数据</li><li>签名区块</li><li>压缩的目录源数据</li><li>目录结束标识结构</li></ul><p>多了一个区块，用于签名验证。如果其他3区块被修改了，都逃不过 v2 验证，直接导致验证失败，所以 v2 比 v1 更加安全。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://item.jd.com/12325825.html">Android 组件化架构</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2020/03/06/%E5%A4%9A%E6%B8%A0%E9%81%93%E5%9B%9E%E9%A1%BE/</id>
    <link href="https://hellovass.github.io/2020/03/06/%E5%A4%9A%E6%B8%A0%E9%81%93%E5%9B%9E%E9%A1%BE/"/>
    <published>2020-03-06T10:15:02.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>自从入职上家之后，一年多的时间里就基本没有碰过多渠道了（叹息）。</p>
<p>总之，回顾下吧。</p>]]>
    </summary>
    <title>多渠道回顾</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="CI/CD" scheme="https://hellovass.github.io/tags/CI-CD/"/>
    <content>
      <![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>在 《Android持续集成实践·后篇》中阐述了为何使用 pipeline 并且展示了一个简单的通用构建脚本，但实际生产环境中，会发现 Jenkins 内置的功能还远不能满足我们所有的需求。</p><p>这时，我们就需要扩展 pipeline。</p><div style="position: relative; width: 100%; height: 0; padding-bottom: 75%;">    <iframe src="//player.bilibili.com/player.html?aid=74144528&cid=126841545&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;">    </iframe></div><span id="more"></span><h2 id="在-pipeline-中定义函数"><a href="#在-pipeline-中定义函数" class="headerlink" title="在 pipeline 中定义函数"></a>在 pipeline 中定义函数</h2><p>pipeline 本质就是一个 groovy 脚本。所以，我们可以在 pipline 中定义函数，并使用 groovy 语言自带的特性。</p><figure class="highlight groovy"><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">pipeline &#123;</span><br><span class="line">    agent any</span><br><span class="line"></span><br><span class="line">    stages&#123;</span><br><span class="line">        steps&#123;</span><br><span class="line">            echo <span class="string">&quot;$&#123;getVersion(BUILD_NUMBER)&#125;&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> getVersion(String buildNum)&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> Date().format(<span class="string">&#x27;yyMMdd&#x27;</span>) + <span class="string">&quot;-$&#123;buildNum&#125;&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述这个例子，我们定义了一个 getVersion 函数，并使用了 Date 类。</p><blockquote><p>PS：当然不是所有的 <code>groovy 脚本</code>都能执行，有时候，我们需要用管理员身份登入 <code>Jenkins</code>，找到对应的 <code>groovy 脚本</code>，点击 <code>Approve</code> 给这个 <code>groovy 脚本</code>权限。</p></blockquote><h3 id="存在的问题"><a href="#存在的问题" class="headerlink" title="存在的问题"></a>存在的问题</h3><p>如果在一个 JenkinsFile 中定义一个函数，倒是无所谓。但是如果在 N 个 JenkinsFile 中重复定义这个函数，那干这个活的人就有<strong>一句mmp不知当讲不当讲了！</strong></p><h1 id="使用共享函数库"><a href="#使用共享函数库" class="headerlink" title="使用共享函数库"></a>使用共享函数库</h1><p>这又是啥勒？</p><p>Jenkins Pipline 很贴心，给我们提供了 <code>shared library</code> 这个功能，具体路径：Manage Jenkins -&gt; Configure System -&gt; Global Pipeline Libraries，如图：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Pipeline%E6%89%A9%E5%B1%95/Jenkins%E5%85%B1%E4%BA%AB%E5%87%BD%E6%95%B0%E5%BA%93.png" alt="共享函数库配置"></p><h2 id="配置项说明"><a href="#配置项说明" class="headerlink" title="配置项说明"></a>配置项说明</h2><ul><li>Name：共享库唯一标识，在 Jenkinsfile 中会用到</li><li>Default version：默认版本。可以是分支名、tag 等。</li><li>Load implicitily：隐式加载。如果勾选此项，将自动加载全局共享库，在 Jenkinsfile 中不需要显式引用，就可以直接使用。</li><li>Allow default version to be overidden：如果勾选此项，则表示允许“Default version”被 Jenkinsfile 中的配置覆盖。</li><li>include @Library changes in job recent changes：如果勾选此项，那么共享库的最后变更信息会跟项目的变更信息一起被打印在构建日志中。</li><li>Retrieval method：获取共享库代码的方法。这里我们选择<code>Modern SCM</code>，然后选择 Git。</li></ul><h2 id="创建共享库"><a href="#创建共享库" class="headerlink" title="创建共享库"></a>创建共享库</h2><p>创建一个共享库项目，目录结构如下：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Pipeline%E6%89%A9%E5%B1%95/%E5%85%B1%E4%BA%AB%E5%87%BD%E6%95%B0%E5%BA%93%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84.png" alt="共享库目录结构"></p><p>将代码推送到远程仓库中，然后将 ssh 地址填入到<code>Modern SCM</code>中。</p><h2 id="使用共享库"><a href="#使用共享库" class="headerlink" title="使用共享库"></a>使用共享库</h2><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Library</span>(<span class="string">&#x27;global-shared-library)</span></span><br><span class="line"><span class="string">pipeline&#123;</span></span><br><span class="line"><span class="string">    agent any</span></span><br><span class="line"><span class="string">    stages&#123;</span></span><br><span class="line"><span class="string">        stage(&#x27;</span>build<span class="string">&#x27;)&#123;</span></span><br><span class="line"><span class="string">            steps&#123;</span></span><br><span class="line"><span class="string">                say(&quot;Hello World&quot;)</span></span><br><span class="line"><span class="string">            &#125;</span></span><br><span class="line"><span class="string">        &#125;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">&#125;</span></span><br></pre></td></tr></table></figure><p>在 pipeline block 的顶部，我们使用 @Library 指定共享库。</p><blockquote><p>NOTE: global-shared-library 就是配置项中的共享库标识符。</p></blockquote><p>引入共享库之后，我们可以直接在 pipeline 中使用 vars 目录下的 say 函数。</p><p>小结一下，如何定义和使用一个共享函数库：</p><ol><li>按照共享库约定的源码结构，实现自己的逻辑</li><li>将共享库托管到代码仓库中</li><li>在 Jenkins 全局配置中配置共享库，主要是配置共享库的仓库地址</li><li>在 Jenkinsfile 中使用 @Library 引用共享库</li></ol><h2 id="Library"><a href="#Library" class="headerlink" title="@Library"></a>@Library</h2><p>使用该注解可以指定引用的共享库版本。写法如下：</p><blockquote><p>@Library(‘global-shared-library@<version>‘) _</p></blockquote><p><version> 说明：</p><ul><li>分支，如 @Library(‘global-shared-library@<version>‘) _</li><li>tag 标签，如 @Library(‘<a href="mailto:&#103;&#x6c;&#111;&#98;&#97;&#x6c;&#45;&#x73;&#104;&#x61;&#x72;&#x65;&#100;&#45;&#108;&#105;&#x62;&#x72;&#97;&#x72;&#x79;&#64;&#114;&#x65;&#108;&#x65;&#x61;&#115;&#101;&#x31;&#x2e;&#48;">global-shared-library@release1.0</a>‘) _</li><li>git commit id，如 @Library(‘global-shared-library@e6gfhsfsdf’) _</li></ul><p>Jenkins 支持同时添加多个共享库，所以 @Library 注解还允许同时引入多个共享库，如：@Library([‘global-shared-library’, ‘other-shared-library’]) _。</p><blockquote><p>NOTE：Jenkins 处理多个共享库出现同名函数的方式是先定义者生效。也就是说，如果 <code>global-shared-library</code> 与 <code>other-shared-library</code> 存在同名的函数 <code>say</code>，而 @Library 引入时 <code>global-shared-library</code> 在 <code>other-shared-library</code> 前面，那么只有 <code>global-shared-library</code> 的 <code>say</code> 生效。</p></blockquote><h2 id="共享函数库介绍"><a href="#共享函数库介绍" class="headerlink" title="共享函数库介绍"></a>共享函数库介绍</h2><p>共享库的目录结构复习：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Pipeline%E6%89%A9%E5%B1%95/%E5%85%B1%E4%BA%AB%E5%87%BD%E6%95%B0%E5%BA%93%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84.png" alt="共享函数库目录结构"></p><p>放在 vars 目录下的是可以供 pipeline 直接调用的全局变量（真的很想吐槽“变量”这个名称）。变量的文件名即为在 pipline 中调用的函数名，文件名为驼峰式。</p><p>使用 var 目录下的全局变量可以调用 Jenkins pipeline 的步骤。比如 say.groovy，使用了 echo 步骤。</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> call(String value = <span class="string">&quot;hello world&quot;</span>)</span><br><span class="line">    echo <span class="string">&quot;$value&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当我们在 <code>Jenkinsfile</code> 中调用 <code>say(&quot;hello world&quot;)</code> 时，它实际调用的是 <code>say.groovy</code> 文件中的 <code>call</code> 函数。</p><p><code>call</code> 函数还支持 Closure（闭包）。举🌰：</p><p>定义一个 mvm.grrovy。</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> call(Closure&lt;String&gt; closure)&#123;</span><br><span class="line">    configFileProvider([configFile(<span class="attr">fileId:</span> <span class="string">&#x27;maven-global-settings&#x27;</span>, <span class="attr">variable:</span> <span class="string">&#x27;MAVEN_GLOBAL_ENV&#x27;</span>)])&#123;</span><br><span class="line">        closure(<span class="string">&quot;$&#123;MAVEN_GLOBAL_ENV&#125;&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过 call 函数里的内容就可以将 configFileProvider 啰嗦的写法封装到 mvm 变量。于是，我们可以这么用：</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Library</span>(<span class="string">&#x27;global-shared-library@master&#x27;</span>) _</span><br><span class="line">pipeline &#123;</span><br><span class="line">    agent any</span><br><span class="line">    tools &#123;</span><br><span class="line">        maven <span class="string">&#x27;mvn-3.5.4&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">    stages &#123;</span><br><span class="line">        stage(<span class="string">&#x27;Build&#x27;</span>)&#123;</span><br><span class="line">            steps&#123;</span><br><span class="line">                mvn&#123; settings-&gt;</span><br><span class="line">                    sh <span class="string">&quot;mvn -s $&#123;settings&#125; clean install&quot;</span></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></pre></td></tr></table></figure><p>接着来看 src 目录：</p><p>src 目录是以一个标准的 Java 源码结构，目录中的类被称为 Library class（库类）。而 @Library(‘global-shared-library@dev’) _ 中的 _ 代表一次性静态加载 src 目录下的所有代码 classpath 中。</p><p>Utils.groovy 代码：</p><figure class="highlight groovy"><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"><span class="keyword">package</span> info.hellovass</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Utils</span> <span class="keyword">implements</span> <span class="title class_">Serializable</span> &#123;</span><br><span class="line">    <span class="keyword">def</span> getVersion(String buildNum, String commitId)&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> Date().format(<span class="string">&#x27;yyMMdd&#x27;</span>) + <span class="string">&quot;-$&#123;buildNum&#125;&quot;</span> + <span class="string">&quot;-$&#123;commitId&#125;&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>NOTE：Utils 实现了 Serializable 接口，是为了确保当 pipeline 被 Jenkins 挂起后能正确恢复。</p></blockquote><p>在使用 src 目录中的类时，需要使用全包名。同时，因为写的是 Groovy 代码，所以还需要使用 script 闭包抱起来。举栗子：</p><figure class="highlight groovy"><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"></span><br><span class="line"><span class="meta">@Library</span>([<span class="string">&#x27;global-shared-library&#x27;</span>]) _</span><br><span class="line">pipeline &#123;</span><br><span class="line">    agent any</span><br><span class="line">    stages(<span class="string">&#x27;Build&#x27;</span>) &#123;</span><br><span class="line">        steps &#123;</span><br><span class="line">            script &#123;</span><br><span class="line">                <span class="keyword">def</span> util = <span class="keyword">new</span> info.hellovass.Utils()</span><br><span class="line">                <span class="keyword">def</span> version = util.getVersion(<span class="string">&quot;$&#123;BUILD_NUMBER&#125;&quot;</span>, <span class="string">&quot;$&#123;GIT_COMMIT&#125;&quot;</span>)</span><br><span class="line">                echo <span class="string">&quot;$&#123;version&#125;&quot;</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="应用"><a href="#应用" class="headerlink" title="应用"></a>应用</h1><p>比如，我们需要在构建成功时通过钉钉机器人发送消息到群里。</p><h2 id="方案一，集成钉钉机器人插件"><a href="#方案一，集成钉钉机器人插件" class="headerlink" title="方案一，集成钉钉机器人插件"></a>方案一，集成钉钉机器人插件</h2><p>这个是网上能搜到的比较常见的方案，需要 jenkins 安装 DingTalk 插件，路径：Manage Jenkins -&gt; Manage Plugins -&gt; 可选插件。</p><p>但是 2.0 版本似乎不支持 pipeline 特性，只能通过 UI选项配置。</p><blockquote><p>PS: 网上很多文章，可以参考</p></blockquote><h2 id="方案二，共享函数库登场"><a href="#方案二，共享函数库登场" class="headerlink" title="方案二，共享函数库登场"></a>方案二，共享函数库登场</h2><p>钉钉机器人可以看做一个比较通用的功能，于是，我们的共享函数库派上用场了。</p><h3 id="新建一个-DingTalk-groovy"><a href="#新建一个-DingTalk-groovy" class="headerlink" title="新建一个 DingTalk.groovy"></a>新建一个 DingTalk.groovy</h3><p>在 src 目录下新建一个 DingTalk.groovy，根据自己的需求编写函数：</p><figure class="highlight groovy"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">DingTalk</span> <span class="keyword">implements</span> <span class="title class_">Serializable</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 发送文字消息</span></span><br><span class="line"><span class="comment">     * @param webHook</span></span><br><span class="line"><span class="comment">     * @param contentType</span></span><br><span class="line"><span class="comment">     * @param content 消息内容</span></span><br><span class="line"><span class="comment">     * @return</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">def</span> sendTextMessage(</span><br><span class="line">            String webHook,</span><br><span class="line">            String contentType,</span><br><span class="line">            String content</span><br><span class="line">    ) &#123;</span><br><span class="line">        <span class="keyword">def</span> template = <span class="string">&quot;&#123;\n&quot;</span> +</span><br><span class="line">                <span class="string">&quot;    \&quot;msgtype\&quot;: \&quot;text\&quot;, \n&quot;</span> +</span><br><span class="line">                <span class="string">&quot;    \&quot;text\&quot;: &#123;\n&quot;</span> +</span><br><span class="line">                <span class="string">&quot;        \&quot;content\&quot;: \&quot;$content\&quot;\n&quot;</span> +</span><br><span class="line">                <span class="string">&quot;    &#125;, \n&quot;</span> +</span><br><span class="line">                <span class="string">&quot;    \&quot;at\&quot;: &#123;\n&quot;</span> +</span><br><span class="line">                <span class="string">&quot;        \&quot;atMobiles\&quot;: [], \n&quot;</span> +</span><br><span class="line">                <span class="string">&quot;        \&quot;isAtAll\&quot;: true\n&quot;</span> +</span><br><span class="line">                <span class="string">&quot;    &#125;\n&quot;</span> +</span><br><span class="line">                <span class="string">&quot;&#125;&quot;</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">def</span> cmd = [<span class="string">&quot;curl&quot;</span>, <span class="string">&quot;$webHook&quot;</span>, <span class="string">&quot;-H&quot;</span>, <span class="string">&quot;Content-Type: $contentType&quot;</span>, <span class="string">&quot;-d&quot;</span>, <span class="string">&quot;$template&quot;</span>]</span><br><span class="line">            <span class="keyword">def</span> process = cmd.execute()</span><br><span class="line">            process.waitFor()</span><br><span class="line">            <span class="keyword">return</span> process.text</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">return</span> e.message</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码定义了一个 <code>sendTextMessage</code> 函数，参数为:</p><ul><li>webHook：钉钉机器人 Webhook 地址</li><li>contentType：application&#x2F;json</li><li>content：想要发送的文本内容</li></ul><p>具体可以参考<a href="https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq">钉钉机器人开发文档</a>，钉钉机器人发送的消息有多种类型：</p><ul><li>text 类型</li><li>link 类型</li><li>markdown 类型</li><li>整体跳转 ActionCard 类型</li></ul><p>本文给出了发送 text类型消息的函数实现，其他几种类似，可以根据具体需求来实现。</p><h3 id="核心原理"><a href="#核心原理" class="headerlink" title="核心原理"></a>核心原理</h3><p>实际上，我们只需要执行一条 <code>curl</code> 命令就可以向钉钉机器人发送消息了，就这么简单。关键就是这条<strong>命令</strong>怎么写，以及如何执行了。</p><h4 id="写法一"><a href="#写法一" class="headerlink" title="写法一"></a>写法一</h4><figure class="highlight groovy"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 发送文字消息</span></span><br><span class="line"><span class="comment"> * @param webHook</span></span><br><span class="line"><span class="comment"> * @param contentType</span></span><br><span class="line"><span class="comment"> * @param content 消息内容</span></span><br><span class="line"><span class="comment"> * @return</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">def</span> sendTextMessage(</span><br><span class="line">        String webHook,</span><br><span class="line">        String contentType,</span><br><span class="line">        String content</span><br><span class="line">) &#123;</span><br><span class="line">    <span class="keyword">def</span> template = <span class="string">&quot;&#123;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;    \&quot;msgtype\&quot;: \&quot;text\&quot;, \n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;    \&quot;text\&quot;: &#123;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;        \&quot;content\&quot;: \&quot;$content\&quot;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;    &#125;, \n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;    \&quot;at\&quot;: &#123;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;        \&quot;atMobiles\&quot;: [], \n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;        \&quot;isAtAll\&quot;: true\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;    &#125;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;&#125;&quot;</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 看这里</span></span><br><span class="line">        <span class="keyword">def</span> cmd = [<span class="string">&quot;curl&quot;</span>, <span class="string">&quot;$webHook&quot;</span>, <span class="string">&quot;-H&quot;</span>, <span class="string">&quot;Content-Type: $contentType&quot;</span>, <span class="string">&quot;-d&quot;</span>, <span class="string">&quot;$template&quot;</span>]</span><br><span class="line">        <span class="keyword">def</span> process = cmd.execute()</span><br><span class="line">        process.waitFor()</span><br><span class="line">        <span class="keyword">return</span> process.text</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="keyword">return</span> e.message</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>cmd</code> 是一个数组，<code>cmd.execute</code> 最后调用了 <code>ProcessGroovyMethods#exec(String[] cmdarray, String[] envp, File dir)</code> 方法，相关方法如下：</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 方法一</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Process <span class="title function_">execute</span><span class="params">(<span class="keyword">final</span> List commands)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="keyword">return</span> execute(stringify(commands));</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">public</span> <span class="keyword">static</span> Process <span class="title function_">execute</span><span class="params">(<span class="keyword">final</span> String[] commandArray)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="keyword">return</span> Runtime.getRuntime().exec(commandArray);</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">public</span> Process <span class="title function_">exec</span><span class="params">(String cmdarray[])</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="keyword">return</span> exec(cmdarray, <span class="literal">null</span>, <span class="literal">null</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">public</span> Process <span class="title function_">exec</span><span class="params">(String[] cmdarray, String[] envp, File dir)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ProcessBuilder</span>(cmdarray)</span><br><span class="line">        .environment(envp)</span><br><span class="line">        .directory(dir)</span><br><span class="line">        .start();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="写法二"><a href="#写法二" class="headerlink" title="写法二"></a>写法二</h4><figure class="highlight groovy"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 发送文字消息</span></span><br><span class="line"><span class="comment"> * @param webHook</span></span><br><span class="line"><span class="comment"> * @param contentType</span></span><br><span class="line"><span class="comment"> * @param content 消息内容</span></span><br><span class="line"><span class="comment"> * @return</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">def</span> sendTextMessage2(</span><br><span class="line">        String webHook,</span><br><span class="line">        String contentType,</span><br><span class="line">        String content</span><br><span class="line">) &#123;</span><br><span class="line">    <span class="keyword">def</span> template = <span class="string">&quot;&#123;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;    \&quot;msgtype\&quot;: \&quot;text\&quot;, \n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;    \&quot;text\&quot;: &#123;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;        \&quot;content\&quot;: \&quot;$content\&quot;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;    &#125;, \n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;    \&quot;at\&quot;: &#123;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;        \&quot;atMobiles\&quot;: [], \n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;        \&quot;isAtAll\&quot;: true\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;    &#125;\n&quot;</span> +</span><br><span class="line">            <span class="string">&quot;&#125;&quot;</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 看这里</span></span><br><span class="line">        <span class="keyword">def</span> cmd = <span class="string">&quot;curl &#x27;$webHook&#x27; -H &#x27;Content-Type: $contentType&#x27; -d &#x27;$template&#x27;&quot;</span></span><br><span class="line">        <span class="keyword">def</span> process = cmd.execute()</span><br><span class="line">        process.waitFor()</span><br><span class="line">        <span class="keyword">return</span> process.text</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="keyword">return</span> e.message</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>cmd 是一个字符串，<code>cmd.execute</code> 最后调用了 <code>ProcessGroovyMethods#exec(String[] cmdarray, String[] envp, File dir)</code> 方法，相关方法如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 方法一</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Process <span class="title function_">execute</span><span class="params">(<span class="keyword">final</span> String self)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="keyword">return</span> Runtime.getRuntime().exec(self);</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">public</span> Process <span class="title function_">exec</span><span class="params">(String command)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="keyword">return</span> exec(command, <span class="literal">null</span>, <span class="literal">null</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">public</span> Process <span class="title function_">exec</span><span class="params">(String command, String[] envp, File dir)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (command.length() == <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Empty command&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="type">StringTokenizer</span> <span class="variable">st</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringTokenizer</span>(command);</span><br><span class="line">    String[] cmdarray = <span class="keyword">new</span> <span class="title class_">String</span>[st.countTokens()];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; st.hasMoreTokens(); i++)</span><br><span class="line">        cmdarray[i] = st.nextToken();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> exec(cmdarray, envp, dir);</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">public</span> Process <span class="title function_">exec</span><span class="params">(String[] cmdarray, String[] envp, File dir)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ProcessBuilder</span>(cmdarray)</span><br><span class="line">        .environment(envp)</span><br><span class="line">        .directory(dir)</span><br><span class="line">        .start();</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="困惑"><a href="#困惑" class="headerlink" title="困惑"></a>困惑</h3><p>理论上来说，两种实现是等价的。然鹅，测试之后，方法二执行耗时一万年，最后钉钉机器人发送消息失败。而方法一和直接在命令行里执行的速度差不多，并且能成功发送消息。</p><p>黑人问号？？？</p><h3 id="打印命令"><a href="#打印命令" class="headerlink" title="打印命令"></a>打印命令</h3><p>这里我们在 <code>exec(String[] cmdarray, String[] envp, File dir)</code> 这个方法里打上断点，来查看一下不同的写法，<code>cmdArray</code> 的值有什么区别？</p><h4 id="写法一-1"><a href="#写法一-1" class="headerlink" title="写法一"></a>写法一</h4><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Pipeline%E6%89%A9%E5%B1%95/%E5%86%99%E6%B3%95%E4%B8%80%E7%9A%84cmdArray.png" alt="写法一的cmdArray"></p><h4 id="写法二-1"><a href="#写法二-1" class="headerlink" title="写法二"></a>写法二</h4><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Pipeline%E6%89%A9%E5%B1%95/%E5%86%99%E6%B3%95%E4%BA%8C%E7%9A%84cmdArray.png" alt="写法二的cmdArray"></p><p>果然，写法一的 cmdArray 符合预期，一看就能理解；写法二的参数被错误分割了，这也就是为毛写法二执行耗时过长而且失败的原因了。那为什么参数会被错误分割呢？</p><p>原因大致是这样的，写法二是通过<strong>空格</strong>来分割参数的！</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>去 stackoverflow 上逛了一下，发现很多老外兄dei也有类似的<a href="https://stackoverflow.com/questions/23742419/perfectly-working-curl-command-fails-when-executed-in-a-groovy-script/52987922#52987922">问题</a>。</p><p>所以，如果遇到要通过 groovy 执行命令的时候，推荐使用写法一。</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li><a href="https://item.jd.com/45779671535.html">Jenkins 2.x 实践指南</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2020/03/04/Pipeline%20%E6%89%A9%E5%B1%95/</id>
    <link href="https://hellovass.github.io/2020/03/04/Pipeline%20%E6%89%A9%E5%B1%95/"/>
    <published>2020-03-04T06:27:02.000Z</published>
    <summary>
      <![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>在 《Android持续集成实践·后篇》中阐述了为何使用 pipeline 并且展示了一个简单的通用构建脚本，但实际生产环境中，会发现 Jenkins 内置的功能还远不能满足我们所有的需求。</p>
<p>这时，我们就需要扩展 pipeline。</p>


<div style="position: relative; width: 100%; height: 0; padding-bottom: 75%;">
    <iframe src="//player.bilibili.com/player.html?aid=74144528&cid=126841545&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;">
    </iframe>
</div>]]>
    </summary>
    <title>Pipeline 扩展</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Android" scheme="https://hellovass.github.io/tags/Android/"/>
    <content>
      <![CDATA[<h2 id="Android-基础编译流程"><a href="#Android-基础编译流程" class="headerlink" title="Android 基础编译流程"></a>Android 基础编译流程</h2><p>Android 工程的构建打包是一个复杂的流程，涉及工程源代码、资源文件、AIDL 文件以及工程所依赖的库文件的编译转换。</p><p>官方提供的构建流程图：</p><p><img src="https://developer.android.com/images/tools/studio/build-process_2x.png" alt="build_process"></p><span id="more"></span><p>编译构建的4个步骤：源代码编译-&gt;代码合成-&gt;资源打包-&gt;签名和对齐。</p><ol><li>Java 编译器对工程的代码资源进行编译，代码资源包括 App 的源代码、apt 编译生成的 R 文件和 AIDL 文件生成的 Java 接口文件。通过 Java 编译器编译生成 *.class 文件。</li><li>通过 dex 工具，将 *.class 文件和<strong>项目依赖的第三方库文件</strong>生成虚拟机可执行的 .dex 文件，如果使用了 MultiDex，会产生多个 dex 文件，包含编译后的所有 .class 文件，即自身的 class文件和依赖库的 .class 文件。</li><li>apkbuilder 工具将 .dex 文件、apt 编译后的资源文件、依赖中的第三方库内的资源文件打包生成签名对齐的 apk 文件。</li><li>使用 Jarsigner 和 Zipalign 对文件进行签名和对齐，最终生成 apk 文件。</li></ol><p>通过 Gradle工具可以看到每个运行的 Task 的情况，还可以看到 Gradle 编译的流程，详细地显示出每个任务的 Task 耗时，如图：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%85%B3%E4%BA%8E%E7%BC%96%E8%AF%91/Android%E7%BC%96%E8%AF%91%E7%A4%BA%E4%BE%8B.png" alt="Gradle 编译流程"></p><p>以下为执行 <code>./gradlew assembleDebug</code> 的 Task 输出：</p><figure class="highlight groovy"><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="attr">app:</span>preBuild <span class="comment">// 开始预编译</span></span><br><span class="line">:<span class="attr">app:</span>preDebugBuild <span class="comment">// 开始 Debug 版本预编译</span></span><br><span class="line">:<span class="attr">app:</span>checkDebugManifest <span class="comment">// 开始检查 AndroidManifest</span></span><br><span class="line">:<span class="attr">app:</span>prepareDebugDependencies <span class="comment">// 检查 Debug 版本依赖</span></span><br><span class="line">:<span class="attr">app:</span>compileDebugAidl <span class="comment">// 编译 Debug 版本的 AIDL 文件</span></span><br><span class="line">:<span class="attr">app:</span>compileDebugRenderscript <span class="comment">// 编译 Renderscript 文件</span></span><br><span class="line">:<span class="attr">app:</span>generateDebugBuildConfig <span class="comment">// 生成 BuildConfig 文件</span></span><br><span class="line">:<span class="attr">app:</span>generateDebugAssets <span class="comment">// 生成 Assets 文件到文件夹</span></span><br><span class="line">:<span class="attr">app:</span>mergeDebugAssets <span class="comment">// 合并 Assets 文件</span></span><br><span class="line">:<span class="attr">app:</span>generateDebugResValues <span class="comment">// 生成 res value 文件</span></span><br><span class="line">:<span class="attr">app:</span>generateDebugResources <span class="comment">// 生成 resource 文件</span></span><br><span class="line">:<span class="attr">app:</span>mergeDebugResources <span class="comment">// 合并 resource 文件</span></span><br><span class="line">:<span class="attr">app:</span>processDebugManifest <span class="comment">// 处理 AndroidManifest 文件</span></span><br><span class="line">:<span class="attr">app:</span>processDebugResources <span class="comment">// 处理 resource 文件</span></span><br><span class="line">:<span class="attr">app:</span>generateDebugSources <span class="comment">// 合成资源文件</span></span><br><span class="line">:<span class="attr">app:</span>compileDebugJavaWithJavac <span class="comment">// 使用 Javac 编译 Java 文件</span></span><br><span class="line">:<span class="attr">app:</span>compileDebugNdk <span class="comment">// NDK 编译</span></span><br><span class="line">:<span class="attr">app:</span>compileDebugSources <span class="comment">// 编译资源文件</span></span><br><span class="line">:<span class="attr">app:</span>transformClassesWithDexForDebug <span class="comment">// 将 .class 文件转换成 .dex 文件</span></span><br><span class="line">:<span class="attr">app:</span>mergeDebugJniLibFolders <span class="comment">// 合并 JNI 文件夹</span></span><br><span class="line">:<span class="attr">app:</span>transformNative_libsWithMergeJniLibsForDebug <span class="comment">// 转换 JNI 文件</span></span><br><span class="line">:<span class="attr">app:</span>processDebugJavaRes <span class="comment">// 处理 Java 资源</span></span><br><span class="line">:<span class="attr">app:</span>transformResourcesWithMergeJavaResForDebug <span class="comment">// 转换 Java资源文件</span></span><br><span class="line">:<span class="attr">app:</span>validateDebugSigning <span class="comment">// 验证签名</span></span><br><span class="line">:<span class="attr">app:</span>packageDebug <span class="comment">// 打包</span></span><br><span class="line">:<span class="attr">app:</span>assembleDebug <span class="comment">// 签名</span></span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://v.youku.com/v_show/id_XMzYwMDQ3MDk2OA==.html?spm=a2h0k.11417342.soresults.dtitle">What’s new with the Android build system (Google I&#x2F;O ‘18)</a></li><li><a href="https://v.youku.com/v_show/id_XMzU5ODExNzQzMg==.html?spm=a2h0k.11417342.soresults.dtitle">What’s new in Android development tools (Google I&#x2F;O ‘18)</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2020/02/13/%E8%8A%B1%E9%9D%9E%E8%8A%B1%C2%B7%E5%85%B3%E4%BA%8E%E7%BC%96%E8%AF%91%E9%9C%80%E8%A6%81%E4%BA%86%E8%A7%A3%E4%BB%80%E4%B9%88/</id>
    <link href="https://hellovass.github.io/2020/02/13/%E8%8A%B1%E9%9D%9E%E8%8A%B1%C2%B7%E5%85%B3%E4%BA%8E%E7%BC%96%E8%AF%91%E9%9C%80%E8%A6%81%E4%BA%86%E8%A7%A3%E4%BB%80%E4%B9%88/"/>
    <published>2020-02-13T14:37:26.000Z</published>
    <summary>
      <![CDATA[<h2 id="Android-基础编译流程"><a href="#Android-基础编译流程" class="headerlink" title="Android 基础编译流程"></a>Android 基础编译流程</h2><p>Android 工程的构建打包是一个复杂的流程，涉及工程源代码、资源文件、AIDL 文件以及工程所依赖的库文件的编译转换。</p>
<p>官方提供的构建流程图：</p>
<p><img src="https://developer.android.com/images/tools/studio/build-process_2x.png" alt="build_process"></p>]]>
    </summary>
    <title>花非花·关于编译需要了解什么</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Java" scheme="https://hellovass.github.io/tags/Java/"/>
    <content>
      <![CDATA[<h2 id="new-Thread"><a href="#new-Thread" class="headerlink" title="new Thread"></a>new Thread</h2><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">Thread(</span><br><span class="line">    Runnable &#123;</span><br><span class="line">        <span class="comment">// ignore</span></span><br><span class="line">    &#125;</span><br><span class="line">).start()</span><br></pre></td></tr></table></figure><p>远古时期的写法，问题也很多：</p><ul><li>每次通过 new Thread 新建对象性能差</li><li>线程缺乏统一管理，可能无限制新建线程，相互之间竞争，及可能占用过多系统资源导致死机或oom</li><li>缺乏更多功能，如定时执行、定期执行、线程中断</li></ul><span id="more"></span><h2 id="线程池"><a href="#线程池" class="headerlink" title="线程池"></a>线程池</h2><p>Java 提供的4种线程池好处：</p><ul><li>重用存在的线程，减少对象创建、消亡的开销，性能佳</li><li>可有效控制最大并发线程数，提高系统资源的使用率，同时避免过多资源竞争，避免堵塞</li><li>提供定时执行、定期执行、单线程、并发数控制等功能</li></ul><h2 id="线程池介绍"><a href="#线程池介绍" class="headerlink" title="线程池介绍"></a>线程池介绍</h2><p>Java 的 Executors 提供4种线程池，分别为：</p><ul><li>newCachedThreadPool</li><li>newSingleThreadExecutor</li><li>newFixedThreadPool</li></ul><h3 id="ThreadPoolExecutor"><a href="#ThreadPoolExecutor" class="headerlink" title="ThreadPoolExecutor"></a>ThreadPoolExecutor</h3><p>先来看一下 ThreadPoolExecutor 构造方法签名：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">ThreadPoolExecutor</span><span class="params">(</span></span><br><span class="line"><span class="params">    <span class="type">int</span> corePoolSize,</span></span><br><span class="line"><span class="params">    <span class="type">int</span> maximumPoolSize,</span></span><br><span class="line"><span class="params">    <span class="type">long</span> keepAliveTime,</span></span><br><span class="line"><span class="params">    TimeUnit unit,</span></span><br><span class="line"><span class="params">    BlockingQueue&lt;Runnable&gt; workQueue</span></span><br><span class="line"><span class="params">)</span> &#123;</span><br><span class="line">    <span class="comment">// 省略N行代码  </span></span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><ul><li><code>corePoolSize</code>，线程池最小线程数，</li><li><code>maximumPoolSize</code>，线程池最大线程数</li><li><code>keepAliveTime</code>，线程存活时间</li><li><code>workQueue</code>，线程容器，线程池满了之后用来存放线程</li></ul><h3 id="newCachedThreadPool"><a href="#newCachedThreadPool" class="headerlink" title="newCachedThreadPool()"></a>newCachedThreadPool()</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title function_">newCachedThreadPool</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(</span><br><span class="line">            <span class="number">0</span>,</span><br><span class="line">            Integer.MAX_VALUE,</span><br><span class="line">            <span class="number">60L</span>,</span><br><span class="line">            TimeUnit.SECONDS,</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">SynchronousQueue</span>&lt;Runnable&gt;()</span><br><span class="line">        )</span><br><span class="line">    &#125;       </span><br></pre></td></tr></table></figure><p>创建一个可缓存线程池，如果线程长度超过处理需要，可灵活空闲线程，若无可回收，则新建线程。maximumPoolSize 参数为无限大，线程存活时间为 60S，如果当执行第二个任务时第一个任务已经完成，会复用第一个任务的线程，而不用新建线程。</p><h3 id="newSingleThreadExecutor"><a href="#newSingleThreadExecutor" class="headerlink" title="newSingleThreadExecutor()"></a>newSingleThreadExecutor()</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title function_">newSingleThreadExecutor</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">FinalizableDelegatedExecutorService</span> (</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(</span><br><span class="line">            <span class="number">1</span>, </span><br><span class="line">            <span class="number">1</span>,</span><br><span class="line">            <span class="number">0L</span>, </span><br><span class="line">            TimeUnit.MILLISECONDS,</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">LinkedBlockingQueue</span>&lt;Runnable&gt;()</span><br><span class="line">            )</span><br><span class="line">        );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>创建只有一个线程的线程池，保证所有任务按照指定顺序（FIFO，LIFO，优先级）执行。适用于不适合并发但可能导致 IO 阻塞影响 UI 线程响应的操作：</p><ul><li>数据库操作</li><li>文件操作</li><li>应用批量安装、删除</li></ul><h3 id="newFixedThreadPool-int-nThreads"><a href="#newFixedThreadPool-int-nThreads" class="headerlink" title="newFixedThreadPool(int nThreads)"></a>newFixedThreadPool(int nThreads)</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title function_">newFixedThreadPool</span><span class="params">(<span class="type">int</span> nThreads)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(</span><br><span class="line">            nThreads, </span><br><span class="line">            nThreads,</span><br><span class="line">            <span class="number">0L</span>, </span><br><span class="line">            TimeUnit.MILLISECONDS,</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">LinkedBlockingQueue</span>&lt;Runnable&gt;()</span><br><span class="line">            );</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>创建一个指定长度的线程池，可控制线程最大并发数，超出的线程会在队列中等待。</p><h3 id="newScheduledThreadPool"><a href="#newScheduledThreadPool" class="headerlink" title="newScheduledThreadPool()"></a>newScheduledThreadPool()</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ScheduledExecutorService <span class="title function_">newScheduledThreadPool</span><span class="params">(<span class="type">int</span> corePoolSize)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ScheduledThreadPoolExecutor</span>(corePoolSize);</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">public</span> <span class="title function_">ScheduledThreadPoolExecutor</span><span class="params">(<span class="type">int</span> corePoolSize)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(</span><br><span class="line">            corePoolSize,</span><br><span class="line">            Integer.MAX_VALUE,</span><br><span class="line">            DEFAULT_KEEPALIVE_MILLIS, </span><br><span class="line">            MILLISECONDS,</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">DelayedWorkQueue</span>()</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p><code>ScheduledThreadPoolExecutor</code> 继承自 <code>ThreadPoolExecutor</code>，该方法创建一个定长线程池，支持定时以及周期性任务执行。</p><h2 id="线程同步-线程安全"><a href="#线程同步-线程安全" class="headerlink" title="线程同步&amp;线程安全"></a>线程同步&amp;线程安全</h2><p>线程安全是一个在面试中经常被拿出来面试的点，题目也挺多的，如果只做题，似乎永远都做不完的样子，所以线程安全问题的本质是啥勒：</p><ul><li>多个线程访问<strong>共同资源</strong>时，在某一个线程对资源进行写操作时（写入开始，未结束），其他线程对这个正在被写入的资源进行了读&#x2F;写操作，造成数据不一致</li></ul><p>锁机制解决问题的本质：</p><p>通过对<strong>共享的资源</strong>进行访问闲置，同一时间内只允许单个线程访问，保证了数据的一致性</p><p>结论：不论线程安全问题，还是针对这个问题提出的锁机制解决方案，核心都在于如何保护<strong>共享资源</strong>，而不是某个方法或者某几行代码。</p><h3 id="synchronized-注解"><a href="#synchronized-注解" class="headerlink" title="synchronized 注解"></a>synchronized 注解</h3><p>kotlin 没有 synchronized 关键字，但提供了 @Synchronized 注解。该注解和 Java 的 synchronized 有同样的效果：它将把 JVM 方法标记为同步。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Synchronized</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">add</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="comment">// 省略N行</span></span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h3 id="volatile-注解"><a href="#volatile-注解" class="headerlink" title="volatile 注解"></a>volatile 注解</h3><p>kotlin 也没有 volatile 关键字，但提供了 @Volatile 注解：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Volatile</span> <span class="keyword">private</span> <span class="keyword">var</span> running = <span class="literal">false</span></span><br></pre></td></tr></table></figure><p>该行为类似于 @Synchronized，注解会将 JVM 备份字段标记为 volatile：</p><ul><li>保证添加了@Volatile 的字段操作性具有同步性，以及对 long、double 的操作的原子性</li><li>只对基本类型（byte、char、short、int、long、float、double、boolean）的复制操作和对象引用复制操作有效，要修改 User.name 是不能保证同步的</li><li>依然解决不了 ++ 的原子性问题</li></ul><h3 id="java-util-concurrent-atomic-包："><a href="#java-util-concurrent-atomic-包：" class="headerlink" title="java.util.concurrent.atomic 包："></a>java.util.concurrent.atomic 包：</h3><p>提供蛮多好用的类 AtomicInteger、AtomicBoolean 等，作用和 volatile 基本一致</p><h3 id="synchronized-方法"><a href="#synchronized-方法" class="headerlink" title="synchronized 方法"></a>synchronized 方法</h3><p>对应同步代码块，kotlin 提供 synchronized 方法：</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="function"><span class="keyword">fun</span> <span class="title">count</span><span class="params">(newValue: <span class="type">Int</span>)</span></span> &#123;</span><br><span class="line">        synchronized(<span class="keyword">this</span>) &#123;</span><br><span class="line">            <span class="comment">// 省略N行代码</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h3 id="synchronized-的膜法"><a href="#synchronized-的膜法" class="headerlink" title="synchronized 的膜法"></a>synchronized 的膜法</h3><ul><li>保证方法内或者代码块内部资源的互斥访问。即同一时刻、由同一个 Monitor 监视的代码，最多只能有一个线程访问。</li></ul><p><img src="http://assets.processon.com/chart_image/5e43fd10e4b06b291a6c0bf0.png" alt="Java 多线程&amp;同步1"></p><ul><li>保证线程之间对监视资源的数据同步。即任何线程在获取到 Monitor 后的第一时间，会先将共享内存中的数据复制到自己的缓存中；任何线程在释放 Monitor 的第一时间，会先将缓存中的数据复制到共享内存中</li></ul><p><img src="http://assets.processon.com/chart_image/5e43ffece4b021dc28a5270c.png" alt="Java 多线程&amp;同步2"></p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://www.trinea.cn/android/java-android-thread-pool/">Java线程池</a></li><li><a href="https://hencoder.com/">hencoder</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2020/02/10/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B%E5%90%8C%E6%AD%A5/</id>
    <link href="https://hellovass.github.io/2020/02/10/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B%E5%90%8C%E6%AD%A5/"/>
    <published>2020-02-10T12:11:27.000Z</published>
    <summary>
      <![CDATA[<h2 id="new-Thread"><a href="#new-Thread" class="headerlink" title="new Thread"></a>new Thread</h2><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">Thread(</span><br><span class="line">    Runnable &#123;</span><br><span class="line">        <span class="comment">// ignore</span></span><br><span class="line">    &#125;</span><br><span class="line">).start()</span><br></pre></td></tr></table></figure>

<p>远古时期的写法，问题也很多：</p>
<ul>
<li>每次通过 new Thread 新建对象性能差</li>
<li>线程缺乏统一管理，可能无限制新建线程，相互之间竞争，及可能占用过多系统资源导致死机或oom</li>
<li>缺乏更多功能，如定时执行、定期执行、线程中断</li>
</ul>]]>
    </summary>
    <title>Java多线程和线程同步</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><p>每到写年终的时候总是思绪万千，一到提笔，又不知道该写些啥，只能明日复明日，于是，本该在 2019年发布的年终总结还是拖到了 2020年。</p><div style="position: relative; width: 100%; height: 0; padding-bottom: 75%;">    <iframe src="//player.bilibili.com/player.html?aid=48519708&cid=84971632&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;">    </iframe></div><span id="more"></span><h2 id="我的猫与我"><a href="#我的猫与我" class="headerlink" title="我的猫与我"></a>我的猫与我</h2><p>从在公司楼下捡到她起，不知不觉我俩已经相依为命一年多了。</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/2019%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/%E5%9C%A8%E5%AE%B6%E7%9D%A1%E8%A7%89%E7%9A%84%E5%B0%8F%E7%8C%AA%E5%84%BF.jpeg" alt="在家睡觉觉的小猪儿"></p><h3 id="小猪儿"><a href="#小猪儿" class="headerlink" title="小猪儿"></a>小猪儿</h3><ul><li>小猪儿是一个很有个性的猫猫，不喜欢人抱，无论是谁，超过1分钟一定会喵喵叫，然后拼命想从手上跳下去</li><li>记仇，如果打了小猪儿，小猪儿就会趁你不注意把数据线咬坏。PS：她还只咬低压线，那些220V的电线她从来不碰</li><li>下手没有分寸，和我玩的时候经常把我咬伤、抓伤</li><li>不太喜欢打呼噜</li></ul><h3 id="我"><a href="#我" class="headerlink" title="我"></a>我</h3><p>永远都爱小猪儿</p><h2 id="我的无人机与我"><a href="#我的无人机与我" class="headerlink" title="我的无人机与我"></a>我的无人机与我</h2><p>从大三开始就一直惦记的大疆精灵3，毕业2年后也一直买8起（别问，问就是穷）。直到 19年4月份总算下单购买了刚发布的大疆 Mavic2 Pro，真香，真香。</p><h3 id="Mavic2-Pro"><a href="#Mavic2-Pro" class="headerlink" title="Mavic2 Pro"></a>Mavic2 Pro</h3><p>是真的轻便，我觉得 Mavic2 的一大亮点便是折叠式设计，折叠之后往包里一塞，就可以背着到处跑。机身+电池，总重在 1kg左右。接着是续航，满电电池的飞行续航（别太多骚操作，3-4级风），大概 25min 是可以飞的。至于成像，那就更8用说了，是真的赞，拍完不用后期直接朋友圈问题也8大。</p><p>PS：见文章开头视频</p><h2 id="我的-itx主机"><a href="#我的-itx主机" class="headerlink" title="我的 itx主机"></a>我的 itx主机</h2><p>买了点配件，将之前的两台大主机给小型化了，一台 5.7L，一台 10L。</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/2019%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/itx%E4%B8%BB%E6%9C%BA.jpeg" alt="itx主机1"></p><h3 id="我-1"><a href="#我-1" class="headerlink" title="我"></a>我</h3><p>自从有了无人机之后，基本周六都会跑到西湖那边放飞机。</p><h2 id="我的工作与我"><a href="#我的工作与我" class="headerlink" title="我的工作与我"></a>我的工作与我</h2><p>一直以来的遗憾，毕业3年多了，没经过大中厂，一直以来，一直是以<strong>小作坊模式</strong>进行开发（😢）。</p><h3 id="前情回顾"><a href="#前情回顾" class="headerlink" title="前情回顾"></a>前情回顾</h3><p>毕业后先后去了<strong>杭州尘埃科技</strong>、<strong>上海洲华</strong>、<strong>杭州趣链科技</strong>，对，就是那种你没去过你就肯定不知道的小厂。</p><h3 id="小作坊模式"><a href="#小作坊模式" class="headerlink" title="小作坊模式"></a>小作坊模式</h3><p>啥事小作坊模式呢？就是一般而言，你这个岗位把你招进去之后就你一人，情况好的话可以发展到2人团队<strong>小而精</strong>的规模。这是什么概念呢？概念就是，一般而言，你都不会接受<strong>大中厂</strong>的那套开发流程、规范，什么项目架构设计、什么持续集成、什么 code review，不存在的。简而言之，怎么快怎么来！记得之前在哪里看到过这样一句话，<strong>小厂往往活不到需要你考虑优化&amp;重构的那个阶段</strong>，这几年走来、想来，字字扎心。</p><p>快而乱的步伐在前期是无可厚非的，<strong>毕竟要恰饭的嘛</strong>。厉害的技术dalao又招不起，只能堆堆业务，赶赶需求，盼望赶紧上线才能维持得了生活这样子。</p><p>但，这种模式对程序员个体来说是带有原罪的！<strong>我承认我有赌的成分</strong>，但是<strong>技术成长、升职加薪</strong>和<strong>删库跑路</strong>你必须得给我实现一个，小厂就是这么心酸。无数的小厂在创业的浪潮中前进，最后还在浪潮之巅的…</p><h4 id="能跑就行-可怜的生命周期"><a href="#能跑就行-可怜的生命周期" class="headerlink" title="能跑就行&amp;可怜的生命周期"></a>能跑就行&amp;可怜的生命周期</h4><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/2019%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/%E8%83%BD%E8%B7%91%E5%B0%B1%E8%A1%8C.jpeg" alt="能跑就行"></p><p>前者我就不说了，看图自行体会。谈谈后者，你在小厂辛苦忙碌的产品最后可能都活不到你去面试其它厂。更别说，面试官经常会问以下的问题：</p><ul><li>你这个项目有什么技术难点啊</li><li>你有没有看过这个开源项目的源码啊</li><li>…</li></ul><p>你可能都没法 carry，因为，项目的生命周期短，业务场景和用户就决定了你可能在工作中根本没有直面这些问题以及解决这些问题的机会，这也正是我这些年困扰我的地方。</p><h4 id="缺乏优秀的同事"><a href="#缺乏优秀的同事" class="headerlink" title="缺乏优秀的同事"></a>缺乏优秀的同事</h4><p>不是说小厂就没有 dalao，但是就目前经历来看，小厂的 dalao 真的少得可怜，有的甚至就没有！为什么想要去大厂，因为 dalao 收敛于大厂。</p><p>从优秀的同事身上往往能学到很多自身没有的东西，经常看开源项目的人不言而喻。dalao 的代码鲁棒性都很赞，而且经常能考虑到很多细节，比如我经常觉得有些代码似乎很冗余，但当自己遇到类似问题或者踩到某些坑之后才恍然大悟，只能感慨一句没有大量项目经验人才能考虑如此细致。</p><h4 id="不缺乏”优秀“的同事"><a href="#不缺乏”优秀“的同事" class="headerlink" title="不缺乏”优秀“的同事"></a>不缺乏”优秀“的同事</h4><p>别问，问就是大厂背书，金字招牌。</p><p>也是在小厂，我真正意识到大厂背景是多么的重要+值钱。研发团队里的项目经理或者技术负责人往往是由大厂背景的一些人来担任，而这些人有的可能真的是 dalao，而有的可能是大厂输送到社会的”优秀人才”。前者会带领大家做好项目，后者的话，带给团队的<strong>欢乐</strong>简直不要太多，比如：</p><ul><li>连 kotlin 都没有了解过，就夸夸其谈<strong>反正到最后出来的字节码不都一样嘛，用 kotlin 和用 java 又有什么区别</strong></li><li>不考虑引入第三方库带来的不稳定性和包的体积增大，就来一句<strong>这不是很简单的嘛，用xx库就好了。现在流量这么多，包体积大个几 M无所谓的</strong></li><li>甚至自身参与到项目中后能把一些常见的接口性能“提升”到40s以上，然后丢过给页面太复杂，前端渲染太慢的</li></ul><p>讲真，我真的喜欢和大厂的人交盆友，但是我真的讨厌那种挂着大厂牌子，没有过硬技术却很会用<strong>屁股</strong>思考的人。不过还好，这类人自身特点比较明显，很容易鉴别，如果有兄 dei摊上了这些的大厂过来的”负责人”，请酌情考虑远离或者跑路：</p><ul><li>脑子都是 Spring 的形状，只会 java 并且不熟悉其他后端常用语言或者技术</li><li>完全不懂前端，但坚持前端童鞋就是<strong>页面仔</strong>，前端的需求都是非常简单的应该要快速完成的论调</li><li>完全不懂客户端，但坚持其他 App 能做到，你怎么就不行的论调</li><li>N 久没有 coding，但是对技术异常自信，不懂前端、客户端、后端的技术难点也从不预测可能遇到的技术问题，但就是能自信给出<strong>这个应该很简单</strong>的结论</li><li>…</li></ul><h4 id="周围的变化与趋势"><a href="#周围的变化与趋势" class="headerlink" title="周围的变化与趋势"></a>周围的变化与趋势</h4><p>三年过去了，身边越来越多的童鞋开始往大厂跳了，🍋了🍋了。</p><h3 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h3><p>开荒的时候可能没有<strong>熔山龙三件套</strong>，但是再怎么也别去那种打着<strong>私有化、定制化</strong>的外包公司，比有自营产品的小厂都惨。向周围优秀的盆友或者网友学习，自身战斗力提高之后加入更好的 team，一起开荒狩猎！</p><h2 id="技术成长"><a href="#技术成长" class="headerlink" title="技术成长"></a>技术成长</h2><p>今年总算比之前有些成长。</p><ul><li>研究了一些有名的路由库，然后基于 kapt 撸了一个路由库，总算对以前学的 apt 知识有了点深刻的认知</li><li>翻了下 redux、flutter-redux 的源码，用 kotlin 也实现了 android 上的 redux</li><li>学习了一点 flutter</li><li>对前端的组件化、状态管理有了更深刻的认识</li><li>给 blankj 老师交了学费，然后仔细研究了<a href="https://github.com/Blankj/AucFrameTemplate">blankj 的 AucFrameTemplate</a>，不得不说，从优秀的人身上真的能学到很多东西，真香，真香！<ul><li>了解了 gradle 构建的生命周期，用 kotlin-dsl 重写了 groovy 的实现</li><li>实践了一番 ASM，了解了字节码插桩的原理以及实现</li></ul></li><li>学习了 Jenkins 2.x 的 pipline as code 新特性<ul><li>搞了一个通用的 pipline 脚本仓库</li><li>熟悉了下 Jenkins 的共享函数库特性</li></ul></li></ul><h2 id="算法-数据结构"><a href="#算法-数据结构" class="headerlink" title="算法&amp;数据结构"></a>算法&amp;数据结构</h2><p>我承认现在的我比较弱，所以搞了一个<a href="https://github.com/HelloVass/leetcode">leetcode-reslover</a>放长线，暂时未开源，因为好多题并没有真正会做。</p><h2 id="投资理财与副业"><a href="#投资理财与副业" class="headerlink" title="投资理财与副业"></a>投资理财与副业</h2><p>之前一直没有这方面的意识，别问，问就是穷，之前两年的工作并不是太好，并没有留下多少积蓄。</p><p>年级大了，也开始意识到副业收入的重要性。杭州不断上涨的房价，程序员的死线35岁，成为标配的996，还有那啥成为程序员们自黑梗的发际线…</p><p>新的一年计划在基金上向身边的养🐔大佬学习，然后开拓一条能稳定赚钱的副业，减轻下日以上涨的焦虑。</p><h2 id="读书清单"><a href="#读书清单" class="headerlink" title="读书清单"></a>读书清单</h2><h3 id="已读"><a href="#已读" class="headerlink" title="已读"></a>已读</h3><ul><li>《Jenkins 2.x 实践指南》</li><li>《Kotlin 核心编程》</li><li>《Flutter技术入门与实战》</li><li>《Android全埋点解决方案》</li><li>《Android组件化架构》</li></ul><h3 id="未读"><a href="#未读" class="headerlink" title="未读"></a>未读</h3><ul><li>《副业赚钱之道 从0到1打造多元化收入》</li><li>《不要完美，要搞定》</li></ul><h2 id="身体"><a href="#身体" class="headerlink" title="身体"></a>身体</h2><p>讲真，去年好不容易减到72kg，一年多大吃大喝结果又长了18kg，重回90kg，难受啊。</p><p>希望新的一年能再通过锻炼减回去吧。</p>]]>
    </content>
    <id>https://hellovass.github.io/2020/02/05/2019%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/</id>
    <link href="https://hellovass.github.io/2020/02/05/2019%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
    <published>2020-02-05T04:32:55.000Z</published>
    <summary>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><p>每到写年终的时候总是思绪万千，一到提笔，又不知道该写些啥，只能明日复明日，于是，本该在 2019年发布的年终总结还是拖到了 2020年。</p>


<div style="position: relative; width: 100%; height: 0; padding-bottom: 75%;">
    <iframe src="//player.bilibili.com/player.html?aid=48519708&cid=84971632&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;">
    </iframe>
</div>]]>
    </summary>
    <title>2019 年终总结</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="CI/CD" scheme="https://hellovass.github.io/tags/CI-CD/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>距离上篇《Android持续集成·前篇》已经有些时日，这次将会填上上篇留下的大坑，pipline 构建。</p><span id="more"></span><h2 id="Pipeline"><a href="#Pipeline" class="headerlink" title="Pipeline"></a>Pipeline</h2><h3 id="pipline-是什么"><a href="#pipline-是什么" class="headerlink" title="pipline 是什么"></a>pipline 是什么</h3><blockquote><p>从某种抽象层次上讲，部署流水线（Development pipeline）是指从软件版本控制库到用户手中这一过程的自动化表现形式。</p></blockquote><p>按照<a href="https://item.jd.com/10843669.html">《持续交付》</a>中的定义，Jenkins 本来就支持 pipeline（部署流水线），只是一开始不叫 pipeline，而叫 job（任务）。</p><p>Jenkins 1.x 只能通过手动操作（即各种UI选项设置）来描述 pipline。而 Jenkins 2.x 终于支持了 pipline as code 的特性，即我们可以通过代码来描述 pipline。</p><h3 id="之前的问题"><a href="#之前的问题" class="headerlink" title="之前的问题"></a>之前的问题</h3><p>谈优点前，先来回顾下上篇中的构建方式，即自由风格软件的项目。这种构建方式是网上最常见的，对于单个项目的简单构建（比如大部分 Android开发就拿 Jenkins 做个打包）已经足够了。但是，问题来了，针对多个类似但又有区别的项目，就捉襟见肘了，需要大量的 job 来支持，这就会导致，如果主流程的构建变了，每个项目的 job 也得修改和测试，难以维护，噩梦啊。</p><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><p>这里就来谈谈为什么要 pipline as code 而不是 UI：</p><ul><li>更好的版本化：将 CI 作为一个项目提交到 SCM 中进行版本控制</li><li>更好的协作：CI 项目的修改对所有人都是可见的。而且，还可以对 CI 项目的代码做 code review</li><li>众所周知：code 可以复用，而手动操作没法复用，没人想每个新项目都去重新设置一遍</li></ul><h3 id="拥抱-pipline"><a href="#拥抱-pipline" class="headerlink" title="拥抱 pipline"></a>拥抱 pipline</h3><p>所以后篇将放弃前篇的自由风格软件构建的方式，坚持 pipline 真香原则。</p><h2 id="之前做不到的以及现在想要做的"><a href="#之前做不到的以及现在想要做的" class="headerlink" title="之前做不到的以及现在想要做的"></a>之前做不到的以及现在想要做的</h2><p>需要开发和维护的 Android 项目不止一个，而且每个项目都需要 CI，但每个项目的构建流程非常相似，但又有些不同。大体上的流程如下：</p><ul><li>克隆代码</li><li>编译打包出 Apk</li><li>静态代码检查（可选）</li><li>单元测试（可选）</li><li>归档（可选）</li><li>上传测试分发平台</li><li>通知相关人员构建结果（可选）</li></ul><p>可以看到整体的流程是相同的，但又存在差异。有的构建可以跳过单元测试，有的构建可以跳过静态代码检查，通知的相关人员也会不同。如果使用自由风格构建的方式，也8是8可以。</p><p>但是，未来是不确定的，可能会添加新的构建流程（比如使用签名插件签名），而新添加的构建流程也可能出现bug。无论出现哪种情况，一旦修改主构建流程，每个项目的 job 都需要修改和测试，这必然会花费大量的时间。</p><p>所以，想有一种船新的构建方式，将整体的构建作为一个模板or框架，每个项目可以通过配置项目各自的 CI文件来决定自己的构建流程，这样岂不是美滋滋。</p><h2 id="实践经验分享"><a href="#实践经验分享" class="headerlink" title="实践经验分享"></a>实践经验分享</h2><h3 id="使用-pipline-构建"><a href="#使用-pipline-构建" class="headerlink" title="使用 pipline 构建"></a>使用 pipline 构建</h3><p>新建一个 pipline 项目，然后选择 pipline script from SCM，像这样：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Android%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E5%90%8E%E7%AF%87/%E6%96%B0%E5%BB%BApipeline%E9%A1%B9%E7%9B%AE.png" alt="新建一个 pipline 项目"></p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Android%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E5%90%8E%E7%AF%87/%E6%9E%84%E5%BB%BA%E6%88%AA%E5%9B%BE.png" alt="pipeline script from SCM"></p><p>这里我们选择将 pipline 脚本纳入到版本管理，理由如下：</p><ul><li>可以多人维护、code review</li><li>可以多项目共用同一个脚本项目</li></ul><p>这样，Jenkins 在启动 job 时，会先去脚本仓库里拉去脚本，然后运行这个脚本。这个脚本里，我们编写的构建方式和步骤就会按部就班的执行。</p><p>这样，大体的架子就搭建好了，但是，针对多项目，我们还有一些事情要做，于是：<br><img src="http://assets.processon.com/chart_image/5e37c237e4b0d27af1859b6b.png" alt="构建流程图"></p><p>这里，我们可以清楚地看到，构建的参数其实有3部分：</p><ul><li>job 的 UI 界面</li><li>脚本仓库的里的脚本</li><li>项目特定的配置</li></ul><h3 id="job-UI-界面-参数化构建"><a href="#job-UI-界面-参数化构建" class="headerlink" title="job UI 界面(参数化构建)"></a>job UI 界面(参数化构建)</h3><p>这里用过自由风格构建的童鞋都非常熟悉了，构建 job 的时候，勾选参数化构建过程，设置：</p><ul><li>项目的仓库地址</li><li>分支</li><li>构建结果的通知人</li><li>…</li></ul><p>当然，还可以根据项目的需求增加更多的参数，这些参数的特点，灵活、多变，需要经常被修改。</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Android%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E5%90%8E%E7%AF%87/job%E7%95%8C%E9%9D%A2.png" alt="job UI界面"></p><h3 id="项目特定配置"><a href="#项目特定配置" class="headerlink" title="项目特定配置"></a>项目特定配置</h3><p>这个配置文件可以理解为与项目绑定的配置，一般是一些不经常修改的参数，比如项目的名字，如图：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Android%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E5%90%8E%E7%AF%87/%E9%A1%B9%E7%9B%AE%E7%89%B9%E5%AE%9A%E9%85%8D%E7%BD%AE.png" alt="项目特定配置文件"></p><h3 id="脚本仓库"><a href="#脚本仓库" class="headerlink" title="脚本仓库"></a>脚本仓库</h3><p>脚本仓库的脚本即是整个方案的灵魂，这里的脚本是将多个项目的构建抽象、泛化，遇到与项目有关的都需要定义为变量。</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line">pipeline&#123;</span><br><span class="line">    agent any</span><br><span class="line">    stages&#123;</span><br><span class="line">        stage(<span class="string">&#x27;检查环境&#x27;</span>)&#123;</span><br><span class="line">            steps&#123;</span><br><span class="line">                sh <span class="attr">label:</span> <span class="string">&#x27;输出环境变量&#x27;</span>, <span class="attr">script:</span> <span class="string">&#x27;printenv&#x27;</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        stage(<span class="string">&#x27;检出代码&#x27;</span>)&#123;</span><br><span class="line">            steps&#123;</span><br><span class="line">                git <span class="attr">branch:</span> <span class="string">&quot;$&#123;__branch&#125;&quot;</span>, <span class="attr">credentialsId:</span> <span class="string">&#x27;fca179ba-0def-407c-8622-6bdef80dea85&#x27;</span>, <span class="attr">url:</span> <span class="string">&quot;$&#123;__repo&#125;&quot;</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        stage(<span class="string">&#x27;加载配置&#x27;</span>)&#123;</span><br><span class="line">            steps&#123;</span><br><span class="line">                script&#123;</span><br><span class="line">                    <span class="keyword">def</span> configPath = <span class="string">&quot;./jenkins.groovy&quot;</span></span><br><span class="line">                    <span class="keyword">if</span>(fileExists(<span class="string">&quot;$&#123;configPath&#125;&quot;</span>))&#123;</span><br><span class="line">                        load <span class="string">&quot;$&#123;configPath&#125;&quot;</span></span><br><span class="line">                        echo <span class="string">&quot;找到配置文件$&#123;configPath&#125;，加载成功&quot;</span></span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        echo <span class="string">&quot;配置文件$&#123;configPath&#125;不存在&quot;</span></span><br><span class="line">                        sh <span class="string">&quot;exit 1&quot;</span></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">        stage(<span class="string">&#x27;编译&#x27;</span>)&#123;</span><br><span class="line">            steps&#123;</span><br><span class="line">                sh <span class="attr">label:</span> <span class="string">&quot;执行 ./gradlew clean assembleRelease&quot;</span>, <span class="attr">script:</span> <span class="string">&quot;./gradlew clean assembleRelease&quot;</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        stage(<span class="string">&#x27;单元测试&#x27;</span>)&#123;</span><br><span class="line">            steps&#123;</span><br><span class="line">                script&#123;</span><br><span class="line">                    <span class="keyword">if</span>(Boolean.valueOf(<span class="string">&quot;$&#123;__open_unit_test&#125;&quot;</span>))&#123;</span><br><span class="line">                        sh <span class="attr">label:</span> <span class="string">&#x27;执行 ./gradlew test&#x27;</span>, <span class="attr">script:</span> <span class="string">&#x27;./gradlew test&#x27;</span></span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        echo <span class="string">&quot;跳过单元测试&quot;</span></span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            post&#123;</span><br><span class="line">                always&#123;</span><br><span class="line">                    junit <span class="attr">allowEmptyResults:</span> <span class="literal">true</span>, <span class="attr">testResults:</span> <span class="string">&#x27;**/test-results/**/*.xml&#x27;</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        stage(<span class="string">&#x27;lint 扫描&#x27;</span>)&#123;</span><br><span class="line">            steps&#123;</span><br><span class="line">                script&#123;</span><br><span class="line">                    <span class="keyword">if</span>(Boolean.valueOf(<span class="string">&quot;$&#123;__open_lint&#125;&quot;</span>))&#123;</span><br><span class="line">                        sh <span class="attr">label:</span> <span class="string">&#x27;执行 ./gradlew lint&#x27;</span>, <span class="attr">script:</span> <span class="string">&#x27;./gradlew lint&#x27;</span></span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123; </span><br><span class="line">                        echo <span class="string">&quot;跳过 lint 扫描&quot;</span></span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            post&#123;</span><br><span class="line">                always&#123;</span><br><span class="line">                    androidLint <span class="attr">canComputeNew:</span> <span class="literal">false</span>, <span class="attr">defaultEncoding:</span> <span class="string">&#x27;UTF-8&#x27;</span>, <span class="attr">healthy:</span> <span class="string">&#x27;&#x27;</span>, <span class="attr">pattern:</span> <span class="string">&#x27;**/lint-results*.xml&#x27;</span>, <span class="attr">unHealthy:</span> <span class="string">&#x27;&#x27;</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        stage(<span class="string">&#x27;归档&#x27;</span>)&#123;</span><br><span class="line">            steps&#123;</span><br><span class="line">                archiveArtifacts <span class="string">&#x27;**/*.apk&#x27;</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>pipline 实际上支持两种语法，脚本式和声明式，这里简单提一下，脚本式语法可以理解为，你就是在写 groovy，这里我使用的是社区推荐的声明式语法，比较简单、易读。当然，不是说用了声明式语法就8能写 groovy 了，在 <code>script</code> 闭包里仍然可以使用 groovy。</p><p>PS：因为我们需要从项目里读取特定的配置文件，并且加载文件里的参数，但是怎么做呢？这里我参考了美团兄 dei 的一个小技巧，判断项目中存在 <code>jenkins.groovy</code> 文件后，使用 groovy 的 <code>load</code> 函数加载配置文件，从而读取到里面的参数。</p><h3 id="大功告成"><a href="#大功告成" class="headerlink" title="大功告成"></a>大功告成</h3><p>这里点击参数化构建-&gt;开始构建，然后等个几分钟，就可以看到构建的结果。</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Android%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E5%90%8E%E7%AF%87/pipeline%E6%9E%84%E5%BB%BA%E7%BB%93%E6%9E%9C.png" alt="jenkins 构建结果"></p><ul><li>可以清楚地看到每个构建阶段的耗时，方便后期优化</li><li>构建出错时，使用 stage view 可以快速定位出出错的阶段</li></ul><h2 id="pipeline-编写指南"><a href="#pipeline-编写指南" class="headerlink" title="pipeline 编写指南"></a>pipeline 编写指南</h2><h3 id="使用-pipline-代码生成器学习"><a href="#使用-pipline-代码生成器学习" class="headerlink" title="使用 pipline 代码生成器学习"></a>使用 pipline 代码生成器学习</h3><p>对于 pipeline 的新手来说，如何编写 pipeline 是一道坎，没越过的时候就会觉得 pipeline 这种构建真麻烦，转而继续使用自由风格的软件。迈过之后又会真香真香了2333</p><p>其实 Jenkins 为了减低编写 pipeline 的门槛，提供了一个 pipline 代码片段的生成器，通过界面操作就能生成相应代码。</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Android%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E5%90%8E%E7%AF%87/pipeline%E8%AF%AD%E6%B3%95%E5%85%A5%E5%8F%A3.png" alt="pipline语法入口"></p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Android%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E5%90%8E%E7%AF%87/pipeline%E4%BB%A3%E7%A0%81%E7%94%9F%E6%88%90%E5%99%A8.png" alt="pipline代码生成器"></p><h3 id="Jenkinsfile-编写和语法校验"><a href="#Jenkinsfile-编写和语法校验" class="headerlink" title="Jenkinsfile 编写和语法校验"></a>Jenkinsfile 编写和语法校验</h3><p>就这么说吧，JenkinsFile 真的就是个孤儿，从诞生以来就没有啥好的开发工具支持，矮中取长，这里推荐俩我正在使用的pa：</p><ul><li>开发的时候只能用 VSCode 的 <a href="https://marketplace.visualstudio.com/items?itemName=naco-siren.gradle-language">Groovy 语法</a>高亮进行开发</li><li>校验 Jenkinsfile 的语法可以使用 VSCode 的插件 <a href="https://marketplace.visualstudio.com/items?itemName=janjoerke.jenkins-pipeline-linter-connector">Jenkins Pipline Linter Connector</a></li></ul><p>PS：别对那个校验插件做太多期待，该插件只能利用 Jenkins Api 进行语法校验。</p><h2 id="凭证管理"><a href="#凭证管理" class="headerlink" title="凭证管理"></a>凭证管理</h2><h3 id="为什么需要凭证"><a href="#为什么需要凭证" class="headerlink" title="为什么需要凭证"></a>为什么需要凭证</h3><p>在 Jenkinsfile 中使用明文密码会造成安全隐患。凭证是 Jenkins 进行受限操作时的凭据。比如使用 SSH 登录远程机器时，用户名和密码或 SSH key 就是凭据。而这些凭据不可能以明文写在 Jenkinsfile 中。Jenkins 凭证管理指的就是对这些凭证进行管理。</p><p>为了最大限度地提高安全性，在 Jenkins master 节点对凭证进行加密存储（通过 Jenkins 实例 ID 加密），只有通过它们的凭证 ID 才能在 pipline 中使用，并且限制了将证书从一个 Jenkins实例赋值到另一个 Jenkins 实例的能力。</p><h3 id="密钥保护（可选）"><a href="#密钥保护（可选）" class="headerlink" title="密钥保护（可选）"></a>密钥保护（可选）</h3><p>通过 Jenkins 我们可以把 Android 的签名文件保护起来。</p><h4 id="安装-Android-Signing-Plugin"><a href="#安装-Android-Signing-Plugin" class="headerlink" title="安装 Android Signing Plugin"></a>安装 Android Signing Plugin</h4><p>在 Jenkins 上安装 <a href="https://github.com/jenkinsci/android-signing-plugin">Android Signing Plugin</a> 插件</p><h4 id="格式转换"><a href="#格式转换" class="headerlink" title="格式转换"></a>格式转换</h4><p>Android Signing Plugin 依赖 Credentials Plugin 只支持 PKSC#12 格式的证书，因此，需要将 JKS 证书转换为 PKCS#12 格式：</p><blockquote><p>keytool -importkeystore -srckeystore signing.jks -srcstoretype JKS -deststoretype PKCS12 -destkeystore signing.p12</p></blockquote><p>然后将转换好的证书上传到 Credentials 并且配置好 ID，比如取名为 ANDROID_SIGN_KEY_STORE。</p><h4 id="编写对应的-pipline-代码"><a href="#编写对应的-pipline-代码" class="headerlink" title="编写对应的 pipline 代码"></a>编写对应的 pipline 代码</h4><p>该插件并不支持 pipline自动生成代码，所以只能看官方文档手撸代码：</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">stage(<span class="string">&#x27;签名Apk&#x27;</span>)&#123;</span><br><span class="line">    steps&#123;</span><br><span class="line">        signAndroidApks(</span><br><span class="line">            <span class="symbol">keyStoreId:</span> <span class="string">&quot;ANDROID_SIGN_KEY_STORE&quot;</span>,</span><br><span class="line">            <span class="symbol">keyAlias:</span> <span class="string">&quot;ANDROID&quot;</span>,</span><br><span class="line">            <span class="symbol">apksToSign:</span> <span class="string">&quot;**/*-unsigned.apk&quot;</span>,</span><br><span class="line">            <span class="symbol">archiveSignedApks:</span> <span class="literal">false</span>,</span><br><span class="line">            <span class="symbol">archiveUnsignedApks:</span> <span class="literal">false</span></span><br><span class="line">        )               </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><ul><li>《Jenkins 2.x 实践指南》，这本书是为数不多的不跟你瞎比比，教你怎么搭建 Jenkins 赚页数，而是单刀直入直接开始 pipline 开荒教学，不厚，但得细看，简单明了的阐述了很多概念，推荐跟着书上的操作实际操作下。</li><li><a href="https://tech.meituan.com/2018/08/02/erp-cd-jenkins-pipeline.html">Jenkins的Pipeline脚本在美团餐饮SaaS中的实践</a>，这里也非常感谢美团兄dei 的文章指南，给了我很多启发。</li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2020/01/23/Android%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E5%AE%9E%E8%B7%B5%C2%B7%E5%90%8E%E7%AF%87/</id>
    <link href="https://hellovass.github.io/2020/01/23/Android%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E5%AE%9E%E8%B7%B5%C2%B7%E5%90%8E%E7%AF%87/"/>
    <published>2020-01-23T07:39:52.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>距离上篇《Android持续集成·前篇》已经有些时日，这次将会填上上篇留下的大坑，pipline 构建。</p>]]>
    </summary>
    <title>Android持续集成实践·后篇</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Android" scheme="https://hellovass.github.io/tags/Android/"/>
    <content>
      <![CDATA[<h2 id="为什么要做单元测试"><a href="#为什么要做单元测试" class="headerlink" title="为什么要做单元测试"></a>为什么要做单元测试</h2><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><ul><li>当组内有强迫症的童鞋想重构某块代码时，单元测试就是坚实的后盾，想象如下对话：</li></ul><blockquote><p>童鞋A：项目里这块代码写的不优雅，我要重构</p><p>负责人：重构啊，好事啊，可以，只要你重构之后单元测试能全部运行通过。</p><p>童鞋A：…</p></blockquote><p>当然，这段对话的前提是建立在单元测试的覆盖率足够高的基础上</p><ul><li>优化设计，开发者先编写测试，然后再编写代码，让代码通过测试的方式来开发业务，这也就是 TDD 开发，强迫每个开发者写出易于调试和可测试的代码，并且很好的解耦合。</li><li>No 文档，单元测试就是一种很好的文档，自己就展示了函数和类的使用。并且这份文档还是可以编译、运行的，与项目代码保持同步</li><li>可回归，与 CI 集成之后实现了真正的敏捷开发，重构完、开发完新业务，线上运行测试，而不需要将代码再部署到真机上，然后人肉点点点。</li></ul><span id="more"></span><h2 id="那为什么之前不写呢"><a href="#那为什么之前不写呢" class="headerlink" title="那为什么之前不写呢"></a>那为什么之前不写呢</h2><p>首先，是对未知事物的了解导致不理解单测带来的好处和收益，然后就是经典的几个理由：</p><ul><li>单元测试太花时间了</li></ul><p>平时开发那么忙，业务都写不完，哪有时间写单测！我第一家公司工作时，给自己找的理由就是这个，不过当时确实很忙。</p><p>但是，话又说过来，当时负责前端的童鞋却能很快地把业务&amp;单测都写完，还有时间学习和打游戏？！仔细想了想，大家都是前端（Android 也算），为什么他能在写单测的基础上还把业务完成。或许，我们大部分的时间都花在部署代码到真机或者模拟器上，然后人肉测试和调试程序，又或者写代码的时候就能多脏就多脏，管他什么灵活性和设计，使得需求变更的时候又要花很多时间在复杂的代码中艰难前行，找出核心的那一小坨，修修补补，完成新的需求。而对新的改动又毫无信心，只能手动测试、调试。。。如此反复，陷入加班的泥泞，于是，代码越来越乱，难以维护，每天上班看着那一坨名为加班无底洞的东西就很烦。最后在别人现充的时节感叹一句，不是自己不努力，奈何加班太无力啊！</p><ul><li>我不是测试，所以</li></ul><p>单元测试不该我来写？黑盒测试，也就是人眼验证UI和人肉点点点确实不是开发的工作，但是白盒测试，也就是了解代码的前提下来写测试，那还真是开发童鞋的锅，得接好！毕竟，谁能比开发自己更熟悉自己写的业务代码呢？</p><ul><li>单测太难写了</li></ul><p>这个理由可能是大部分非常想写单测但是却难以落地的童鞋的心声了。比如我，大部分时候都是接二手项目，而到手的项目，别说单测了，可能代码本身的结构、逻辑都一塌糊涂，还充斥着大量的bug。这个时候，应该试着去熟悉这些代码，然后调整代码，使少部分能被测试的代码先通过单元测试，让整个程序慢慢变得可测试起来。</p><blockquote><p>单元测试，有总好过没有</p></blockquote><h2 id="如何落地呢"><a href="#如何落地呢" class="headerlink" title="如何落地呢"></a>如何落地呢</h2><p>TODO</p>]]>
    </content>
    <id>https://hellovass.github.io/2019/04/19/Android%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E5%AE%9E%E8%B7%B5%E5%92%8C%E6%8E%A2%E7%B4%A2/</id>
    <link href="https://hellovass.github.io/2019/04/19/Android%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E5%AE%9E%E8%B7%B5%E5%92%8C%E6%8E%A2%E7%B4%A2/"/>
    <published>2019-04-19T05:52:48.000Z</published>
    <summary>
      <![CDATA[<h2 id="为什么要做单元测试"><a href="#为什么要做单元测试" class="headerlink" title="为什么要做单元测试"></a>为什么要做单元测试</h2><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><ul>
<li>当组内有强迫症的童鞋想重构某块代码时，单元测试就是坚实的后盾，想象如下对话：</li>
</ul>
<blockquote>
<p>童鞋A：项目里这块代码写的不优雅，我要重构</p>
<p>负责人：重构啊，好事啊，可以，只要你重构之后单元测试能全部运行通过。</p>
<p>童鞋A：…</p>
</blockquote>
<p>当然，这段对话的前提是建立在单元测试的覆盖率足够高的基础上</p>
<ul>
<li>优化设计，开发者先编写测试，然后再编写代码，让代码通过测试的方式来开发业务，这也就是 TDD 开发，强迫每个开发者写出易于调试和可测试的代码，并且很好的解耦合。</li>
<li>No 文档，单元测试就是一种很好的文档，自己就展示了函数和类的使用。并且这份文档还是可以编译、运行的，与项目代码保持同步</li>
<li>可回归，与 CI 集成之后实现了真正的敏捷开发，重构完、开发完新业务，线上运行测试，而不需要将代码再部署到真机上，然后人肉点点点。</li>
</ul>]]>
    </summary>
    <title>Android单元测试实践和探索</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="CI/CD" scheme="https://hellovass.github.io/tags/CI-CD/"/>
    <content>
      <![CDATA[<h2 id="为什么要持续集成"><a href="#为什么要持续集成" class="headerlink" title="为什么要持续集成"></a>为什么要持续集成</h2><blockquote><p>人肉构建太累了</p></blockquote><h3 id="我们想做的事情"><a href="#我们想做的事情" class="headerlink" title="我们想做的事情"></a>我们想做的事情</h3><ul><li>clone 仓库的代码</li><li>静态代码检查（可选）</li><li>单元测试（可选）</li><li>编译打包 apk</li><li>上传到测试分发平台</li><li>自动化测试（可选）</li><li>通知相关人员构建结果等</li></ul><p>救救孩子吧！！！</p><span id="more"></span><h2 id="Jenkins-构建"><a href="#Jenkins-构建" class="headerlink" title="Jenkins 构建"></a>Jenkins 构建</h2><p>使用的比较多的是自由风格的软件项目（Jenkins 构建的一种方式，会结合 SCM 和构建系统来构建项目，甚至可以构建软件以外的系统）。目前的构建就是基于<strong>自由风格的软件项目</strong>来做的，简单，基本满足需求，但也有不方便的地方，之后会说到。</p><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><p>win 下非常简单，选择安装版（不需要依赖 Tomcat），安装完成之后就可以 <code>ip:port</code> 启动。</p><h3 id="自由风格的软件项目"><a href="#自由风格的软件项目" class="headerlink" title="自由风格的软件项目"></a>自由风格的软件项目</h3><h4 id="安装插件"><a href="#安装插件" class="headerlink" title="安装插件"></a>安装插件</h4><ul><li>Git Plugin</li><li>Gradle Plugin</li><li>Android Signing Plugin</li><li>Build Name Setter</li><li>Date Parameter Plugin</li><li>Dynamic Parameter Plugin</li><li>Git Parameter Plugin</li></ul><h4 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h4><h5 id="Manage-Jenkins-Global-Tool-Configuration"><a href="#Manage-Jenkins-Global-Tool-Configuration" class="headerlink" title="Manage Jenkins-Global Tool Configuration"></a>Manage Jenkins-Global Tool Configuration</h5><p>如果插件安装成功，可以看到这三个插件：</p><ul><li>jdk 插件<img src="http://assets.processon.com/chart_image/5cb024cae4b09ccab355c702.png" alt="jdk_plugin"></li><li>git 插件<img src="http://assets.processon.com/chart_image/5cb024c5e4b09a003b2e412f.png" alt="git_plugin"></li><li>gradle 插件<img src="http://assets.processon.com/chart_image/5ca5b6bde4b031d0225d5d52.png" alt="gradle_plugin"></li></ul><blockquote><p>注意：Jenkins 机器上的 Gradle 的版本必须&gt;&#x3D;项目中的 Gradle 的版本，否则就会编译失败。</p></blockquote><h5 id="Configure-System"><a href="#Configure-System" class="headerlink" title="Configure System"></a>Configure System</h5><p>配置 Android SDK 路径<img src="http://assets.processon.com/chart_image/5ca5c81ee4b029f6dae74553.png" alt="android_sdk_plugin"></p><p>注意这里的 Name 必须与本机的<strong>环境变量Key值</strong>一致</p><h5 id="创建项目"><a href="#创建项目" class="headerlink" title="创建项目"></a>创建项目</h5><p>点击 <strong>new item</strong>，选择 <strong>Freestyle project</strong></p><h5 id="项目配置"><a href="#项目配置" class="headerlink" title="项目配置"></a>项目配置</h5><p>选择 <strong>Source Code ManageMent</strong><img src="http://assets.processon.com/chart_image/5cab22e1e4b06765f088f007.png" alt="源码管理"></p><h5 id="构建步骤"><a href="#构建步骤" class="headerlink" title="构建步骤"></a>构建步骤</h5><p>点击 <strong>Add build step</strong>，选择 <strong>Invoke Gradle script</strong>，配置构建时 Gradle 版本，如图：<img src="http://assets.processon.com/chart_image/5cb02752e4b02a2858e8f87c.png" alt="invoke_gradle_script"></p><blockquote><p>再啰嗦一遍，注意 ci 机器上的 gradle 版本和工程里的 gradle 版本必须一致！！！</p><p>然后配置一下需要执行的 tasks，先 clean 工程，然后根据 FLAVOR（渠道）和 BuildType（构建类型）来打包，生成的 apk 数目 &#x3D; 渠道数*构建类型数</p></blockquote><h5 id="恭喜，简单配置完成"><a href="#恭喜，简单配置完成" class="headerlink" title="恭喜，简单配置完成"></a>恭喜，简单配置完成</h5><p>点击 save，就算配置完成了。当然，以上只是最基础的 Jenkins 配置，接下来看看如何进行参数化构建。</p><h4 id="参数化构建"><a href="#参数化构建" class="headerlink" title="参数化构建"></a>参数化构建</h4><p>我们想要构建的产物个性化一些，比如:<code>#工程名${分支名}_${渠道}_${构建类型}_${构建时间}_${Jenkins构建时的序列号}</code>，所以，我们需要的参数有：</p><ul><li>BRANCH</li><li>FLAVOR</li><li>BUILD_TYPE</li><li>BUILD_TIME</li><li>BUILD_NUMBER</li></ul><p>然后，需要能在本地打包也能在 Jenkins 机器上打包，所以，我们还需要 <code>CI_BUILD</code> 来区别是否在 CI 机器上构建:</p><ul><li>CI_BUILD</li></ul><p>本地构建类型为 debug 的时候，默认会使用 Android Studio 自己生成的 debug.keystore，而不同机器上的 debug.keystore 的 sha-1 是不一样的！这会导致诸如高德地图这样的第三方 SDK 无法正常运行，所以，我们还需要同一份 debug.keystore，于是脚本应该这么写：</p><figure class="highlight groovy"><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">signingConfigs &#123;</span><br><span class="line">    config &#123;</span><br><span class="line">        <span class="keyword">if</span> (CI_BUILD == <span class="string">&quot;true&quot;</span>) &#123;</span><br><span class="line">            keyAlias KEY_ALIAS</span><br><span class="line">            keyPassword KEY_PASSWORD</span><br><span class="line">            storeFile file(STORE_FILE)</span><br><span class="line">            storePassword STORE_PASSWORD</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123; <span class="comment">// 读取本地 keystore.properties 文件</span></span><br><span class="line">            <span class="keyword">def</span> keystoreProperties = <span class="keyword">new</span> Properties()</span><br><span class="line">   keystoreProperties.load(<span class="keyword">new</span> FileInputStream(rootProject.file(<span class="string">&quot;keystore.properties&quot;</span>)))</span><br><span class="line">            keyAlias keystoreProperties[<span class="string">&#x27;keyAlias&#x27;</span>]</span><br><span class="line">            keyPassword keystoreProperties[<span class="string">&#x27;keyPassword&#x27;</span>]</span><br><span class="line">            storeFile file(keystoreProperties[<span class="string">&#x27;storeFilePath&#x27;</span>])</span><br><span class="line">            storePassword keystoreProperties[<span class="string">&#x27;storePassword&#x27;</span>]</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>当 CI_BUILD &#x3D;&#x3D; false &amp;&amp; 本地构建，使用本地同样的 debug.keystore 来签名！</p></blockquote><h5 id="勾选-This-project-is-parameterized，然后开始添加参数："><a href="#勾选-This-project-is-parameterized，然后开始添加参数：" class="headerlink" title="勾选 This project is parameterized，然后开始添加参数："></a>勾选 This project is parameterized，然后开始添加参数：</h5><table><thead><tr><th>参数名</th><th>参数类型</th><th>可选值</th></tr></thead><tbody><tr><td>CI_BUILD</td><td>Choice Parameter</td><td>true、false</td></tr><tr><td>BRANCH</td><td>Git Parameter</td><td>tag、branch</td></tr><tr><td>BUILD_TYPE</td><td>Choice Parameter</td><td>Release、Debug</td></tr><tr><td>BUILD_FLAVOR</td><td>Choice Parameter</td><td>envStaging、envTesting</td></tr><tr><td>BUILD_TIME</td><td>Date Parameter</td><td>当前时间</td></tr><tr><td>KEY_ALIAS</td><td>String Parameter</td><td>———————-</td></tr><tr><td>KEY_PASSWORD</td><td>String Parameter</td><td>———————-</td></tr><tr><td>STORE_PASSWORD</td><td>String Parameter</td><td>———————-</td></tr><tr><td>STORE_FILE</td><td>String Parameter</td><td>———————-</td></tr></tbody></table><p><strong>勾选 Pass job parameters as Gradle properties</strong>，该选项会帮我们把参数注入到 gradle.properties，替换文件中的对应值。</p><figure class="highlight groovy"><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"># 是否为 CI 环境</span><br><span class="line">CI_BUILD=<span class="literal">false</span></span><br></pre></td></tr></table></figure><h5 id="参数化构建完成"><a href="#参数化构建完成" class="headerlink" title="参数化构建完成"></a>参数化构建完成</h5><p>左侧出现了 <code>Build with Parameters</code>，右侧：<img src="http://assets.processon.com/chart_image/5cb03a21e4b0b62750f80baf.png" alt="右侧面板"></p><h3 id="Pipeline-构建"><a href="#Pipeline-构建" class="headerlink" title="Pipeline 构建"></a>Pipeline 构建</h3><ul><li>TODO</li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2019/04/04/Android%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E5%AE%9E%E8%B7%B5%C2%B7%E5%89%8D%E7%AF%87/</id>
    <link href="https://hellovass.github.io/2019/04/04/Android%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E5%AE%9E%E8%B7%B5%C2%B7%E5%89%8D%E7%AF%87/"/>
    <published>2019-04-04T03:56:36.000Z</published>
    <summary>
      <![CDATA[<h2 id="为什么要持续集成"><a href="#为什么要持续集成" class="headerlink" title="为什么要持续集成"></a>为什么要持续集成</h2><blockquote>
<p>人肉构建太累了</p>
</blockquote>
<h3 id="我们想做的事情"><a href="#我们想做的事情" class="headerlink" title="我们想做的事情"></a>我们想做的事情</h3><ul>
<li>clone 仓库的代码</li>
<li>静态代码检查（可选）</li>
<li>单元测试（可选）</li>
<li>编译打包 apk</li>
<li>上传到测试分发平台</li>
<li>自动化测试（可选）</li>
<li>通知相关人员构建结果等</li>
</ul>
<p>救救孩子吧！！！</p>]]>
    </summary>
    <title>Android持续集成实践·前篇</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Android" scheme="https://hellovass.github.io/tags/Android/"/>
    <content>
      <![CDATA[<h2 id="为什么需要组件化"><a href="#为什么需要组件化" class="headerlink" title="为什么需要组件化"></a>为什么需要组件化</h2><h3 id="组件化之前的项目结构"><a href="#组件化之前的项目结构" class="headerlink" title="组件化之前的项目结构"></a>组件化之前的项目结构</h3><p><img src="http://assets.processon.com/chart_image/5ca44870e4b035b243c08eab.png" alt="组件化之前的项目结构"></p><ul><li>App 工程<ul><li>多个业务组件</li><li>一个静态的路由，依赖了所有的业务组件，业务组件可以通过 Router 暴露出来的方法跳转到其他业务组件</li></ul></li><li>CommonLibrary<ul><li>多个技术组件</li></ul></li></ul><span id="more"></span><h4 id="存在的问题"><a href="#存在的问题" class="headerlink" title="存在的问题"></a>存在的问题</h4><ul><li>随着业务的逐渐迭代，App 工程内的业务组件逐渐增多，工程膨胀</li><li>缺少编译隔离，模块之间存在相互依赖的可能</li><li>静态路由，需要手动维护，增加开发成本</li></ul><h3 id="业务组件通信"><a href="#业务组件通信" class="headerlink" title="业务组件通信"></a>业务组件通信</h3><p>仔细思考，业务组件间的通信会有哪几种情况</p><ul><li>跳转需求，通常情况下是组件A跳转到组件B，并且可能需要返回值</li><li>方法调用需求，组件A调用组件B的方法，并且可能有返回值</li></ul><h4 id="跨组件方法调用"><a href="#跨组件方法调用" class="headerlink" title="跨组件方法调用"></a>跨组件方法调用</h4><h5 id="被调用的业务组件下沉"><a href="#被调用的业务组件下沉" class="headerlink" title="被调用的业务组件下沉"></a>被调用的业务组件下沉</h5><p><img src="http://assets.processon.com/chart_image/5ca46752e4b08743436256b6.png" alt="组件下沉"></p><p>这种方案最暴力直接，但是问题也最大。只要其他业务组件需要调用，就移动到不合理的层级，容易导致下层越来越臃肿，也就失去了组件化的灵魂。</p><h5 id="原生提供的方案"><a href="#原生提供的方案" class="headerlink" title="原生提供的方案"></a>原生提供的方案</h5><ul><li>LocalBroadcast</li><li>AIDL</li><li>ContentProvider</li></ul><h5 id="消息总线"><a href="#消息总线" class="headerlink" title="消息总线"></a>消息总线</h5><p>EventBus、Otto、RxBus，甚至自己造一个轮子。</p><p><img src="http://assets.processon.com/chart_image/5ca469cce4b0874343625ea8.png" alt="EventBus"></p><p>EventBus 的问题也很明显，解耦太彻底，控制不当，事件满天飞。一个 Event 通常会导致多处 state 的变化，这种变化只能反映在UI上，而没有一个统一的地方可以看到这些 state 的变化，导致 debug 异常困难。</p><h5 id="SOA-思想"><a href="#SOA-思想" class="headerlink" title="SOA 思想"></a>SOA 思想</h5><p><img src="http://assets.processon.com/chart_image/5ca46d75e4b031d0225bfc60.png" alt="SOA"></p><p>这种思想来源于服务端的 SOA设计思路，原理就是将模块间的依赖导致，变为功能的提供与使用。服务提供者可以看作是一个中间件（代理），代理了所有组件能提供的服务（方法），然后组件需要调用某个服务，只需要获取到服务提供者，然后调用服务提供者提供的方法就行了。</p><h5 id="Redux"><a href="#Redux" class="headerlink" title="Redux"></a>Redux</h5><p>该思想来源于前端著名的状态管理库 <a href="https://www.redux.org.cn/">Redux</a>，</p><p><img src="http://assets.processon.com/chart_image/5ca471e0e4b034408dec0606.png" alt="Redux"></p><p>看上去似乎和之前分享的 EventBus 没什么区别，但实际上，Redux 还做了这些事情：</p><ul><li>对状态的集中管理，整个 App 里只有一个挂载了多个 state 的扁平树 AppState</li><li>dispatch action &#x3D;&gt; AppState changed，可以打印 action、AppState，实现了状态的追踪</li></ul><h4 id="组件间跳转"><a href="#组件间跳转" class="headerlink" title="组件间跳转"></a>组件间跳转</h4><p>这就涉及到近年来移动端最火热的话题之一了，移动端路由设计。</p><h5 id="蛮荒时代-静态路由"><a href="#蛮荒时代-静态路由" class="headerlink" title="蛮荒时代-静态路由"></a>蛮荒时代-静态路由</h5><p>静态路由，简单来说，就是：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">object</span> Router&#123;</span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">navigateToA</span><span class="params">()</span></span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">navigateToB</span><span class="params">()</span></span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">navigateToC</span><span class="params">()</span></span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>需要开发者手动维护对业务组件的依赖，多人协作时容易起冲突，Router 还必须与业务组件在同一个工程，局限性非常大，也可以说是最惨的时代。</p><h5 id="旧石器时代-apt路由"><a href="#旧石器时代-apt路由" class="headerlink" title="旧石器时代-apt路由"></a>旧石器时代-apt路由</h5><p>这类栗子非常多，网上大部分开源框架也是基于这个方案来实现，核心就是 <a href="https://joyrun.github.io/2016/07/19/AptHelloWorld/">APT</a>。但是，使用 Apt 也有一些问题，每个 module library 需要手动注册，原因是 Apt 是在 javacompile 任务前插入了一个 task，所以只对自己的 module library 的注解做处理。</p><h5 id="新石器时代-plugin路由"><a href="#新石器时代-plugin路由" class="headerlink" title="新石器时代-plugin路由"></a>新石器时代-plugin路由</h5><p>这个就更厉害了，通过编写 gradle 插件实现路由表的自动加载，大概原理：</p><blockquote><p>在编译时，扫描所有类，将符合条件的类收集起来，并通过修改字节码生成注册代码到指定的管理类中，从而实现编译时自动注册的功能，不用再关心项目中有哪些组件类了。不会增加新的 class，不需要反射，运行时直接调用组件的构造方法。</p></blockquote><h2 id="组件化演进"><a href="#组件化演进" class="headerlink" title="组件化演进"></a>组件化演进</h2><h3 id="业务组件插拔"><a href="#业务组件插拔" class="headerlink" title="业务组件插拔"></a>业务组件插拔</h3><ul><li>TODO</li></ul><h3 id="Git-多仓库管理"><a href="#Git-多仓库管理" class="headerlink" title="Git 多仓库管理"></a>Git 多仓库管理</h3><ul><li>TODO</li></ul><h3 id="业务组件开发流程"><a href="#业务组件开发流程" class="headerlink" title="业务组件开发流程"></a>业务组件开发流程</h3><ul><li>TODO</li></ul><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="http://app.zhenzikj.com/blog/300000023/10128.html">Android组件自动注册方案</a></li><li><a href="https://dmanager.github.io/android/2017/12/30/androud-mo-kuai-hua/">二维火掌柜Android模块化架构实践</a></li><li><a href="https://joyrun.github.io/2016/07/19/AptHelloWorld/">Android APT （编译时代码生成）最佳实践</a></li><li><a href="https://xiaozhuanlan.com/topic/7095341862">Android模块化平台设计-讲稿</a></li><li><a href="http://www.10tiao.com/html/169/201612/2650821633/1.html">Android组件化核心之路由实现</a></li><li><a href="https://www.jianshu.com/p/8a3eeeaf01e8">Android 组件化 —— 路由设计最佳实践</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2019/04/02/Android%E7%BB%84%E4%BB%B6%E5%8C%96%E5%AE%9E%E8%B7%B5%E4%B8%8E%E6%8E%A2%E7%B4%A2/</id>
    <link href="https://hellovass.github.io/2019/04/02/Android%E7%BB%84%E4%BB%B6%E5%8C%96%E5%AE%9E%E8%B7%B5%E4%B8%8E%E6%8E%A2%E7%B4%A2/"/>
    <published>2019-04-02T03:56:53.000Z</published>
    <summary>
      <![CDATA[<h2 id="为什么需要组件化"><a href="#为什么需要组件化" class="headerlink" title="为什么需要组件化"></a>为什么需要组件化</h2><h3 id="组件化之前的项目结构"><a href="#组件化之前的项目结构" class="headerlink" title="组件化之前的项目结构"></a>组件化之前的项目结构</h3><p><img src="http://assets.processon.com/chart_image/5ca44870e4b035b243c08eab.png" alt="组件化之前的项目结构"></p>
<ul>
<li>App 工程<ul>
<li>多个业务组件</li>
<li>一个静态的路由，依赖了所有的业务组件，业务组件可以通过 Router 暴露出来的方法跳转到其他业务组件</li>
</ul>
</li>
<li>CommonLibrary<ul>
<li>多个技术组件</li>
</ul>
</li>
</ul>]]>
    </summary>
    <title>Android组件化实践与探索</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="2018-10-10，和-redux-的相遇"><a href="#2018-10-10，和-redux-的相遇" class="headerlink" title="2018.10.10，和 redux 的相遇"></a>2018.10.10，和 redux 的相遇</h2><blockquote><p>女人都是大猪蹄子，哼</p></blockquote><p>记得刚捡到 redux 的时候她还辣么弱小，可怜加无助。阴雨绵绵的天气，在公司楼下喵喵喵地叫着，本来是想带她避避雨，结果，手一伸，就直接爬我身上，赖着不走了。我能怎么办，我也很绝望啊。没办法，就直接把外套脱下往这个脏兮兮的小家伙身上一套，抱在怀里就上楼去了。</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/redux%E6%98%AF%E4%B8%AA%E5%A4%A7%E7%8C%AA%E8%B9%84%E5%AD%90/%E4%BB%8E%E5%89%8D%E7%9A%84redux%E5%A4%9A%E4%B9%88%E5%8F%AF%E7%88%B1%E5%95%8A.jpg" alt="从前的你多么可爱啊"></p><span id="more"></span><h2 id="现在呢，呵呵，大猪蹄子，每天都咬我"><a href="#现在呢，呵呵，大猪蹄子，每天都咬我" class="headerlink" title="现在呢，呵呵，大猪蹄子，每天都咬我"></a>现在呢，呵呵，大猪蹄子，每天都咬我</h2>]]>
    </content>
    <id>https://hellovass.github.io/2018/12/14/redux%20%E6%98%AF%E4%B8%AA%E5%A4%A7%E7%8C%AA%E8%B9%84%E5%AD%90/</id>
    <link href="https://hellovass.github.io/2018/12/14/redux%20%E6%98%AF%E4%B8%AA%E5%A4%A7%E7%8C%AA%E8%B9%84%E5%AD%90/"/>
    <published>2018-12-14T14:33:11.000Z</published>
    <summary>
      <![CDATA[<h2 id="2018-10-10，和-redux-的相遇"><a href="#2018-10-10，和-redux-的相遇" class="headerlink" title="2018.10.10，和 redux 的相遇"></a>2018.10.10，和 redux 的相遇</h2><blockquote>
<p>女人都是大猪蹄子，哼</p>
</blockquote>
<p>记得刚捡到 redux 的时候她还辣么弱小，可怜加无助。阴雨绵绵的天气，在公司楼下喵喵喵地叫着，本来是想带她避避雨，结果，手一伸，就直接爬我身上，赖着不走了。我能怎么办，我也很绝望啊。没办法，就直接把外套脱下往这个脏兮兮的小家伙身上一套，抱在怀里就上楼去了。</p>
<p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/redux%E6%98%AF%E4%B8%AA%E5%A4%A7%E7%8C%AA%E8%B9%84%E5%AD%90/%E4%BB%8E%E5%89%8D%E7%9A%84redux%E5%A4%9A%E4%B9%88%E5%8F%AF%E7%88%B1%E5%95%8A.jpg" alt="从前的你多么可爱啊"></p>]]>
    </summary>
    <title>redux 是个大猪蹄子</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Android" scheme="https://hellovass.github.io/tags/Android/"/>
    <content>
      <![CDATA[<h2 id="Vue-里实现一个列表"><a href="#Vue-里实现一个列表" class="headerlink" title="Vue 里实现一个列表"></a>Vue 里实现一个列表</h2><p>so easy，Vue 为我们提供了<code>v-for</code> 指令，可以绑定数组的数据来渲染一个项目列表：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app-4&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">ol</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">li</span> <span class="attr">v-for</span>=<span class="string">&quot;todo in todos&quot;</span>&gt;</span></span><br><span class="line">      &#123;&#123; todo.text &#125;&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">ol</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight js"><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">var</span> app4 = <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">  <span class="attr">el</span>: <span class="string">&#x27;#app-4&#x27;</span>,</span><br><span class="line">  <span class="attr">data</span>: &#123;</span><br><span class="line">    <span class="attr">todos</span>: [</span><br><span class="line">      &#123; <span class="attr">text</span>: <span class="string">&#x27;学习 JavaScript&#x27;</span> &#125;,</span><br><span class="line">      &#123; <span class="attr">text</span>: <span class="string">&#x27;学习 Vue&#x27;</span> &#125;,</span><br><span class="line">      &#123; <span class="attr">text</span>: <span class="string">&#x27;整个牛项目&#x27;</span> &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>数据和视图的绑定就完成了。</p><span id="more"></span><h3 id="加载更多功能"><a href="#加载更多功能" class="headerlink" title="加载更多功能"></a>加载更多功能</h3><p>只需要 <code>todos.push(items)</code>，你就会发现<strong>列表末尾</strong>添加了新的项目。</p><h3 id="下拉刷新功能"><a href="#下拉刷新功能" class="headerlink" title="下拉刷新功能"></a>下拉刷新功能</h3><p>只需要 <code>todos = items</code>，刷新功能就完成了</p><p>在 Vue 里更新 UI，似乎只需要<strong>对数据进行操作</strong>就可以了，UI 就会<strong>自动更新</strong>，这不就是传说中响应式编程嘛？</p><blockquote><p>NOTE: 问题来了，如果 Android 也想<strong>响应式</strong>，<strong>只对数据进行操作</strong>，不去触碰 <code>adapter.notifyItemXXX</code> 系列 <code>api</code> 就完成 RecyclerView 的更新吗？</p></blockquote><p>当然是可以的咯，兄 dei！</p><h2 id="Android-的-MVVM"><a href="#Android-的-MVVM" class="headerlink" title="Android 的 MVVM"></a>Android 的 MVVM</h2><blockquote><p>注意，我并不会用 databinding 来实现。即使用了，你也会发现，似乎实现起来有困难。</p></blockquote><h3 id="前提情要"><a href="#前提情要" class="headerlink" title="前提情要"></a>前提情要</h3><h4 id="DiffUtil-LiveData"><a href="#DiffUtil-LiveData" class="headerlink" title="DiffUtil&amp;LiveData"></a>DiffUtil&amp;LiveData</h4><h5 id="DiffUtil"><a href="#DiffUtil" class="headerlink" title="DiffUtil"></a>DiffUtil</h5><p><a href="https://developer.android.com/reference/android/support/v7/util/DiffUtil">DiffUtil</a>，为我们提供了计算<strong>差异值</strong>的能力：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/DiffUtil%E4%B8%8ELiveData/DiffUtil%E8%AE%A1%E7%AE%97%E5%8E%9F%E7%90%86.png" alt="DiffUtil 计算原理"></p><p>简单理解，<code>DiffUtil</code> 根据 <code>cur&amp;next</code> ，就能计算出<strong>差异值</strong>，也就是 <code>DiffUtil.DiffResult</code>。</p><h5 id="LiveData"><a href="#LiveData" class="headerlink" title="LiveData"></a>LiveData</h5><p><a href="https://developer.android.com/topic/libraries/architecture/livedata">LiveData</a>，我们可以先把它看成 <code>RxJava</code> 里的 <code>PublishSubject</code>，只不过 <code>LiveData</code> 具有生命周期感知能力。</p><h4 id="MVVM-的划分"><a href="#MVVM-的划分" class="headerlink" title="MVVM 的划分"></a>MVVM 的划分</h4><p>这个模式我就不介绍了，也属于懒得介绍系列。这里讲下，MVVM 具体对应 Android 里的哪些东西。</p><ul><li>V&#x3D;&gt; Activity&#x2F;Fragment，对标视图层，没啥可讲的</li><li>M&#x3D;&gt;XXXRepo，对标数据层，就是获取<strong>内存数据&#x2F;网络数据&#x2F;本地数据</strong>的数据仓库</li><li>VM&#x3D;&gt;XXXVM，这个是 MVVM 的核心，链接着 V 和 M。但是和 MVP 不同，是 V 持有 VM，而不是 VM 持有 V，V 订阅 VM 里的数据（LiveData 包裹的对象），当 <code>LiveData</code> 调用 <code>postValue/setValue</code>，V 就会收到数据变化通知</li></ul><p>如图：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/DiffUtil%E4%B8%8ELiveData/Android%E9%87%8C%E7%9A%84MVVM.png" alt="Android 里的 MVVM"></p><h3 id="开始实现了"><a href="#开始实现了" class="headerlink" title="开始实现了"></a>开始实现了</h3><h4 id="VM-层做的事情"><a href="#VM-层做的事情" class="headerlink" title="VM 层做的事情"></a>VM 层做的事情</h4><h5 id="数据域"><a href="#数据域" class="headerlink" title="数据域"></a>数据域</h5><p>对标 Vue 里的 data ，不过，我们这里需要把真正的数据用 LiveData 包裹起来，像这样：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 列表数据项</span></span><br><span class="line">   <span class="keyword">private</span> <span class="keyword">val</span> _uiStateModel = MutableLiveData&lt;UIStateModel&gt;()</span><br><span class="line"></span><br><span class="line">   <span class="keyword">private</span> <span class="keyword">val</span> _refreshStateModel = MutableLiveData&lt;RefreshStateModel&gt;()</span><br><span class="line"></span><br><span class="line">   <span class="keyword">private</span> <span class="keyword">val</span> _footerStateModel = MutableLiveData&lt;FooterStateModel&gt;()</span><br></pre></td></tr></table></figure><h5 id="方法域"><a href="#方法域" class="headerlink" title="方法域"></a>方法域</h5><p>对标 Vue 里的 methods，定义一些业务方法，这里只有一个 <code>loadData</code> 方法，之后会详解。</p><h5 id="计算属性"><a href="#计算属性" class="headerlink" title="计算属性"></a>计算属性</h5><p>对标 Vue 里的 computed ，这里，我们将<strong>数据域</strong>暴露出去，方便 V 层的订阅&#x2F; 绑定：</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="function"><span class="keyword">fun</span> <span class="title">getUIStateModel</span><span class="params">()</span></span> = _uiStateModel</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">getRefreshStateModel</span><span class="params">()</span></span> = _refreshStateModel</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">getFooterStateModel</span><span class="params">()</span></span> = _footerStateModel</span><br></pre></td></tr></table></figure><h4 id="V-层做的事情"><a href="#V-层做的事情" class="headerlink" title="V 层做的事情"></a>V 层做的事情</h4><h5 id="step1-创建并获取-VM-的实例"><a href="#step1-创建并获取-VM-的实例" class="headerlink" title="step1. 创建并获取 VM 的实例"></a>step1. 创建并获取 VM 的实例</h5><figure class="highlight kotlin"><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"><span class="comment">// create VM</span></span><br><span class="line">vm = obtainVM(JuVM::<span class="keyword">class</span>.java)</span><br></pre></td></tr></table></figure><h5 id="step2-初始化组件，并设置需要的回调"><a href="#step2-初始化组件，并设置需要的回调" class="headerlink" title="step2. 初始化组件，并设置需要的回调"></a>step2. 初始化组件，并设置需要的回调</h5><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// init Recyclerview</span></span><br><span class="line">initRecyclerViewComponent(rcvList)</span><br><span class="line"></span><br><span class="line"><span class="comment">// init refresh component</span></span><br><span class="line">initRefreshComponent(refreshLayout)</span><br><span class="line"></span><br><span class="line"><span class="comment">// init loadMore component</span></span><br><span class="line">initLoadMoreComponent(refreshLayout, rcvList)</span><br></pre></td></tr></table></figure><h5 id="step3-订阅-观察-VM-里的计算属性"><a href="#step3-订阅-观察-VM-里的计算属性" class="headerlink" title="step3. 订阅&#x2F;观察 VM 里的计算属性"></a>step3. 订阅&#x2F;观察 VM 里的计算属性</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">observeVMState</span><span class="params">(vm: <span class="type">JuVM</span>?)</span></span> &#123;</span><br><span class="line">    <span class="comment">// 订阅列表数据项</span></span><br><span class="line">    vm?.let &#123; juVM -&gt;</span><br><span class="line">        juVM.getUIStateModel().observe(<span class="keyword">this</span>, Observer &#123; uiStateModel -&gt;</span><br><span class="line">            uiStateModel?.let &#123; it -&gt;</span><br><span class="line">                viewAdapter!!.setItems(it.latest)</span><br><span class="line">                it.diffResult!!.dispatchUpdatesTo(viewAdapter!!)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="step4-在合适的生命周期方法中，调用-VM-里的方法"><a href="#step4-在合适的生命周期方法中，调用-VM-里的方法" class="headerlink" title="step4. 在合适的生命周期方法中，调用 VM 里的方法"></a>step4. 在合适的生命周期方法中，调用 VM 里的方法</h4><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onCreate</span><span class="params">(savedInstanceState: <span class="type">Bundle</span>?)</span></span> &#123;</span><br><span class="line">    <span class="keyword">super</span>.onCreate(savedInstanceState)</span><br><span class="line">    setContentView(R.layout.activity_main)</span><br><span class="line">    <span class="comment">// 省略...</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 加载第一页</span></span><br><span class="line">    vm?.loadData(<span class="literal">false</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="恭喜恭喜，基本完工"><a href="#恭喜恭喜，基本完工" class="headerlink" title="恭喜恭喜，基本完工"></a>恭喜恭喜，基本完工</h4><p>到这一步，我们算完成了一个页面的基本功能了。</p><h3 id="数据和视图的绑定是如何实现的"><a href="#数据和视图的绑定是如何实现的" class="headerlink" title="数据和视图的绑定是如何实现的"></a>数据和视图的绑定是如何实现的</h3><p>首先得聊聊<strong>旧石器时代</strong>的事，那时候的我们是这么实现<strong>加载更多</strong>和<strong>下拉刷新</strong>的：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 下拉刷新</span></span><br><span class="line"><span class="meta">@Override</span> </span><br><span class="line"><span class="keyword">protected</span> void onSucceed(List&lt;Post&gt; postList) &#123;</span><br><span class="line">       mPostList.clear();</span><br><span class="line">       mPostList.addAll(postList);</span><br><span class="line">       mAdapter.notifyDataSetChanged();</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="meta">@Override</span> </span><br><span class="line"><span class="keyword">protected</span> void onSucceed(List&lt;Post&gt; postList) &#123;</span><br><span class="line">       mPostList.addAll(postList);</span><br><span class="line">       mAdapter.notifyItemRangeInserted(mPostList.size()-postList.size(),postList.size());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>有什么问题呢？首先，刷新的代码太暴力了，直接 <code>notifyDataSetChanged</code> 导致所有 item 重新绑定一遍。接着，是加载更多，调用了 <code>notifyItemRangeInserted</code>，需要传入<strong>起始位置</strong>以及<strong>新增项目的个数</strong>，如果参数错误，凉凉╮(╯▽╰)╭</p><p>这简直没法和 web 世界的 mvvm 框架比嘛，太 low 了有木有！</p><h4 id="新石器时代"><a href="#新石器时代" class="headerlink" title="新石器时代"></a>新石器时代</h4><p>我想，屏蔽 <code>adapter.notifyXXX</code> 一系列 <code>api</code>，只通过<strong>对数据项的操作</strong>就实现 RecyclerView 的更新。</p><p>是时候贴出 <code>loadData</code> 的代码了：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 加载数据</span></span><br><span class="line">repo.getJus(pageNum)</span><br><span class="line">        .map &#123; it -&gt;</span><br><span class="line">            <span class="comment">// 1.当前数据</span></span><br><span class="line">            <span class="keyword">val</span> cur = ArrayList(<span class="keyword">this</span>.juList)</span><br><span class="line">            <span class="comment">// 2.处理数据，增、删、改</span></span><br><span class="line">            actionHandler.handle(<span class="keyword">this</span>.juList, it)</span><br><span class="line">            <span class="comment">// 3.处理后的数据</span></span><br><span class="line">            <span class="keyword">val</span> next = ArrayList(<span class="keyword">this</span>.juList)</span><br><span class="line">            <span class="comment">// 4.计算差异值</span></span><br><span class="line">            UIStateModel.success(cur, next, it.count())</span><br><span class="line">        &#125;</span><br><span class="line">        .subscribeOn(Schedulers.io())</span><br><span class="line">        .observeOn(AndroidSchedulers.mainThread())</span><br><span class="line">        .subscribe(&#123; it -&gt;</span><br><span class="line">            <span class="comment">// 更新状态</span></span><br><span class="line">            actionHandler.succeed(it.hasMore())</span><br><span class="line">            <span class="comment">// 发布差异值</span></span><br><span class="line">            _uiStateModel.value = it</span><br><span class="line">            <span class="comment">// 页数加 1</span></span><br><span class="line">            pageNum++</span><br><span class="line">        &#125;, &#123; _ -&gt;</span><br><span class="line">            actionHandler.failed()</span><br><span class="line">        &#125;)</span><br></pre></td></tr></table></figure><p>正如同文章开头说的，通过 <code>Google</code> 提供的 <code>DiffUtil</code> ，我们将<strong>对数据的操作</strong>封装为 DiffUtil.DiffResult，而且是在工作线程做这些事，接着切换线程，在主线程发布 <code>DiffUtil.DiffResult</code>。最后，V 层就会收到<strong>订阅通知</strong>，至此，<strong>列表数据项</strong>与 <code>RecyclerView</code> 实现了绑定。</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></pre></td><td class="code"><pre><span class="line">vm?.let &#123; juVM -&gt;</span><br><span class="line">            juVM.getUIStateModel().observe(<span class="keyword">this</span>, Observer &#123; uiStateModel -&gt;</span><br><span class="line">                uiStateModel?.let &#123; it -&gt;</span><br><span class="line">                    viewAdapter!!.setItems(it.latest)</span><br><span class="line">                    it.diffResult!!.dispatchUpdatesTo(viewAdapter!!)</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><h3 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h3><p>我们实现了<strong>加载更多</strong>和<strong>下拉刷新</strong>功能，真实项目中，我们可能还会涉及到 RecyclerView 修改 item。当然，理解了上面论述的原理之后，实现这样的需求也不是太难。这篇文章把<strong>关键代码</strong>都列出来了，部分细节不明白的话，可以参考 github 上的<a href="https://github.com/HelloVass/LiteMVVM">完整实现</a></p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li><a href="https://stackoverflow.com/questions/47458429/how-to-visually-stay-on-same-scroll-position-upon-unknown-number-of-recyclervie/47522246#47522246">how-to-visually-stay-on-same-scroll-position-upon-unknown-number-of-recyclervie</a></li><li><a href="https://cn.vuejs.org/v2/guide/list.html">Vue 列表渲染</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2018/09/10/DiffUtil+LiveData%E5%AE%9E%E7%8E%B0%E4%B8%8B%E6%8B%89%E5%88%B7%E6%96%B0%E5%8F%8A%E5%8A%A0%E8%BD%BD%E6%9B%B4%E5%A4%9A/</id>
    <link href="https://hellovass.github.io/2018/09/10/DiffUtil+LiveData%E5%AE%9E%E7%8E%B0%E4%B8%8B%E6%8B%89%E5%88%B7%E6%96%B0%E5%8F%8A%E5%8A%A0%E8%BD%BD%E6%9B%B4%E5%A4%9A/"/>
    <published>2018-09-10T12:10:19.000Z</published>
    <summary>
      <![CDATA[<h2 id="Vue-里实现一个列表"><a href="#Vue-里实现一个列表" class="headerlink" title="Vue 里实现一个列表"></a>Vue 里实现一个列表</h2><p>so easy，Vue 为我们提供了<code>v-for</code> 指令，可以绑定数组的数据来渲染一个项目列表：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app-4&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">ol</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">li</span> <span class="attr">v-for</span>=<span class="string">&quot;todo in todos&quot;</span>&gt;</span></span><br><span class="line">      &#123;&#123; todo.text &#125;&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">ol</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<figure class="highlight js"><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">var</span> app4 = <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">  <span class="attr">el</span>: <span class="string">&#x27;#app-4&#x27;</span>,</span><br><span class="line">  <span class="attr">data</span>: &#123;</span><br><span class="line">    <span class="attr">todos</span>: [</span><br><span class="line">      &#123; <span class="attr">text</span>: <span class="string">&#x27;学习 JavaScript&#x27;</span> &#125;,</span><br><span class="line">      &#123; <span class="attr">text</span>: <span class="string">&#x27;学习 Vue&#x27;</span> &#125;,</span><br><span class="line">      &#123; <span class="attr">text</span>: <span class="string">&#x27;整个牛项目&#x27;</span> &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>数据和视图的绑定就完成了。</p>]]>
    </summary>
    <title>DiffUtil、LiveData 的应用</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="前端" scheme="https://hellovass.github.io/tags/%E5%89%8D%E7%AB%AF/"/>
    <content>
      <![CDATA[<h2 id="需求描述"><a href="#需求描述" class="headerlink" title="需求描述"></a>需求描述</h2><p>需要将<strong>用户信息的 UI</strong>（下文用 <code>UserInfo</code> 来代替） 写成一个 <code>Vue</code> 组件，达到<strong>重用</strong>的目的。</p><span id="more"></span><h2 id="UserInfo-组成"><a href="#UserInfo-组成" class="headerlink" title="UserInfo 组成"></a>UserInfo 组成</h2><ul><li>用户信息的模板</li><li>获取用户信息的逻辑</li><li>样式</li></ul><h2 id="大概长这样"><a href="#大概长这样" class="headerlink" title="大概长这样"></a>大概长这样</h2><p>请先忽略<strong>丑陋的UI</strong>，显示的元素主要就俩：</p><ul><li><strong>头像</strong></li><li><strong>昵称</strong>。</li></ul><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Vue%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86/UserInfo%E7%BB%84%E4%BB%B6.png" alt="UserInfo 组件"></p><h2 id="服务端返回的数据格式"><a href="#服务端返回的数据格式" class="headerlink" title="服务端返回的数据格式"></a>服务端返回的数据格式</h2><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span><span class="string">&quot;2&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span><span class="string">&quot;user&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;attributes&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;nick_name&quot;</span><span class="punctuation">:</span><span class="string">&quot;HelloVass&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;avatar&quot;</span><span class="punctuation">:</span><span class="string">&quot;https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83epZOhVL6QcUqJjEo7mqSpiamWRAaX1lB9dV79PzfOA5CMzBlBmCUfKibb2JyMQ0Rubic9OLMzjBRS9Gw/132&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;score&quot;</span><span class="punctuation">:</span><span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;gender&quot;</span><span class="punctuation">:</span><span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;city&quot;</span><span class="punctuation">:</span><span class="string">&quot;Hangzhou&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;province&quot;</span><span class="punctuation">:</span><span class="string">&quot;Zhejiang&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;country&quot;</span><span class="punctuation">:</span><span class="string">&quot;China&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="UserInfo-的代码非常简单"><a href="#UserInfo-的代码非常简单" class="headerlink" title="UserInfo 的代码非常简单"></a>UserInfo 的代码非常简单</h2><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line"></span><br><span class="line">  &lt;div class=&quot;user-container&quot;&gt;</span><br><span class="line">    &lt;!--背景--&gt;</span><br><span class="line">    &lt;image</span><br><span class="line">      class=&quot;user-bg&quot;</span><br><span class="line">      mode=&quot;aspectFill&quot;</span><br><span class="line">      :src=&quot;userInfo.avatar&quot;</span><br><span class="line">    &gt;</span><br><span class="line">    &lt;/image&gt;</span><br><span class="line">     &lt;!--用户信息--&gt;</span><br><span class="line">    &lt;div class=&quot;user-info&quot;&gt;</span><br><span class="line">      &lt;!--头像--&gt;</span><br><span class="line">      &lt;image</span><br><span class="line">        class=&quot;user-avatar&quot;</span><br><span class="line">        :src=&quot;userInfo.avatar&quot;</span><br><span class="line">      &gt;&lt;/image&gt;</span><br><span class="line">      &lt;!--昵称--&gt;</span><br><span class="line">      &lt;div</span><br><span class="line">        class=&quot;user-nickname&quot;</span><br><span class="line">      &gt;&#123;&#123;userInfo.nick_name&#125;&#125;</span><br><span class="line">      &lt;/div&gt;</span><br><span class="line">    &lt;/div&gt;</span><br><span class="line">  &lt;/div&gt;</span><br><span class="line">&lt;/template&gt;</span><br><span class="line"></span><br><span class="line">&lt;script&gt;</span><br><span class="line">  // 用户Api</span><br><span class="line">  import UsersApi from &quot;@/network/users-api&quot;;</span><br><span class="line"></span><br><span class="line">  export default &#123;</span><br><span class="line">      </span><br><span class="line">    name: &quot;UserHeader&quot;,</span><br><span class="line"></span><br><span class="line">    data() &#123;</span><br><span class="line">      return &#123;</span><br><span class="line">        result: Object</span><br><span class="line">      &#125;;</span><br><span class="line">    &#125;,</span><br><span class="line"></span><br><span class="line">    computed: &#123;</span><br><span class="line">      // 用户是否登录</span><br><span class="line">      isLogin() &#123;</span><br><span class="line">        return this.$store.getters.isLogin;</span><br><span class="line">      &#125;,</span><br><span class="line">  // 用户信息</span><br><span class="line">      userInfo() &#123;</span><br><span class="line">        return this.result.data.attributes;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line"></span><br><span class="line">    created() &#123;</span><br><span class="line">        </span><br><span class="line">      // 未登录，return</span><br><span class="line">      if (!this.isLogin) &#123;</span><br><span class="line">        return;</span><br><span class="line">      &#125;</span><br><span class="line">        </span><br><span class="line">      // 已经登录，直接从服务器获取用户数据</span><br><span class="line">      UsersApi.getUserInfo()</span><br><span class="line">        .then(res =&gt; &#123;</span><br><span class="line">          this.result = res;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">&lt;/script&gt;</span><br><span class="line"></span><br><span class="line">// 省略样式</span><br><span class="line">&lt;style scoped lang=&quot;scss&quot;&gt;</span><br><span class="line">&lt;/style&gt;</span><br></pre></td></tr></table></figure><h2 id="看起来似乎没什么问题，实验一下"><a href="#看起来似乎没什么问题，实验一下" class="headerlink" title="看起来似乎没什么问题，实验一下"></a>看起来似乎没什么问题，实验一下</h2><p>哦豁，凉凉</p><figure class="highlight js"><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 class="title class_">VM116</span>:<span class="number">1</span> thirdScriptError</span><br><span class="line"><span class="title class_">Cannot</span> read property <span class="string">&#x27;attributes&#x27;</span> <span class="keyword">of</span> <span class="literal">undefined</span>;at pages/users/main page lifeCycleMethod onReady <span class="keyword">function</span></span><br><span class="line"><span class="title class_">TypeError</span>: <span class="title class_">Cannot</span> read property <span class="string">&#x27;attributes&#x27;</span> <span class="keyword">of</span> <span class="literal">undefined</span></span><br><span class="line">    at <span class="title class_">VueComponent</span>.<span class="property">userInfo</span> (<span class="attr">http</span>:<span class="comment">//127.0.0.1:46848/appservice/static/js/pages/users/main.js:367:30)</span></span><br><span class="line">    at <span class="title class_">Watcher</span>.<span class="property">get</span> (<span class="attr">http</span>:<span class="comment">//127.0.0.1:46848/appservice/static/js/vendor.js:2635:25)</span></span><br><span class="line">    at <span class="title class_">Watcher</span>.<span class="property">evaluate</span> (<span class="attr">http</span>:<span class="comment">//127.0.0.1:46848/appservice/static/js/vendor.js:2742:21)</span></span><br><span class="line">    at <span class="title class_">VueComponent</span>.<span class="property">computedGetter</span> [<span class="keyword">as</span> userInfo] (<span class="attr">http</span>:<span class="comment">//127.0.0.1:46848/appservice/static/js/vendor.js:2971:17)</span></span><br><span class="line">    at <span class="title class_">VueComponent</span>.<span class="property">render</span> (<span class="attr">http</span>:<span class="comment">//127.0.0.1:46848/appservice/static/js/pages/users/main.js:399:17)</span></span><br><span class="line">    at <span class="title class_">VueComponent</span>.<span class="property">Vue</span>.<span class="property">_render</span> (<span class="attr">http</span>:<span class="comment">//127.0.0.1:46848/appservice/static/js/vendor.js:3785:22)</span></span><br><span class="line">    at <span class="title class_">VueComponent</span>.<span class="property">updateComponent</span> (<span class="attr">http</span>:<span class="comment">//127.0.0.1:46848/appservice/static/js/vendor.js:2305:21)</span></span><br><span class="line">    at <span class="title class_">Watcher</span>.<span class="property">get</span> (<span class="attr">http</span>:<span class="comment">//127.0.0.1:46848/appservice/static/js/vendor.js:2635:25)</span></span><br><span class="line">    at <span class="keyword">new</span> <span class="title class_">Watcher</span> (<span class="attr">http</span>:<span class="comment">//127.0.0.1:46848/appservice/static/js/vendor.js:2624:12)</span></span><br><span class="line">    at <span class="title function_">mountComponent</span> (<span class="attr">http</span>:<span class="comment">//127.0.0.1:46848/appservice/static/js/vendor.js:2309:17)</span></span><br></pre></td></tr></table></figure><p>console 里直接报错了，而 <code>UserInfo</code> 也没有正常渲染出来，why？</p><h2 id="冷静分析"><a href="#冷静分析" class="headerlink" title="冷静分析"></a>冷静分析</h2><figure class="highlight js"><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="title function_">data</span>(<span class="params"></span>) &#123;</span><br><span class="line">     <span class="keyword">return</span> &#123;</span><br><span class="line">       <span class="attr">result</span>: <span class="title class_">Object</span></span><br><span class="line">     &#125;;</span><br><span class="line">   &#125;,</span><br></pre></td></tr></table></figure><p>因为服务端返回的数据遵循标准 JSONApi 格式(有时候嵌套层级会比较深)，而我这里想偷懒，就定义了一个 result，并没有定义 result 里的具体字段，并给他们赋值。</p><h3 id="按照我的思路"><a href="#按照我的思路" class="headerlink" title="按照我的思路"></a>按照我的思路</h3><p>目前只需要 nick_name 和 avatar 两个字段的值，而这两个字段嵌套的比较深，我不希望在 template 里写这样的绑定代码：</p><figure class="highlight plaintext"><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">&lt;template&gt;</span><br><span class="line"></span><br><span class="line">  &lt;div class=&quot;user-container&quot;&gt;</span><br><span class="line">    &lt;!--背景--&gt;</span><br><span class="line">    &lt;image</span><br><span class="line">      class=&quot;user-bg&quot;</span><br><span class="line">      mode=&quot;aspectFill&quot;</span><br><span class="line">      :src=&quot;result.data.attributes.avatar&quot;</span><br><span class="line">    &gt;</span><br><span class="line">    &lt;/image&gt;</span><br><span class="line">     &lt;!--用户信息--&gt;</span><br><span class="line">    &lt;div class=&quot;user-info&quot;&gt;</span><br><span class="line">      &lt;!--头像--&gt;</span><br><span class="line">      &lt;image</span><br><span class="line">        class=&quot;user-avatar&quot;</span><br><span class="line">        :src=&quot;result.data.attributes.avatar&quot;</span><br><span class="line">      &gt;&lt;/image&gt;</span><br><span class="line">      &lt;!--昵称--&gt;</span><br><span class="line">      &lt;div</span><br><span class="line">        class=&quot;user-nickname&quot;</span><br><span class="line">      &gt;&#123;&#123;result.data.attributes.nick_name&#125;&#125;</span><br><span class="line">      &lt;/div&gt;</span><br><span class="line">    &lt;/div&gt;</span><br><span class="line">  &lt;/div&gt;</span><br><span class="line">&lt;/template&gt;</span><br></pre></td></tr></table></figure><p>太丑陋了！！！</p><p>于是，我在计算属性中定义了一个 <code>userInfo()</code> 方法，将 <code>result.data.attributes</code> 作为它的返回值，当 <code>getUserInfo</code> 方法获取到服务器上的数据后，进行一个<code>this.result = res</code> 操作，这样，计算属性 <code>userInfo</code> 依赖的 <code>result</code> 更新了，<code>userInfo</code> 也会更新，也就完成了UI的渲染。这一切是多么美好啊！</p><h3 id="但是为什么没有按照我的剧本演呢？"><a href="#但是为什么没有按照我的剧本演呢？" class="headerlink" title="但是为什么没有按照我的剧本演呢？"></a>但是为什么没有按照我的剧本演呢？</h3><p>这就涉及我的知识盲区了，Vue 是如何追踪数据变化，实现响应式编程的？</p><p>遇事不顺找 Google，这里我找到三篇比较有参考价值的文章：</p><ul><li><a href="https://github.com/Corbusier/Awesome-Vue/issues/8">Vue 进阶 ——- 深入响应式原理</a></li><li><a href="https://cn.vuejs.org/v2/guide/list.html#%E5%AF%B9%E8%B1%A1%E6%9B%B4%E6%94%B9%E6%A3%80%E6%B5%8B%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9">对象更改检测注意事项</a></li><li><a href="https://segmentfault.com/q/1010000009396332">vue2.0 初始化请求 JSON 多层嵌套问题</a></li></ul><p>第一篇文章提到了变化检测的问题，</p><blockquote><p>受限于JS及废弃的<code>Object.observe</code>，Vue不能检测到对象属性的添加或删除。由于Vue会在初始化实例时对属性执行<code>getter/setter</code>转化的过程，所以属性必须在<code>data</code>对象上保存才能被转换，如此，才可以让它是响应的。例如：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">        <span class="attr">data</span>:&#123;</span><br><span class="line">            <span class="attr">a</span>:<span class="number">1</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="comment">/* &lt; !-- vm.a 是响应的 --&gt; */</span></span><br><span class="line">    </span><br><span class="line">    vm.<span class="property">b</span> = <span class="number">2</span></span><br><span class="line">    <span class="comment">/* &lt; !-- vm.b 是非响应的 --&gt; */</span></span><br></pre></td></tr></table></figure><p>Vue不允许在<strong>已创建的实例</strong>上动态添加新的根级响应式属性。但是可以使用<code>Vue.set(object,key,value)</code>方法将响应属性添加到嵌套的对象上：</p><figure class="highlight js"><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"><span class="comment">/*&lt; !-- 一定要在实例化之前添加！ -- &gt; */</span></span><br><span class="line">    <span class="title class_">Vue</span>.<span class="title function_">set</span>(vm.<span class="property">someObject</span>, <span class="string">&#x27;b&#x27;</span>, <span class="number">2</span>)</span><br></pre></td></tr></table></figure></blockquote><p>第二篇，也就是vue官方的说明：</p><blockquote><p>还是由于 JavaScript 的限制，<strong>Vue 不能检测对象属性的添加或删除</strong>：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> vm = <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">  <span class="attr">data</span>: &#123;</span><br><span class="line">    <span class="attr">a</span>: <span class="number">1</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// `vm.a` 现在是响应式的</span></span><br><span class="line"></span><br><span class="line">vm.<span class="property">b</span> = <span class="number">2</span></span><br><span class="line"><span class="comment">// `vm.b` 不是响应式的</span></span><br></pre></td></tr></table></figure><p>对于<strong>已经创建的实例</strong>，Vue 不能动态添加根级别的响应式属性。但是，可以使用 <code>Vue.set(object, key, value)</code> 方法向嵌套对象添加响应式属性。例如，对于：</p><figure class="highlight js"><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"><span class="keyword">var</span> vm = <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">  <span class="attr">data</span>: &#123;</span><br><span class="line">    <span class="attr">userProfile</span>: &#123;</span><br><span class="line">      <span class="attr">name</span>: <span class="string">&#x27;Anika&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>你可以添加一个新的 <code>age</code> 属性到嵌套的 <code>userProfile</code> 对象：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Vue</span>.<span class="title function_">set</span>(vm.<span class="property">userProfile</span>, <span class="string">&#x27;age&#x27;</span>, <span class="number">27</span>)</span><br></pre></td></tr></table></figure></blockquote><p>最后捋一捋思路，为什么会发生错误呢？</p><p>当页面中的 image、div 渲染是，userInfo 数据肯定还没获取到，但是这时候 userInfo() 方法里 result.data.attrbutes 的 result.data 还没有定义，所以就会报错 <code>Cannot read property &#39;attributes&#39; </code>。</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>别偷懒，按照后端返回的 JSON 的格式初始化 data 里的字段，如下：</p><figure class="highlight js"><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="title function_">data</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">result</span>: &#123;</span><br><span class="line">          <span class="attr">data</span>: &#123;</span><br><span class="line">            <span class="attr">id</span>: <span class="title class_">Number</span>,</span><br><span class="line">            <span class="attr">type</span>: <span class="title class_">String</span>,</span><br><span class="line">            <span class="attr">attributes</span>: <span class="title class_">Object</span></span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://hellovass.github.io/2018/07/29/%E5%85%B3%E4%BA%8E%20Vue%20%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86%E7%9A%84%E5%9B%B0%E6%83%91/</id>
    <link href="https://hellovass.github.io/2018/07/29/%E5%85%B3%E4%BA%8E%20Vue%20%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86%E7%9A%84%E5%9B%B0%E6%83%91/"/>
    <published>2018-07-29T06:11:41.000Z</published>
    <summary>
      <![CDATA[<h2 id="需求描述"><a href="#需求描述" class="headerlink" title="需求描述"></a>需求描述</h2><p>需要将<strong>用户信息的 UI</strong>（下文用 <code>UserInfo</code> 来代替） 写成一个 <code>Vue</code> 组件，达到<strong>重用</strong>的目的。</p>]]>
    </summary>
    <title>关于 Vue 响应式原理的困惑</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Android" scheme="https://hellovass.github.io/tags/Android/"/>
    <content>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><p>本文描述了如何实现该需求的思路，代码可能<strong>不通用</strong>，但是该思路应该可以解决很多类似的需求…</p><span id="more"></span><h2 id="需要分享的内容"><a href="#需要分享的内容" class="headerlink" title="需要分享的内容"></a>需要分享的内容</h2><ul><li>上半部分，1-4张图片</li><li>下半部分，包含很多细小的东西，签名、用户名、用户头像、二维码图片…</li><li>​…</li></ul><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%8A%A8%E6%80%81%E7%94%9F%E6%88%90%E5%88%86%E4%BA%AB%E5%9B%BE%E7%89%87/%E5%88%86%E4%BA%AB%E5%9B%BE%E7%89%87%E6%95%88%E6%9E%9C1.jpg" alt="dynamic_shareview_post_image_1"></p><h2 id="实现原理"><a href="#实现原理" class="headerlink" title="实现原理"></a>实现原理</h2><p>因为我的需要还是比较复杂的（主要来自于上半部分的排列规则），所以直接 extends 了 ViewGroup 来 layout 这些子 View，然后用数据填充这些布局，然后创建分享 Bitmap。</p><h3 id="对-ViewGroup-里的元素做一个抽象"><a href="#对-ViewGroup-里的元素做一个抽象" class="headerlink" title="对 ViewGroup 里的元素做一个抽象"></a>对 ViewGroup 里的元素做一个抽象</h3><p>对自定义View（我们就叫它 DynamicShareView 好了），它关心的是如何拿到<strong>上半部分的 View</strong> 和<strong>下半部分的 View</strong>，所以这里定义一个 Adapter 接口：</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="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Adapter</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 获取封面个数</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="type">int</span> <span class="title function_">getCoverCount</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 获取封面 View</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  View <span class="title function_">getImage</span><span class="params">(ViewGroup viewGroup, <span class="type">int</span> position)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 获取底部 View</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  View <span class="title function_">getBottom</span><span class="params">(ViewGroup viewGroup)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对调用者来说，我们只需要调用 <code>DynamicShareView</code> 实例的 <code>setAdapter</code> 方法，然后将一个实现了 <code>Adapter 接口的对象</code>传给它，接着 <code>DynamicShareView</code> 会将这些 <code>View</code> 挨个添加到进来，如下：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAdapter</span><span class="params">(<span class="meta">@NonNull</span> Adapter adapter)</span> &#123;</span><br><span class="line"></span><br><span class="line">   mAdapter = adapter;</span><br><span class="line"></span><br><span class="line">   removeAllViews(); <span class="comment">// 移除所有子View</span></span><br><span class="line"></span><br><span class="line">   <span class="comment">// 添加图片</span></span><br><span class="line">   <span class="type">int</span> <span class="variable">imageCount</span> <span class="operator">=</span> Math.min(mAdapter.getCoverCount(), MAX_COUNT);</span><br><span class="line">   <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> <span class="number">0</span>; index &lt; imageCount; index++) &#123;</span><br><span class="line"></span><br><span class="line">     <span class="type">View</span> <span class="variable">image</span> <span class="operator">=</span> mAdapter.getImage(<span class="built_in">this</span>, index);</span><br><span class="line">     addView(image);</span><br><span class="line">     mImages.add(image);</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="type">View</span> <span class="variable">bottomLayout</span> <span class="operator">=</span> mAdapter.getBottom(<span class="built_in">this</span>);</span><br><span class="line">   mBottomLayout = bottomLayout;</span><br><span class="line">   addView(bottomLayout, <span class="keyword">new</span> <span class="title class_">LayoutParams</span>(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>这步完成之后，才是重头戏，我们需要自己来 <code>measure</code> 内部的子 <code>View</code>，计算出 <code>DynamicShareView</code> 的宽高。</p><h3 id="测量高度-布局"><a href="#测量高度-布局" class="headerlink" title="测量高度&amp;布局"></a>测量高度&amp;布局</h3><p>这里的实现，emmmmmmmmm，复杂度和产品的需求成正比。栗如，我这里的复杂度主要来自上半部分的排列规则：</p><p>1.一张封面时，封面的宽高等于屏幕的宽度</p><p>2.两张封面时，封面的宽度等于屏幕的宽度，高度为屏幕宽度的一半，上下排列</p><p>3.三张封面时，封面的高度等于屏幕宽度的一半，前两张图片占一行，均分；第三张图片独占一行</p><p>4.&#x2F;&#x2F; 省略…</p><p>怎么破，这里，我定义了一个 Rule 接口：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Rule</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">measureChildren</span><span class="params">(<span class="type">int</span> screenWidth, <span class="type">int</span> screenHeight, <span class="type">int</span> spacing, View... children)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">layoutChildren</span><span class="params">(<span class="type">int</span> screenWidth, <span class="type">int</span> screenHeight, <span class="type">int</span> spacing, View... children)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后定义5个子类来<strong>测量</strong>和<strong>布局</strong>，如下图：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%8A%A8%E6%80%81%E7%94%9F%E6%88%90%E5%88%86%E4%BA%AB%E5%9B%BE%E7%89%87/%E5%88%86%E4%BA%AB%E5%9B%BE%E7%89%87%E6%95%88%E6%9E%9C2.jpg" alt="策略模式实现测量和布局"></p><p>Rule1Impl 对应只有 1 个封面的情况，Rule2Impl 对应有 2 个封面的情况，以此类推。BottomRule 负责下半部分子 <code>View</code> 的<strong>测量和布局</strong>。当然，如果你喜欢，也可以写成一坨代码来 <code>work</code>。我们先来看下，<code>onMeasure</code> 方法长咋样吧。</p><h4 id="onMeasure-步骤"><a href="#onMeasure-步骤" class="headerlink" title="onMeasure 步骤"></a>onMeasure 步骤</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><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span> <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onMeasure</span><span class="params">(<span class="type">int</span> widthMeasureSpec, <span class="type">int</span> heightMeasureSpec)</span> &#123;</span><br><span class="line">   <span class="built_in">super</span>.onMeasure(widthMeasureSpec, heightMeasureSpec);</span><br><span class="line"></span><br><span class="line">   <span class="type">int</span> <span class="variable">widthSize</span> <span class="operator">=</span> MeasureSpec.getSize(widthMeasureSpec);</span><br><span class="line"></span><br><span class="line">   <span class="type">int</span> <span class="variable">heightSize</span> <span class="operator">=</span> MeasureSpec.getSize(heightMeasureSpec);</span><br><span class="line">   <span class="type">int</span> <span class="variable">heightMode</span> <span class="operator">=</span> MeasureSpec.getMode(heightMeasureSpec);</span><br><span class="line"></span><br><span class="line">   mActualWidth = widthSize; <span class="comment">// 宽度等于屏幕宽度</span></span><br><span class="line"></span><br><span class="line">   <span class="keyword">switch</span> (mImages.size()) &#123;</span><br><span class="line"></span><br><span class="line">     <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">       mRule1.measureChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(<span class="number">0</span>));</span><br><span class="line">       <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">     <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">       mRule2.measureChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(<span class="number">0</span>),</span><br><span class="line">           mImages.get(<span class="number">1</span>));</span><br><span class="line">       <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">     <span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line">       mRule3.measureChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(<span class="number">0</span>),</span><br><span class="line">           mImages.get(<span class="number">1</span>), mImages.get(<span class="number">2</span>));</span><br><span class="line">       <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">     <span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line">       mRule4.measureChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(<span class="number">0</span>),</span><br><span class="line">           mImages.get(<span class="number">1</span>), mImages.get(<span class="number">2</span>), mImages.get(<span class="number">3</span>));</span><br><span class="line">       <span class="keyword">break</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">   mBottomRule.measureChildren(mActualWidth, mActualHeight, mSpacing, mBottomLayout);</span><br><span class="line"></span><br><span class="line">   <span class="comment">// 获得实际高度</span></span><br><span class="line">   mActualHeight = mActualWidth + mBottomLayout.getMeasuredHeight();</span><br><span class="line"></span><br><span class="line">   <span class="comment">// 设置 ViewGroup 尺寸</span></span><br><span class="line">   setMeasuredDimension(mActualWidth,</span><br><span class="line">       heightMode == MeasureSpec.EXACTLY ? heightSize : mActualHeight);</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><h4 id="layout-步骤"><a href="#layout-步骤" class="headerlink" title="layout 步骤"></a>layout 步骤</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span> <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onLayout</span><span class="params">(<span class="type">boolean</span> changed, <span class="type">int</span> l, <span class="type">int</span> t, <span class="type">int</span> r, <span class="type">int</span> b)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (mImages == <span class="literal">null</span> || mImages.isEmpty()) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">switch</span> (mImages.size()) &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">        mRule1.layoutChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(<span class="number">0</span>));</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">        mRule2.layoutChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(<span class="number">0</span>),</span><br><span class="line">            mImages.get(<span class="number">1</span>));</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line">        mRule3.layoutChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(<span class="number">0</span>), mImages.get(<span class="number">1</span>),</span><br><span class="line">            mImages.get(<span class="number">2</span>));</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line">        mRule4.layoutChildren(mActualWidth, mActualHeight, mSpacing, mImages.get(<span class="number">0</span>), mImages.get(<span class="number">1</span>),</span><br><span class="line">            mImages.get(<span class="number">2</span>), mImages.get(<span class="number">3</span>));</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    mBottomRule.layoutChildren(mActualWidth, mActualHeight, mSpacing, mBottomLayout);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>看上去是不是很干净呢23333！</p><h3 id="静态-View-的布局-测量"><a href="#静态-View-的布局-测量" class="headerlink" title="静态 View 的布局&amp;测量"></a>静态 View 的布局&amp;测量</h3><p>这里和上半部分不同，不需要根据业务动态排列子 View，所以使用一个 xxxx.xml 来布局，如图：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%8A%A8%E6%80%81%E7%94%9F%E6%88%90%E5%88%86%E4%BA%AB%E5%9B%BE%E7%89%87/%E5%88%86%E4%BA%AB%E5%9B%BE%E7%89%87%E6%95%88%E6%9E%9C3.jpg" alt="下半部分View"></p><blockquote><p>NOTE:这里的根布局，没有写 <code>layout_height=match_parent</code>，因为下半部分的高度实际上应由内部的子 <code>View</code> 高度来决定！这也就是说，我们进行下半部分测量的时候，我们需要自己计算下半部分的高度到底是多少，这样，才能得到 <code>DynamicShareView</code> 的高度数值！那么问题来了，如何测量 <code>layout_heigh=wrap_content</code> 的 <code>View</code>的高度呢？看代码:</p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BottomRule</span> <span class="keyword">extends</span> <span class="title class_">AbsRule</span> &#123;</span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">BottomRule</span><span class="params">(Context context)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(context);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">measureChildren</span><span class="params">(<span class="type">int</span> screenWidth, <span class="type">int</span> screenHeight, <span class="type">int</span> spacing, View... children)</span> &#123;</span><br><span class="line">    measureManually2(children[<span class="number">0</span>], screenWidth);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">layoutChildren</span><span class="params">(<span class="type">int</span> screenWidth, <span class="type">int</span> screenHeight, <span class="type">int</span> spacing, View... children)</span> &#123;</span><br><span class="line">    children[<span class="number">0</span>].layout(<span class="number">0</span>, screenWidth, screenWidth, screenHeight);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbsRule</span> <span class="keyword">implements</span> <span class="title class_">Rule</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> Context mContext;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">AbsRule</span><span class="params">(<span class="meta">@NonNull</span> Context context)</span> &#123;</span><br><span class="line">    mContext = context;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">measureManually2</span><span class="params">(View target, <span class="type">int</span> widthSize)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="variable">widthMeasureSpec</span> <span class="operator">=</span> View.MeasureSpec.makeMeasureSpec(widthSize, View.MeasureSpec.EXACTLY);</span><br><span class="line">    <span class="type">int</span> <span class="variable">heightMeasureSpec</span> <span class="operator">=</span></span><br><span class="line">        View.MeasureSpec.makeMeasureSpec((<span class="number">1</span> &lt;&lt; <span class="number">30</span>) - <span class="number">1</span>, View.MeasureSpec.AT_MOST);</span><br><span class="line">    target.measure(widthMeasureSpec, heightMeasureSpec);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>看到 <code>measureManually2</code> 这个方法，我想机智的童鞋已经豁然开朗了。至于为什么可以用这种方式测量出 <code>layout_width=wrap_content</code> 的 <code>View</code> 的高度，答案在 《Android开发艺术探索》一书中的 <strong>自定义View</strong><br>一章。</p><h3 id="implements-Adapter-的坑"><a href="#implements-Adapter-的坑" class="headerlink" title="implements Adapter 的坑"></a>implements Adapter 的坑</h3><p>上面讲过只要实现 <code>Adapter</code> 这个接口就可以了，然而实际上，能否成功生成图片的关键也在这里，稍微不注意，就会陷入<strong>异步问题</strong>的深坑中。</p><h4 id="如何加载图片呢"><a href="#如何加载图片呢" class="headerlink" title="如何加载图片呢"></a>如何加载图片呢</h4><p>主流方案一般是用 <code>Picasso、Glide</code> 这样的图片加载库，这里，我使用的是 <code>Glide</code>。那直接 <code>Glide.with().load().into ...</code> 不就万事大吉了嘛！nonono，不是这样滴，因为 <code>Glide</code> 加载图片的过程是<strong>异步</strong>的，也就是说 <code>ImageView</code> 可能已经全部添加到 <code>DynamicShareView</code> 了，onMeasure、layout 的步骤也完成了，但图片还未从网络、磁盘加载完，<code>ImageView</code> 里的 <code>Bitmap</code> 是不存在的 。这时，我们再这么做：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">share</span><span class="params">(View target)</span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 测量 DynamicShareView 的大小</span></span><br><span class="line">   measureManually(target, ScreenUtil.getScreenWidth(mContext),</span><br><span class="line">       ScreenUtil.getScreenWidth(mContext) + DensityUtil.dip2px(mContext, <span class="number">175.0F</span>));</span><br><span class="line"><span class="comment">// 布局 DynamicShareView</span></span><br><span class="line">   target.layout(<span class="number">0</span>, <span class="number">0</span>, target.getMeasuredWidth(), target.getMeasuredHeight());</span><br><span class="line">   <span class="comment">//  生成 Bitmap</span></span><br><span class="line">   <span class="type">Bitmap</span> <span class="variable">bitmap</span> <span class="operator">=</span> generateBitmap(target, target.getMeasuredWidth(), target.getMeasuredHeight());</span><br><span class="line"></span><br><span class="line">   <span class="comment">//  将 Bitmap 保存为临时文件</span></span><br><span class="line">   <span class="keyword">try</span> &#123;</span><br><span class="line">     saveBitmap(bitmap);</span><br><span class="line">   &#125; <span class="keyword">catch</span> (FileNotFoundException e) &#123;</span><br><span class="line">     e.printStackTrace();</span><br><span class="line">   &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">     e.printStackTrace();</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   mContext.startActivity(createShareIntent());</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>生成的 <code>Bitmap</code> 很可能是没有图片的。因为这些图片都是需要 <code>Glide</code> 去<strong>远程图片服务器</strong>加载，解析后才能得到的。而我们并不知道加载这些图片需要多久，甚至都没有等待这些加载工作完成，就直接填充数据到 <code>DynamicShareView</code> 上，然后满怀期待地生成 <code>Bitmap</code> 了…</p><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><p>简单来说，就是在知道图片全部加载完成之后，我们再生成最后的分享图，关键就是等！</p><p>这里我使用了一个<strong>计数器</strong>，每当 <code>Glide</code> 加载完成，拿到 <code>Bitmap</code> 后，我就让<strong>计数器</strong>+1，当<strong>计数器等于需要加载的图片个数时</strong>，回调。如下:</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></pre></td><td class="code"><pre><span class="line">Glide.with(viewGroup.getContext())</span><br><span class="line">          .load(mImages.get(position).getImageUrl())</span><br><span class="line">          .asBitmap()</span><br><span class="line">          .into(<span class="keyword">new</span> <span class="title class_">SimpleTarget</span>&lt;Bitmap&gt;() &#123;</span><br><span class="line">            <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onResourceReady</span><span class="params">(Bitmap resource,</span></span><br><span class="line"><span class="params">                GlideAnimation&lt;? <span class="built_in">super</span> Bitmap&gt; glideAnimation)</span> &#123;</span><br><span class="line"></span><br><span class="line">              ivCover.setImageBitmap(resource);</span><br><span class="line">              mloadImageCount++;</span><br><span class="line">              <span class="keyword">if</span> (mloadImageCount == getCoverCount() + <span class="number">1</span>) &#123;</span><br><span class="line">                mOnImageLoadedListener.onImageLoaded();</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>外部构造 Adapter 实例时，传入一个 OnImageLoadedListener 即可。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// MyAdapter 的签名</span></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">MyAdapter</span><span class="params">(<span class="meta">@NonNull</span> Context context, List&lt;Image&gt; images, <span class="meta">@NonNull</span> Bottom bottom,</span></span><br><span class="line"><span class="params">      <span class="meta">@NonNull</span> OnImageLoadedListener onImageLoadedListener)</span> &#123;</span><br><span class="line">      </span><br><span class="line"> &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="优化-思考"><a href="#优化-思考" class="headerlink" title="优化&amp;思考"></a>优化&amp;思考</h2><ul><li>Bitmap 的生成其实是一个耗时的操作，能否搬移到工作线程中</li><li>有没有比计数器更好的实现来解决异步问题</li><li>该实现有没有 leak memory 的风险</li><li>如果下半部分的文字内容很长，是否会导致 OOM</li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2017/12/29/%E5%8A%A8%E6%80%81%E7%94%9F%E6%88%90%E5%88%86%E4%BA%AB%E5%9B%BE%E7%89%87/</id>
    <link href="https://hellovass.github.io/2017/12/29/%E5%8A%A8%E6%80%81%E7%94%9F%E6%88%90%E5%88%86%E4%BA%AB%E5%9B%BE%E7%89%87/"/>
    <published>2017-12-29T16:19:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><p>本文描述了如何实现该需求的思路，代码可能<strong>不通用</strong>，但是该思路应该可以解决很多类似的需求…</p>]]>
    </summary>
    <title>动态生成分享图片</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><p>“减肥真TM痛苦，如果没有强烈的愿望，真的，千万别去尝试。。。因为，要么瘦，要么死2333”</p><span id="more"></span><h2 id="减肥的缘由"><a href="#减肥的缘由" class="headerlink" title="减肥的缘由"></a>减肥的缘由</h2><ul><li>胖，没有小姐姐喜欢</li><li>胖，走路费劲</li><li>胖，不符合我在学妹中的形象</li><li>胖，给同事和老板留下的印象可能会不好。因为胖一直是懒散的代言词啊</li><li>胖，不说了，都是泪…</li></ul><h2 id="成果"><a href="#成果" class="headerlink" title="成果"></a>成果</h2><p>截止到写文章的时候，已经成功从 95kg 减到了 72.5kg，将近减了 50斤 左右，厉害吧！</p><h2 id="95kg-的我"><a href="#95kg-的我" class="headerlink" title="95kg 的我"></a>95kg 的我</h2><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E8%BD%B2%E7%88%B7%E7%9A%84%E5%87%8F%E8%82%A5%E6%97%A5%E8%AE%B0/95kg%E6%97%B6%E7%9A%84%E6%88%91.jpg" alt="胖胖哒的我"></p><p>现在看起来，简直有种想自杀的冲动，怎么能胖成这样，跟猪一样。。。</p><h2 id="现在的我"><a href="#现在的我" class="headerlink" title="现在的我"></a>现在的我</h2><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E8%BD%B2%E7%88%B7%E7%9A%84%E5%87%8F%E8%82%A5%E6%97%A5%E8%AE%B0/%E5%88%9D%E4%B8%AD%E6%97%B6%E7%9A%84%E6%88%91.jpg" alt="初中时候的我"></p><p>简直回到了初中的有木有！</p><h3 id="好处"><a href="#好处" class="headerlink" title="好处"></a>好处</h3><ul><li>小肚子不存在了，变成结实的腹肌了，哈哈哈</li><li>膝盖的压力明显小了，走起路来健步如飞啊</li><li>体能更好，力量更强了，感觉现在的自己能打三个肥宅时期的我</li><li>衣服终于不用老是挑 XXL 号了，真的麻烦</li><li>基础代谢提高了，消化变好了，吃完饭没多久，肚子就瘪了，又想吃东西了。偶尔加加夜宵啥的也不会反弹，美滋滋</li><li>可以向大家炫耀下成果，当然我最终的目标其实是减到60kg，也就是累积减肥70斤，哈哈，差不多是一个成年人体重的一半了，怎么这么可怕的啊</li><li>…</li></ul><h2 id="从零开始的减肥生活"><a href="#从零开始的减肥生活" class="headerlink" title="从零开始的减肥生活"></a>从零开始的减肥生活</h2><h3 id="失败的减肥开始"><a href="#失败的减肥开始" class="headerlink" title="失败的减肥开始"></a>失败的减肥开始</h3><p>自 2016.11 入职，之后用了大概也就3个月的时间，我成功地从80kg长到了90kg。变肥之后，我愈发感受到了体重对我膝盖的压力，而背后的肌肉因为锻炼不够进而导致我的含胸驼背更加严重。2017.3，我办了人生中第一张健身卡，抱着减肥瘦腰的心情，我开始了第一段减肥之旅。</p><p>只能说自己那时候还是太年轻啊，以为在健身房里随便动动，就消耗了很多卡路里了，然后吃的还是那么不注意。结果，锻炼了4个月，成功增肥5kg，迎来了史上最恐怖的体重，95kg！！！真他喵成了200斤的大胖子了，我的天。</p><h3 id="真正的减肥开始"><a href="#真正的减肥开始" class="headerlink" title="真正的减肥开始"></a>真正的减肥开始</h3><p>emmm，其实在我自己瞎练的时候，有个小姐姐教练曾经多次安利我办私教课，然而我那时候出于某些原因（没钱，没钱，没钱）都拒绝了她的好意。后来，7月份之后，公司给我涨了2k工资之后，哇，我感觉时候出手了，于是，我主动找到我的教练和她签订了<strong>割肉条约</strong>：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E8%BD%B2%E7%88%B7%E7%9A%84%E5%87%8F%E8%82%A5%E6%97%A5%E8%AE%B0/%E5%92%8C%E6%95%99%E7%BB%83%E7%9A%84%E8%81%8A%E5%A4%A9%E8%AE%B0%E5%BD%9501.png" alt="割肉条约"></p><p>从此，传奇一般的减肥人生正式开始。</p><h4 id="健身减肥的第一天，想死"><a href="#健身减肥的第一天，想死" class="headerlink" title="健身减肥的第一天，想死"></a>健身减肥的第一天，想死</h4><p>现在回想起来，我真的超级无敌棒棒锤感谢那时候上完第一节课还决定要上第二节课的自己。第一节课上完，下楼没走几步后，我就跑到路边垃圾桶去吐了，那酸爽…（想逃课）</p><h4 id="健身减肥的第二天"><a href="#健身减肥的第二天" class="headerlink" title="健身减肥的第二天"></a>健身减肥的第二天</h4><p>上课，下楼，吐…（想逃课）</p><h4 id="健身减肥的第三天"><a href="#健身减肥的第三天" class="headerlink" title="健身减肥的第三天"></a>健身减肥的第三天</h4><p>上课，下楼，吐…（想逃课）</p><h4 id="持续了大概2周，腰好像细了？？？"><a href="#持续了大概2周，腰好像细了？？？" class="headerlink" title="持续了大概2周，腰好像细了？？？"></a>持续了大概2周，腰好像细了？？？</h4><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E8%BD%B2%E7%88%B7%E7%9A%84%E5%87%8F%E8%82%A5%E6%97%A5%E8%AE%B0/%E5%92%8C%E6%95%99%E7%BB%83%E7%9A%84%E8%81%8A%E5%A4%A9%E8%AE%B0%E5%BD%9502.png" alt="Screenshot_2018-01-04-19-19-57-447_微信"></p><h4 id="又过了几天，好像不是错觉哎"><a href="#又过了几天，好像不是错觉哎" class="headerlink" title="又过了几天，好像不是错觉哎"></a>又过了几天，好像不是错觉哎</h4><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E8%BD%B2%E7%88%B7%E7%9A%84%E5%87%8F%E8%82%A5%E6%97%A5%E8%AE%B0/%E5%92%8C%E6%95%99%E7%BB%83%E7%9A%84%E8%81%8A%E5%A4%A9%E8%AE%B0%E5%BD%9503.png" alt="Screenshot_2018-01-04-19-23-17-361_微信"></p><h4 id="第N天之后"><a href="#第N天之后" class="headerlink" title="第N天之后"></a>第N天之后</h4><p>脂肪就一点点儿这么流逝，当我想再从身上找到那熟悉的游泳圈时，却再也找不回那位老朋友了，我就知道，健身减肥没毛病，照这个趋势，我肯定能减下去。</p>]]>
    </content>
    <id>https://hellovass.github.io/2017/12/07/%E8%BD%B2%E7%88%B7%E7%9A%84%E5%87%8F%E8%82%A5%E6%97%A5%E8%AE%B0/</id>
    <link href="https://hellovass.github.io/2017/12/07/%E8%BD%B2%E7%88%B7%E7%9A%84%E5%87%8F%E8%82%A5%E6%97%A5%E8%AE%B0/"/>
    <published>2017-12-07T15:23:18.000Z</published>
    <summary>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><p>“减肥真TM痛苦，如果没有强烈的愿望，真的，千万别去尝试。。。因为，要么瘦，要么死2333”</p>]]>
    </summary>
    <title>轲爷的减肥日记</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Android" scheme="https://hellovass.github.io/tags/Android/"/>
    <content>
      <![CDATA[<h2 id="issue-微信登录失败"><a href="#issue-微信登录失败" class="headerlink" title="issue:微信登录失败"></a>issue:微信登录失败</h2><p>问题是这样的，项目里的社交化分享是基于 <code>UMShare</code> 封装成的一个 <code>ShareLib module</code>，为了让这个 <code>module</code> 对调用者说更<strong>透明</strong>，我将 <code>WXEntryActivity</code> 的<strong>申明</strong>从 <code>App module</code> 移动到了 <code>ShareLib module</code>里， <code>AndroidMainifest.xml</code> 中关于 <code>WXEntryActivity</code> 的<strong>申明</strong>如下：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%88%86%E4%BA%AB%E7%BB%84%E4%BB%B6%E8%B8%A9%E5%9D%91/ShareLibrary%E7%9A%84Manifest%E5%A3%B0%E6%98%8E.png" alt="ShareLibrary的 AndroidMainifest 里的申明"></p><p>同时把 <code>wxapi</code> 目录（包含 <code>WXEntryActivity.java</code> 也一并转移，根目录下的 <code>wxapi</code> 目录如图：</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%88%86%E4%BA%AB%E7%BB%84%E4%BB%B6%E8%B8%A9%E5%9D%91/wxapi%E7%9B%AE%E5%BD%95.png" alt="根目录下的wxapi目录"></p><p><code>App module</code> 确实清爽了不少。但是，这么做会引发怎样的问题呢？</p><span id="more"></span><h2 id="目前发现的问题"><a href="#目前发现的问题" class="headerlink" title="目前发现的问题"></a>目前发现的问题</h2><ul><li>用户点击微信登录总是失败，无法正常跳转到微信授权页面</li><li>用户无法使用微信分享</li><li>以上两种情况都会抛出 NameNotFoundException</li></ul><h2 id="冷静分析"><a href="#冷静分析" class="headerlink" title="冷静分析"></a>冷静分析</h2><h3 id="友盟的文档"><a href="#友盟的文档" class="headerlink" title="友盟的文档"></a>友盟的文档</h3><p><a href="http://dev.umeng.com/social/android/quick-integration#1_3_3">配置Android Mainifest XML</a>，文档里明确指出是需要在 <code>AndroidMainifest</code> 里申明 <code>WXEntryActivity</code>， 并在<strong>根目录</strong>下新建一个 <code>wxapi</code> 包，然后将 <code>WXEntryActivity</code> 放在里面。但说如果把这些东西放到 <code>module</code>项目里会如何，友盟并没有说，emmmm…</p><h3 id="遇事不觉-Debug"><a href="#遇事不觉-Debug" class="headerlink" title="遇事不觉 Debug"></a>遇事不觉 Debug</h3><p>进入 <code>UMShare SDK</code> 的源码里探索了一番后，将<strong>错误</strong>锁定在了 <code>checkWxBySelf</code> 这个静态方法上，该方法声明如下：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">checkWxBySelf</span><span class="params">(Context context)</span> &#123;</span><br><span class="line">  <span class="comment">// 获取包名</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">pkName</span> <span class="operator">=</span> context.getPackageName();</span><br><span class="line">  <span class="comment">// 获取类名</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">classPath</span> <span class="operator">=</span> pkName + <span class="string">&quot;.wxapi.WXEntryActivity&quot;</span>;</span><br><span class="line">    <span class="type">PackageInfo</span> <span class="variable">packageInfo</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="type">PackageManager</span> <span class="variable">packageManager</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      packageManager = context.getPackageManager();</span><br><span class="line">      packageInfo = packageManager.getPackageInfo(pkName, <span class="number">64</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (NameNotFoundException var8) &#123;</span><br><span class="line">      var8.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> getSignatureDigest(packageInfo);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="comment">// 1.通过全名加载 WXEntryActivity 类</span></span><br><span class="line">      Class&lt;?&gt; clz = Class.forName(classPath);</span><br><span class="line">      <span class="keyword">if</span>(clz == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;请检查微信后台注册签名：&quot;</span> + result.toLowerCase() + <span class="string">&quot;\n&quot;</span> + <span class="string">&quot;包名：&quot;</span> + pkName + <span class="string">&quot;\n&quot;</span> + <span class="string">&quot;没有配置微信回调activity或配置不正确&quot;</span>;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span>(Config.isUmengWx.booleanValue()) &#123;</span><br><span class="line">        <span class="keyword">if</span>(clz.getSuperclass() == <span class="literal">null</span>) &#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="string">&quot;WXEntryActivity配置不正确，您使用的是精简版，请使WXEntryActivity继承com.umeng.weixin.callback.WXCallbackActivity&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(!clz.getSuperclass().toString().contains(<span class="string">&quot;com.umeng.weixin&quot;</span>)) &#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="string">&quot;WXEntryActivity配置不正确，您使用的是精简版，请使WXEntryActivity继承com.umeng.weixin.callback.WXCallbackActivity&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">if</span>(clz.getSuperclass() == <span class="literal">null</span>) &#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="string">&quot;WXEntryActivity配置不正确，您使用的是完整版，请使WXEntryActivity继承com.umeng.socialize.weixin.view.WXCallbackActivity&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(!clz.getSuperclass().toString().contains(<span class="string">&quot;com.umeng.socialize&quot;</span>)) &#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="string">&quot;WXEntryActivity配置不正确，您使用的是完整版，请使WXEntryActivity继承com.umeng.socialize.weixin.view.WXCallbackActivity&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (ClassNotFoundException var9) &#123;</span><br><span class="line">      var9.printStackTrace();</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;请检查微信后台注册签名：&quot;</span> + result.toLowerCase() + <span class="string">&quot;\n&quot;</span> + <span class="string">&quot;包名：&quot;</span> + pkName + <span class="string">&quot;\n&quot;</span> + <span class="string">&quot;没有配置微信回调activity或配置不正确&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="type">ComponentName</span> <span class="variable">componentName</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ComponentName</span>(context.getPackageName(), classPath);</span><br><span class="line">      <span class="comment">// 2.根据指定的组件，获取 ActivityInfo 信息</span></span><br><span class="line">      packageManager.getActivityInfo(componentName, <span class="number">0</span>);</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;请检查微信后台注册签名：&quot;</span> + result.toLowerCase() + <span class="string">&quot;\n&quot;</span> + <span class="string">&quot;包名：&quot;</span> + pkName + <span class="string">&quot;\n&quot;</span> + <span class="string">&quot;Activity微信配置正确&quot;</span>;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (NameNotFoundException var7) &#123;</span><br><span class="line">      var7.printStackTrace();</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;请检查微信后台注册签名：&quot;</span> + result.toLowerCase() + <span class="string">&quot;\n&quot;</span> + <span class="string">&quot;包名：&quot;</span> + pkName + <span class="string">&quot;\n&quot;</span> + <span class="string">&quot;没有配置微信回调activity没有在Manifest中配置&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>注释里已经写了两个可能抛出异常的地方，和 packageName 都有关系，也就是说我们能否正确获取 packageName 直接影响到能否正常使用 UMShare SDK 的功能！很显然，我们的 <code>App Module</code> 里并没有对应的 WXEntryActivity，所以执行到<strong>注释1</strong>的时候就会抛出异常了！</p><h2 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h2><p>如果，我在 <code>App module</code> 里加上 wxapi 目录就 OK 了嘛？</p><blockquote><p>答案是no！还是会报错，只不过这次是在<strong>注释2</strong>抛出异常。</p></blockquote><p>又报错，这次明明加上了 wxapi 目录，已经可以找到 WXEntryActivity 了，为毛还是报错勒，emmm，再来冷静分析下吧。</p><h3 id="多个-AndroidMainifest-的合并"><a href="#多个-AndroidMainifest-的合并" class="headerlink" title="多个 AndroidMainifest 的合并"></a>多个 AndroidMainifest 的合并</h3><blockquote><p>关于多个 AndroidMainifest 的合并问题，可以去<a href="https://developer.android.com/studio/build/manifest-merge.html?hl=zh-cn">Android 官网</a>补补课。</p></blockquote><p>因为我们的项目有多个 module，每个 module 都有自己的 AndroidMainifest，而最终，这些文件会被合并为一个文件。</p><h3 id="还是看看-Merged-AndroidMainifest-吧"><a href="#还是看看-Merged-AndroidMainifest-吧" class="headerlink" title="还是看看 Merged AndroidMainifest 吧"></a>还是看看 Merged AndroidMainifest 吧</h3><h4 id="App-module-下的-merge-后的-AndroidMainifest-模样"><a href="#App-module-下的-merge-后的-AndroidMainifest-模样" class="headerlink" title="App module 下的 merge 后的 AndroidMainifest 模样"></a>App module 下的 merge 后的 AndroidMainifest 模样</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">activity</span></span></span><br><span class="line"><span class="tag">       <span class="attr">android:name</span>=<span class="string">&quot;info.hellovass.sharelib.wxapi.WXEntryActivity&quot;</span></span></span><br><span class="line"><span class="tag">       <span class="attr">android:configChanges</span>=<span class="string">&quot;keyboardHidden|orientation|screenSize&quot;</span></span></span><br><span class="line"><span class="tag">       <span class="attr">android:exported</span>=<span class="string">&quot;true&quot;</span></span></span><br><span class="line"><span class="tag">       <span class="attr">android:screenOrientation</span>=<span class="string">&quot;portrait&quot;</span></span></span><br><span class="line"><span class="tag">       <span class="attr">android:theme</span>=<span class="string">&quot;@android:style/Theme.Translucent.NoTitleBar&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><h4 id="ShareLib-module-下的-merge-后的-AndroidMainifest-模样："><a href="#ShareLib-module-下的-merge-后的-AndroidMainifest-模样：" class="headerlink" title="ShareLib module 下的 merge 后的 AndroidMainifest 模样："></a>ShareLib module 下的 merge 后的 AndroidMainifest 模样：</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">activity</span></span></span><br><span class="line"><span class="tag">       <span class="attr">android:name</span>=<span class="string">&quot;info.hellovass.sharelib.wxapi.WXEntryActivity&quot;</span></span></span><br><span class="line"><span class="tag">       <span class="attr">android:configChanges</span>=<span class="string">&quot;keyboardHidden|orientation|screenSize&quot;</span></span></span><br><span class="line"><span class="tag">       <span class="attr">android:exported</span>=<span class="string">&quot;true&quot;</span></span></span><br><span class="line"><span class="tag">       <span class="attr">android:screenOrientation</span>=<span class="string">&quot;portrait&quot;</span></span></span><br><span class="line"><span class="tag">       <span class="attr">android:theme</span>=<span class="string">&quot;@android:style/Theme.Translucent.NoTitleBar&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><h4 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h4><p>因为我们并没有在 App module 下的 AndroidMainifest 对 WXEntryActivity 做任何申明，所以，merge 时会合并 ShareLib module 里的申明，于是，就变成了上面这样子。看到这里，我想事情也明了了，因为这种情况，<code>ShareLib module</code> 的包名和 <code>App module</code> 的包名是<strong>不一致</strong>的。简单来说，WXEntryActivity 并未注册到 AndroidMainifest 中，所以会在 <code>packageManager.getActivityInfo(component, 0)</code> 出抛出异常。</p>]]>
    </content>
    <id>https://hellovass.github.io/2017/11/21/%E7%A4%BE%E4%BA%A4%E5%8C%96%E5%88%86%E4%BA%AB%E7%BB%84%E4%BB%B6%E8%B8%A9%E5%9D%91/</id>
    <link href="https://hellovass.github.io/2017/11/21/%E7%A4%BE%E4%BA%A4%E5%8C%96%E5%88%86%E4%BA%AB%E7%BB%84%E4%BB%B6%E8%B8%A9%E5%9D%91/"/>
    <published>2017-11-21T04:41:38.000Z</published>
    <summary>
      <![CDATA[<h2 id="issue-微信登录失败"><a href="#issue-微信登录失败" class="headerlink" title="issue:微信登录失败"></a>issue:微信登录失败</h2><p>问题是这样的，项目里的社交化分享是基于 <code>UMShare</code> 封装成的一个 <code>ShareLib module</code>，为了让这个 <code>module</code> 对调用者说更<strong>透明</strong>，我将 <code>WXEntryActivity</code> 的<strong>申明</strong>从 <code>App module</code> 移动到了 <code>ShareLib module</code>里， <code>AndroidMainifest.xml</code> 中关于 <code>WXEntryActivity</code> 的<strong>申明</strong>如下：</p>
<p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%88%86%E4%BA%AB%E7%BB%84%E4%BB%B6%E8%B8%A9%E5%9D%91/ShareLibrary%E7%9A%84Manifest%E5%A3%B0%E6%98%8E.png" alt="ShareLibrary的 AndroidMainifest 里的申明"></p>
<p>同时把 <code>wxapi</code> 目录（包含 <code>WXEntryActivity.java</code> 也一并转移，根目录下的 <code>wxapi</code> 目录如图：</p>
<p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%88%86%E4%BA%AB%E7%BB%84%E4%BB%B6%E8%B8%A9%E5%9D%91/wxapi%E7%9B%AE%E5%BD%95.png" alt="根目录下的wxapi目录"></p>
<p><code>App module</code> 确实清爽了不少。但是，这么做会引发怎样的问题呢？</p>]]>
    </summary>
    <title>社交化分享组件踩坑</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Android" scheme="https://hellovass.github.io/tags/Android/"/>
    <content>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><p>学习测试的概念，编写单元测试，保证代码的鲁棒性…</p><span id="more"></span><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="http://chriszou.com/">小创作</a></li><li><a href="https://item.jd.com/12163883.html">Java TDD开发</a></li></ul>]]>
    </content>
    <id>https://hellovass.github.io/2017/09/01/%E6%B5%8B%E8%AF%95%C2%B7%E5%AE%88%E5%BA%8F%E4%B8%AD%E7%AB%8B/</id>
    <link href="https://hellovass.github.io/2017/09/01/%E6%B5%8B%E8%AF%95%C2%B7%E5%AE%88%E5%BA%8F%E4%B8%AD%E7%AB%8B/"/>
    <published>2017-09-01T07:52:53.000Z</published>
    <summary>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><p>学习测试的概念，编写单元测试，保证代码的鲁棒性…</p>]]>
    </summary>
    <title>测试·守序中立</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="前言（选自《伽利略的烦恼》）"><a href="#前言（选自《伽利略的烦恼》）" class="headerlink" title="前言（选自《伽利略的烦恼》）"></a>前言（选自《伽利略的烦恼》）</h2><blockquote><p>“你觉得一个优秀的研究者必备的素质是什么？”</p><p>奈惠美稍微考虑了一下，回答道：“是认真吗？”</p><p>“这一点儿或许很重要，但光有认真的劲头是远远不够的，又是一时的糊涂也会带来重大的发现。身为研究者，必不可少的素质是纯粹，是那种不为任何事物所影响的，不被任何色彩所沾染的纯白之心。说起来简单，真正做起来却非常困难。其原因就在于，研究这种工作其实就像是在堆积石块，努力的研究者自然会希望向着自己的目标不断向上堆积。他们的心中自然有着自信，坚信自己的目标并没有错，但有时这也是致命的。刚开始放的石块位置是否正确，不，首先他放的究竟是不是石块——一旦心中产生了这样的怀疑，就很难再把自己堆起来的石块全部推翻，因为会被之前取得的成绩所束缚。想要保持一颗纯粹的心是很困难的。”</p></blockquote><span id="more"></span><h2 id="忧郁的心情"><a href="#忧郁的心情" class="headerlink" title="忧郁的心情"></a>忧郁的心情</h2><p>好久不见的 M 菌又给我发来了几条信息，聚精会神观看《权力的游戏》的我表示稍后会主动@她。这次她的烦恼还是和往日的一样，问题其实也并不重要，关键的是她的<strong>病</strong>还没好，所以，会烦、会焦躁，似乎这周围熟悉的不能再熟悉的地方也会给她带来不安，甚至令她厌恶。M菌的内心世界究竟如何，我，不得而知，尽管是她的学长和盆友，我也有点儿力不从心了。</p><h3 id="怀疑-批评"><a href="#怀疑-批评" class="headerlink" title="怀疑&amp;批评"></a>怀疑&amp;批评</h3><p>首先，我是认为她这一年来是没什么长进的，可能样貌变得更漂亮了，可能气质有了，但是，内心还是跟往日没什么区别，就像一只流浪的小野猫，渴望一个安静的地方，却在夜深人静时得不到任何的安宁。为什么这样说，因为与她同期的几个小学妹反而变得成熟了一些，至少<strong>不再像大一时那样不知道自己在忙啥了</strong>。知道自己在忙啥，这点很重要，我高中的物理老师教训过我好多次。</p><h2 id="答非所问-无能为力"><a href="#答非所问-无能为力" class="headerlink" title="答非所问&amp;无能为力"></a>答非所问&amp;无能为力</h2><p>这是我对自己今次对话的评价。</p><p>没办法，我帮不了她。我知道 M菌 的问题并非是我一个工作在外，偶尔闲聊几句就能解决的，她最需要的，我想她自己也知道，来自家人、盆友、童鞋、老师、<del>男朋友</del>的关心和陪伴。我想她可能还是没得到这些。</p><p>抱歉，这次的谈话似乎很<strong>不走心</strong>。</p><h2 id="Be-Pure"><a href="#Be-Pure" class="headerlink" title="Be Pure"></a>Be Pure</h2><blockquote><p>工作和考研我该选哪个？</p><p>​——M菌</p></blockquote><p>请忘掉微信里的对话，那时的我还没想好怎么回答，<del>不算数，重来</del></p><p>其实，我想你自己也知道，你并不是不知道问题的答案，这个问题，由别人来建议或给出答案并没有任何意义。你啊，只是想找个人，来说服你自己，帮你说出你自己的答案，然后鼓励下你，<strong>那你很棒棒哦</strong>，correct？</p><p>而你的矛盾，你自己也知道，放不下往日的荣光，<strong>不甘心于此</strong>，而又不肯重来，correct？</p><p>截止到写这篇文章之前，我也没啥有说服力的话能对你讲，但现在你可以多读几遍<strong>前言</strong>。如果，还是感到抑郁的话，可以<del>悄悄咪咪地来找我撸猫23333</del>看看东野先生的小说嘛…</p><h2 id="写在结尾"><a href="#写在结尾" class="headerlink" title="写在结尾"></a>写在结尾</h2><p>本人</p><p>社交纯粹</p><p><del>没女朋友</del></p>]]>
    </content>
    <id>https://hellovass.github.io/2017/08/01/%E7%BA%AF%E7%B2%B9%E3%80%81%E5%BF%A7%E9%83%81%E5%92%8C%E8%B0%88%E5%BF%83/</id>
    <link href="https://hellovass.github.io/2017/08/01/%E7%BA%AF%E7%B2%B9%E3%80%81%E5%BF%A7%E9%83%81%E5%92%8C%E8%B0%88%E5%BF%83/"/>
    <published>2017-08-01T17:28:58.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言（选自《伽利略的烦恼》）"><a href="#前言（选自《伽利略的烦恼》）" class="headerlink" title="前言（选自《伽利略的烦恼》）"></a>前言（选自《伽利略的烦恼》）</h2><blockquote>
<p>“你觉得一个优秀的研究者必备的素质是什么？”</p>
<p>奈惠美稍微考虑了一下，回答道：“是认真吗？”</p>
<p>“这一点儿或许很重要，但光有认真的劲头是远远不够的，又是一时的糊涂也会带来重大的发现。身为研究者，必不可少的素质是纯粹，是那种不为任何事物所影响的，不被任何色彩所沾染的纯白之心。说起来简单，真正做起来却非常困难。其原因就在于，研究这种工作其实就像是在堆积石块，努力的研究者自然会希望向着自己的目标不断向上堆积。他们的心中自然有着自信，坚信自己的目标并没有错，但有时这也是致命的。刚开始放的石块位置是否正确，不，首先他放的究竟是不是石块——一旦心中产生了这样的怀疑，就很难再把自己堆起来的石块全部推翻，因为会被之前取得的成绩所束缚。想要保持一颗纯粹的心是很困难的。”</p>
</blockquote>]]>
    </summary>
    <title>纯粹、抑郁和谈心</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><blockquote><p>“没办法啊，人们通常会有先入为主的观念。明明知道肥皂泡中有空气，却因看不见便忽视它的存在。如果以这种态度对待，还能不遗漏人生中的许多事情，那真是遗憾。”</p></blockquote><span id="more"></span><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E8%82%A5%E7%9A%82%E6%B3%A1%E5%8D%B0%E8%B1%A1%E4%B8%8E%E5%87%8F%E8%82%A5/%E6%81%B6%E6%84%8F.jpg" alt="恶意"></p><h2 id="错误的印象"><a href="#错误的印象" class="headerlink" title="错误的印象"></a>错误的印象</h2><p>看<strong>东野先生</strong>的书也有几本了，发现<strong>东野先生</strong>真的很擅长利用人<strong>先入为主</strong>的这个缺点。最近看完《恶意》和《神探伽利略》，正好可以来谈谈感想。《恶意》这本小说真的很<strong>捉弄人</strong>，也不知道什么时候，就被<strong>套</strong>进去了，觉得原来<strong>野野口</strong>才是最大的受害人。看完之后，仔细回想，为什么一开始我会相信<strong>野野口</strong>的笔记呢？我盯着书的封面发呆，突然，封面上印的那只<strong>小白猫</strong>让我豁然开朗——一开始的<strong>毒猫事件</strong>啊！野野口从一开始就设计了这个<strong>不存在的日高先生制作毒丸子杀猫</strong>桥段，给我们植入了一个<strong>不好的甚至有些恶毒的日高形象</strong>，这一招，妙！</p><h2 id="三省吾身"><a href="#三省吾身" class="headerlink" title="三省吾身"></a>三省吾身</h2><p>那么，我又给周围的盆友植入了一种怎样的印象呢？</p><h3 id="那你很胖胖哦"><a href="#那你很胖胖哦" class="headerlink" title="那你很胖胖哦"></a>那你很胖胖哦</h3><p>我得出了一条结论，胖，是原罪！</p><ul><li>我之前根本不胖好嘛，这些肉是后来长上去的！而这些累赘无疑增加了我的行动负担，导致我每天都是在做<strong>负重运动</strong>，直接影响就是——累，很累。。。背总是有点儿驼，直不起来，显得我这个人很<strong>慵懒</strong>。</li><li>迟钝，这是给人的另一个不好印象。因为胖胖哒，显得脸比较圆，而小说里、电影里，一般这种角色不是便当命，就是演反派被主角 KO 的。</li><li>撩妹的成功率不会太高！总有学妹来“关心我”，<strong>轲爷你怎么还没有女票啊？<strong>好吧，既然这么想知道，解释下吧。其实，我这人对妹纸的要求其实还蛮高的，比很多人想象的要高。首先是颜值，没错，我也是</strong>先入为主</strong>的那种俗人，也会忽略肥皂泡里的<strong>空气</strong>。然后是兴趣，我期待的妹纸是一个和我有差不多<strong>爱好</strong>的人。一般来说，前面这两点，就已经能<strong>筛掉</strong>大部分妹纸了。那么剩下的，真正适合我的那些，<del>说不定和我一样，首先考虑的不是肥皂泡里的空气</del>，所以，你懂得。</li><li>…</li></ul><h2 id="减肥"><a href="#减肥" class="headerlink" title="减肥"></a>减肥</h2><p>自然而然，我决定为能让人看得到的那部分而努力了！其实，写这篇文章前，我已经上了两节私教课，就效果的来说的话，非常有，每次健完身，就他喵像刚游完泳一样，exited！！！</p><h3 id="成本-精力"><a href="#成本-精力" class="headerlink" title="成本&amp;精力"></a>成本&amp;精力</h3><ul><li>私教课 280￥&#x2F;节</li><li>每月至少需要上 12 节</li><li>至少要上<strong>三个月</strong>才能见到些成效</li><li>每次上课，都很累</li><li>…</li></ul><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>没有什么美好的事物是可以不用努力付出而 easy get 的，就连占肥皂泡 1% 不到的<strong>表面薄面</strong>也一样。谁知道那些外貌协会的童鞋，没有为自己对另一半的高标准付出过常人难以想象的努力呢！</p>]]>
    </content>
    <id>https://hellovass.github.io/2017/07/29/%E8%82%A5%E7%9A%82%E6%B3%A1%E3%80%81%E5%8D%B0%E8%B1%A1%E4%B8%8E%E5%87%8F%E8%82%A5/</id>
    <link href="https://hellovass.github.io/2017/07/29/%E8%82%A5%E7%9A%82%E6%B3%A1%E3%80%81%E5%8D%B0%E8%B1%A1%E4%B8%8E%E5%87%8F%E8%82%A5/"/>
    <published>2017-07-29T14:42:06.000Z</published>
    <summary>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><blockquote>
<p>“没办法啊，人们通常会有先入为主的观念。明明知道肥皂泡中有空气，却因看不见便忽视它的存在。如果以这种态度对待，还能不遗漏人生中的许多事情，那真是遗憾。”</p>
</blockquote>]]>
    </summary>
    <title>肥皂泡、印象与减肥</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Android" scheme="https://hellovass.github.io/tags/Android/"/>
    <content>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=463462465&auto=0&height=66"></iframe><p>记得大二那年第一次接触 GreenDao 这个<strong>神奇</strong>的数据库，惊叹道，哇，原来代码还能这么写啊，不用自己手撸 SQLiteDatabase，不用写那些麻烦的 SQL 语句，编程还真是一件 “轻松” 的事情呢2333。然而，时隔多年，当我再次看到自己曾经留在项目里的那段代码时，相顾无言，惟有泪千行…</p><span id="more"></span><h2 id="自己挖的坑，哭着也要填完"><a href="#自己挖的坑，哭着也要填完" class="headerlink" title="自己挖的坑，哭着也要填完"></a>自己挖的坑，哭着也要填完</h2><p>在此，向川神和小飞飞道歉，哈哈，让你们一直用着我当年的拙劣之作，实在抱歉，你们那时候一定很想打死我吧！</p><h3 id="新的类图，更强的封装"><a href="#新的类图，更强的封装" class="headerlink" title="新的类图，更强的封装"></a>新的类图，更强的封装</h3><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E7%83%98%E7%84%99DBService/greenDao%E5%B0%81%E8%A3%85%E7%B1%BB%E5%9B%BE.png" alt="类图"></p><h3 id="目录结构看起来是这样的"><a href="#目录结构看起来是这样的" class="headerlink" title="目录结构看起来是这样的"></a>目录结构看起来是这样的</h3><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E7%83%98%E7%84%99DBService/greenDao%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84%E5%9B%BE.png" alt="目录结构"></p><h2 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h2><h3 id="数据库版本"><a href="#数据库版本" class="headerlink" title="数据库版本"></a>数据库版本</h3><p>greenDao3.x，3.x版本的 greenDao 采用注解+apt 的方式生成 PO（数据持久化对象），和以前的定义 Generator 方式相比，真的简洁了不少。</p><h3 id="设计模式-法则"><a href="#设计模式-法则" class="headerlink" title="设计模式&amp;法则"></a>设计模式&amp;法则</h3><ul><li>抽象工厂</li><li>依赖倒置</li></ul><h2 id="产品类族"><a href="#产品类族" class="headerlink" title="产品类族"></a>产品类族</h2><h3 id="DBService"><a href="#DBService" class="headerlink" title="DBService"></a>DBService</h3><p>仅包含一个 AbsDaoDelegate 的引用，通过建造者模式创建它的实例。源码如下：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">DBService</span>&lt;T, K&gt; &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> AbsDaoDelegate&lt;T, K&gt; mDaoDelegate;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">DBService</span><span class="params">(Builder&lt;T, K&gt; builder)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mDaoDelegate = builder.mDaoDelegate;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> AbsDaoDelegate&lt;T, K&gt; <span class="title function_">getDaoDelegate</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> mDaoDelegate;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Builder</span>&lt;T, K&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> AbsDaoDelegate&lt;T, K&gt; mDaoDelegate;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Builder&lt;T, K&gt; <span class="title function_">setDaoDelegate</span><span class="params">(AbsDaoDelegate&lt;T, K&gt; daoDelegate)</span> &#123;</span><br><span class="line"></span><br><span class="line">      mDaoDelegate = daoDelegate;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">checkEmptyFields</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (mDaoDelegate == <span class="literal">null</span>) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;core dao can&#x27;t be null&quot;</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> DBService&lt;T, K&gt; <span class="title function_">build</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">      checkEmptyFields();</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DBService</span>&lt;&gt;(<span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意，DBService 是一个泛型类，泛型 T 代表了我们定义的 PO（数据持久化对象），泛型 K 代表了 PO 的<strong>主键类型</strong>。不要急，稍后会给出如何定义 PO 的栗子。</p><h3 id="AbsDaoDelegate"><a href="#AbsDaoDelegate" class="headerlink" title="AbsDaoDelegate"></a>AbsDaoDelegate</h3><ul><li>实现了 DataSource 接口，提供了开发中需要的 60% 的方法</li><li>代理模式，本身不做 CUID 操作，全部交由 greenDao 生成的 XXDao 来完成</li><li>…</li></ul><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><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><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbsDaoDelegate</span>&lt;T, K&gt; <span class="keyword">implements</span> <span class="title class_">DataSource</span>&lt;T, K&gt; &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">protected</span> AbstractDao&lt;T, K&gt; mAbstractDao;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">AbsDaoDelegate</span><span class="params">(AbstractDao&lt;T, K&gt; abstractDao)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mAbstractDao = abstractDao;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">(T data)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mAbstractDao.insert(data);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addAll</span><span class="params">(List&lt;T&gt; dataList)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mAbstractDao.insertInTx(dataList);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">delete</span><span class="params">(T data)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mAbstractDao.delete(data);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deleteByKey</span><span class="params">(K k)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mAbstractDao.deleteByKey(k);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deleteByKeys</span><span class="params">(List&lt;K&gt; keyList)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mAbstractDao.deleteByKeyInTx(keyList);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deleteByCondition</span><span class="params">(WhereCondition whereCondition)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mAbstractDao.queryBuilder()</span><br><span class="line">        .where(whereCondition)</span><br><span class="line">        .buildDelete()</span><br><span class="line">        .executeDeleteWithoutDetachingEntities();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deleteAll</span><span class="params">(List&lt;T&gt; dataList)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mAbstractDao.deleteInTx(dataList);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">update</span><span class="params">(T t)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mAbstractDao.update(t);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateAll</span><span class="params">(List&lt;T&gt; dataList)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mAbstractDao.updateInTx(dataList);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">insertOrReplace</span><span class="params">(T t)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mAbstractDao.insertOrReplace(t);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">insertOrReplace</span><span class="params">(List&lt;T&gt; dataList)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mAbstractDao.insertOrReplaceInTx(dataList);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> T <span class="title function_">queryByKey</span><span class="params">(K k)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> mAbstractDao.load(k);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> List&lt;T&gt; <span class="title function_">queryByPage</span><span class="params">(<span class="type">int</span> pageNum)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> mAbstractDao.queryBuilder().offset(<span class="number">10</span> * (pageNum - <span class="number">1</span>)).limit(<span class="number">10</span>).list();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> List&lt;T&gt; <span class="title function_">queryByCondition</span><span class="params">(WhereCondition whereCondition, Property sort)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> mAbstractDao.queryBuilder().where(whereCondition).orderDesc(sort).list();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> List&lt;T&gt; <span class="title function_">queryAll</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> mAbstractDao.loadAll();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">clear</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    mAbstractDao.deleteAll();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="工厂类族"><a href="#工厂类族" class="headerlink" title="工厂类族"></a>工厂类族</h2><h3 id="抽象工厂-DBServiceFactory"><a href="#抽象工厂-DBServiceFactory" class="headerlink" title="抽象工厂 DBServiceFactory"></a>抽象工厂 DBServiceFactory</h3><ul><li>定义了创建 DBService 的方法，至于如何创建工厂，交给具体工厂（实现了抽象工厂的类）来决定</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DBServiceFactory</span>&lt;T, K&gt; &#123;</span><br><span class="line"></span><br><span class="line">  DBService&lt;T, K&gt; <span class="title function_">createDBService</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="DBServiceStore"><a href="#DBServiceStore" class="headerlink" title="DBServiceStore"></a>DBServiceStore</h3><ul><li>定义了创建 DBService 的方法，看起来和 DBServiceFactory 有点儿像，但意义不同。商店并不负责造产品，只是根据用户传入的<strong>工厂</strong>来提供对应的产品 DBService，自己并不会负责去造产品。如果是这样的话，那商店未免也太辛苦了不是吗？</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">DBServiceStore</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">abstract</span> &lt;T, K&gt; DBService&lt;T, K&gt; <span class="title function_">createDBService</span><span class="params">(DBServiceFactory&lt;T, K&gt; factory)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="DBHelper"><a href="#DBHelper" class="headerlink" title="DBHelper"></a>DBHelper</h3><p>和 2.x 时代的写法相比并没有太大的区别，这里就直接贴代码了。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">DBHelper</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">static</span> DBHelper sDBHelper;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> DaoMaster mDaoMaster;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> DaoSession mDaoSession;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">static</span> DBHelper <span class="title function_">getInstance</span><span class="params">(Context context, String dbName)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (sDBHelper == <span class="literal">null</span>) &#123;</span><br><span class="line">      </span><br><span class="line">      sDBHelper = <span class="keyword">new</span> <span class="title class_">DBHelper</span>(context, dbName);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> sDBHelper;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> DaoSession <span class="title function_">getDaoSession</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> mDaoSession;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">DBHelper</span><span class="params">(Context context, String dbName)</span> &#123;</span><br><span class="line"></span><br><span class="line">    DaoMaster.<span class="type">DevOpenHelper</span> <span class="variable">devOpenHelper</span> <span class="operator">=</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">DaoMaster</span>.DevOpenHelper(context.getApplicationContext(), dbName, <span class="literal">null</span>);</span><br><span class="line">    mDaoMaster = <span class="keyword">new</span> <span class="title class_">DaoMaster</span>(devOpenHelper.getWritableDatabase());</span><br><span class="line">    mDaoSession = mDaoMaster.newSession();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="PO-定义"><a href="#PO-定义" class="headerlink" title="PO 定义"></a>PO 定义</h3><p>也就是类图中 User 的声明，greenDao 3.x 之后，我们可以使用注解来声明一个 PO，然后 build 一下，greenDao 会自动在 greendao 这个包目录（包目录可以根据自己的喜好指定）下生成我们需要的 DaoMaster、DaoSession、UserDao。 私以为，这些由 apt 自动生成的 class ，我们最好不要改动！！！</p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>前面讲了这么多，不过是介绍我的封装思路而已，那么实际项目中怎么使用呢？其实写这篇博客也是给自己做备忘2333…</p><h3 id="PeroDBServiceStore"><a href="#PeroDBServiceStore" class="headerlink" title="PeroDBServiceStore"></a>PeroDBServiceStore</h3><p>implements 自 DBServiceStore，代表 DBService 商店，我们需要 XXDBService 的时候，只需要调用 createDBService 方法，将具体的工厂 XXDBServiceFactory 作为参数传入即可。</p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PeroDBServiceStore</span> <span class="keyword">extends</span> <span class="title class_">DBServiceStore</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">PeroDBServiceStore</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">PeroDBServiceStoreSingletonHolder</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">PeroDBServiceStore</span> <span class="variable">sInstance</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PeroDBServiceStore</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">static</span> PeroDBServiceStore <span class="title function_">getInstance</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> PeroDBServiceStoreSingletonHolder.sInstance;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> &lt;T, K&gt; DBService&lt;T, K&gt; <span class="title function_">createDBService</span><span class="params">(DBServiceFactory&lt;T, K&gt; factory)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> factory.createDBService();</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>implements 自 DBServiceFactory，生产具体产品，然后提供给商店，最后提供给我们这些<strong>消费者</strong>。</p><ol><li>根据需要定义 PO</li><li>根据 PO 定义 XXDaoDelegate</li><li>根据 XXDaoDelegate 定义 XXDBService</li><li>根据 XXDBService 定义 XXDBServiceFactory</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserDBServiceFactory</span> <span class="keyword">implements</span> <span class="title class_">DBServiceFactory</span>&lt;User, String&gt; &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">UserDBServiceFactory</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">static</span> UserDBServiceFactory <span class="title function_">create</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">UserDBServiceFactory</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> DBService&lt;User, String&gt; <span class="title function_">createDBService</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DBService</span>.Builder&lt;User, String&gt;   ().setDaoDelegate(provideDaoDelegate()).build();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> AbsDaoDelegate&lt;User, String&gt; <span class="title function_">provideDaoDelegate</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AbsDaoDelegate</span>&lt;User, String&gt;(</span><br><span class="line">        DBHelper.getInstance(App.getInstance(), <span class="string">&quot;pero_db&quot;</span>).getDaoSession().getUserDao()) &#123;</span><br><span class="line"></span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里，我只定义了一个 UserDBServiceFactory，其它的都是用<strong>匿名对象</strong>实现了。当然，这是为了简化代码，大部分情况下都是可以满足的，毕竟 DataSource 接口定义了很多常用的方法，比如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">DataSource</span>&lt;T, K&gt; &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">add</span><span class="params">(T data)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">addAll</span><span class="params">(List&lt;T&gt; dataList)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">delete</span><span class="params">(T data)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">deleteByKey</span><span class="params">(K k)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">deleteByKeys</span><span class="params">(List&lt;K&gt; keyList)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">deleteByCondition</span><span class="params">(WhereCondition whereCondition)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">deleteAll</span><span class="params">(List&lt;T&gt; dataList)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">update</span><span class="params">(T t)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">updateAll</span><span class="params">(List&lt;T&gt; dataList)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">insertOrReplace</span><span class="params">(T t)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">insertOrReplace</span><span class="params">(List&lt;T&gt; dataList)</span>;</span><br><span class="line"></span><br><span class="line">  T <span class="title function_">queryByKey</span><span class="params">(K k)</span>;</span><br><span class="line"></span><br><span class="line">  List&lt;T&gt; <span class="title function_">queryByPage</span><span class="params">(<span class="type">int</span> pageNum)</span>;</span><br><span class="line"></span><br><span class="line">  List&lt;T&gt; <span class="title function_">queryByCondition</span><span class="params">(WhereCondition whereCondition, Property sort)</span>;</span><br><span class="line"></span><br><span class="line">  List&lt;T&gt; <span class="title function_">queryAll</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">clear</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这时候，我们 save 一个 User 的时候可以这么写了：</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></pre></td><td class="code"><pre><span class="line">PeroDBServiceStore.getInstance().createDBService(UserDBServiceFactory.create())</span><br><span class="line">  .getDaoDelegate()</span><br><span class="line">        .add(user); <span class="comment">// save user to db</span></span><br></pre></td></tr></table></figure><p>&#x2F;&#x2F; 因为懒，省略其他的栗子了</p><h2 id="疑问-解答"><a href="#疑问-解答" class="headerlink" title="疑问&amp;解答"></a>疑问&amp;解答</h2><h3 id="兄弟，那我需要更骚的操作怎么办？"><a href="#兄弟，那我需要更骚的操作怎么办？" class="headerlink" title="兄弟，那我需要更骚的操作怎么办？"></a>兄弟，那我需要更骚的操作怎么办？</h3><p>比如，我需要根据 User 的某个属性（比如用户 Id）来获取数据，怎么破？</p><h3 id="解答"><a href="#解答" class="headerlink" title="解答"></a>解答</h3><p><del>额，这个骚操作，在 DataSource 里确实不怎么好定义（至少我当时没想好）</del>，可以破。在 XXDBServiceFactory 里定义 XXDaoDelegate，实现你的骚操作。调用的时候，这么来：</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></pre></td><td class="code"><pre><span class="line">PeroDBServiceStore.getInstance().createDBService(UserDBServiceFactory.create())</span><br><span class="line">  .getDaoDelegate()</span><br><span class="line">  .queryByCondition(UserDao.Properties.UserId.eq(userId),UserDao.Properties.createdAt);</span><br></pre></td></tr></table></figure><p><del>可能不够优雅 233333，总之我看着是有点儿不爽，哪天有空再改改…</del></p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>五一假期，在空间看到学弟组织了一波钱塘江烧烤+KTV，撸着烤串，唱歌歌，就这么唱出自己的大学人生，仔细想想，就是有这样的骚操作呢！可惜，还是少了几个妹纸啊233333</p>]]>
    </content>
    <id>https://hellovass.github.io/2017/04/21/%E4%BC%98%E9%9B%85%E5%9C%B0%E7%83%98%E7%84%99%20DBService/</id>
    <link href="https://hellovass.github.io/2017/04/21/%E4%BC%98%E9%9B%85%E5%9C%B0%E7%83%98%E7%84%99%20DBService/"/>
    <published>2017-04-21T10:30:05.000Z</published>
    <summary>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=463462465&auto=0&height=66"></iframe>

<p>记得大二那年第一次接触 GreenDao 这个<strong>神奇</strong>的数据库，惊叹道，哇，原来代码还能这么写啊，不用自己手撸 SQLiteDatabase，不用写那些麻烦的 SQL 语句，编程还真是一件 “轻松” 的事情呢2333。然而，时隔多年，当我再次看到自己曾经留在项目里的那段代码时，相顾无言，惟有泪千行…</p>]]>
    </summary>
    <title>优雅地烘焙 DBService</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>周末，去了一趟久违的校园，参加了 HelloWorld 和我社联合举办的一次技术沙龙讲座，<del>我不会告诉你其实就是找个机会去和他们玩耍的2333</del></p><span id="more"></span><p>后面两场纯技术的分享，我就不谈了，第一场分享，似乎的标题似乎是《技术与艺术》，这个分享的主题没什么问题，但是，讲座的童鞋可能太紧张了，没有理清楚自己的逻辑，讲得实在太枯燥乏味了。。。不过，也好，正好勾起了我的兴趣，今次，就来分享下什么是<strong>技术♂与艺术</strong>。</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E9%87%8D%E6%9E%84%E6%97%A0%E9%9C%80%E6%95%AC%E7%95%8F/%E4%BD%A0%E5%AD%A6%E5%88%B0%E4%BA%86%E5%90%97.jpg" alt="yc 你学到了吗"></p><h2 id="无需敬畏"><a href="#无需敬畏" class="headerlink" title="无需敬畏"></a>无需敬畏</h2><p>把你的敬畏扔到大海，对于即将变得像空气和水一样普通的技术，无需对它敬畏。</p>]]>
    </content>
    <id>https://hellovass.github.io/2017/04/16/%E9%87%8D%E6%9E%84%EF%BC%8C%E6%97%A0%E9%9C%80%E6%95%AC%E7%95%8F/</id>
    <link href="https://hellovass.github.io/2017/04/16/%E9%87%8D%E6%9E%84%EF%BC%8C%E6%97%A0%E9%9C%80%E6%95%AC%E7%95%8F/"/>
    <published>2017-04-16T13:30:10.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>周末，去了一趟久违的校园，参加了 HelloWorld 和我社联合举办的一次技术沙龙讲座，<del>我不会告诉你其实就是找个机会去和他们玩耍的2333</del></p>]]>
    </summary>
    <title>重构，无需敬畏</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Android" scheme="https://hellovass.github.io/tags/Android/"/>
    <content>
      <![CDATA[<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=434659672&auto=0&height=66"></iframe><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><blockquote><p>这感觉已经不对</p><p>我最后才了解</p><p>一页页不忍翻阅</p><p>的情节你好累</p></blockquote><p>这次要做的事是按照业务重构网络层的<strong>错误拦截和分发</strong>，仅以这段歌词献给两位前同事。</p><span id="more"></span><h2 id="整理下逻辑"><a href="#整理下逻辑" class="headerlink" title="整理下逻辑"></a>整理下逻辑</h2><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Rx%E9%94%99%E8%AF%AF%E6%8B%A6%E6%88%AA%E5%92%8C%E5%88%86%E5%8F%91/%E9%94%99%E8%AF%AF%E6%8B%A6%E6%88%AA%E5%92%8C%E5%88%86%E5%8F%91.png" alt="错误拦截以及分发"></p><h2 id="本次的两个知识点（敲黑板"><a href="#本次的两个知识点（敲黑板" class="headerlink" title="本次的两个知识点（敲黑板"></a>本次的两个知识点（敲黑板</h2><h3 id="onErrorResumeNext-操作符的运用"><a href="#onErrorResumeNext-操作符的运用" class="headerlink" title="onErrorResumeNext 操作符的运用"></a>onErrorResumeNext 操作符的运用</h3><p>这个操作符是干嘛的呢？当错误发生时，使用另一个数据流（Observable）继续发射数据，在返回的 Observable 中是看不到错误信息的。利用这个操作符，我们可以实现把<strong>一个异常信息包装起来再次抛出</strong>。</p><h3 id="CallAdapter-的魔改"><a href="#CallAdapter-的魔改" class="headerlink" title="CallAdapter 的魔改"></a>CallAdapter 的魔改</h3><p>那在哪里拦截异常，然后重新包装再抛出（分发）呢？</p><p>这里先分享下我的好 gay 友 <a href="http://www.jianshu.com/u/6b372d09b617">YoKey</a> 的<a href="http://www.jianshu.com/p/f3f0eccbcd6f">方案</a>。youyou 的方案非常简洁，使用一个静态方法，方法里内部根据 server 端返回的 status 来分发错误。调用的时候，就像设置<strong>哨卡</strong>一样，在每个 API 请求的后面带上 <code>RxResultHelper.handleResult()</code> 即可（ps:就是遇到请求比较多的时候，需要设置好几个<strong>哨卡</strong>）。</p><h4 id="我的方案"><a href="#我的方案" class="headerlink" title="我的方案"></a>我的方案</h4><p>其实大体上和 youyou 是如出一辙的，就是设置<strong>哨卡</strong>的地方有点儿不太一样，这里我们再回忆下 API 的形式吧：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 获取验证码</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="meta">@POST(&quot;/sms&quot;)</span> Observable&lt;ServerResult&lt;Params&gt;&gt; <span class="title function_">fetchSmsCode</span><span class="params">(<span class="meta">@Body</span> Params params)</span>;</span><br></pre></td></tr></table></figure><p>方法调用的返回结果是一个 Observable&lt;ServerResult<Params>，熟悉 Retrofit 的童鞋肯定明白，Retrofit 只能返回一个 ReponseBody，为什么 <code>ReponseBody -&gt;Observable&lt;ServerResult&gt;</code> 了呢？</p><p>聪明的童鞋肯定知道，这里辛勤的老外盆友替我们做了转换的工作，使我们得到了一个 Observable，这才使流式调用有可能。而转换的地方，那应该是一个<strong>适配器没错了</strong>，看看我们的代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">mRetrofit = <span class="keyword">new</span> <span class="title class_">Retrofit</span>.Builder().baseUrl(retrofitWrapperFactory.provideBaseUrl())</span><br><span class="line">        .client(retrofitWrapperFactory.createOkHttpClient())</span><br><span class="line">        .addConverterFactory(retrofitWrapperFactory.createGsonConverterFactory())</span><br><span class="line">        .addCallAdapterFactory(retrofitWrapperFactory.createRxJavaCallAdapter()) <span class="comment">// 设置 CallAdapterFactory</span></span><br><span class="line">        .build();</span><br></pre></td></tr></table></figure><p>再仔细想想，这个 CallAdapterFactory 肯定是做了 <code>ResponseBody -&gt; Observable</code> 的转换没错了，如果我们能 custom 这个 Factory，我们就能拿到 Observable，如果在这里给它设置一个错误分发器的话，岂不是美滋滋？</p><h3 id="小课堂"><a href="#小课堂" class="headerlink" title="小课堂"></a>小课堂</h3><p><a href="http://www.jianshu.com/p/308f3c54abdd">自定义 CallAdapter</a>，可能是因为我用的是最新版的 Retrofit 吧，CallAdapter 的源码已经有些改变了</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Adapts a &#123;<span class="doctag">@link</span> Call&#125; with response type &#123;<span class="doctag">@code</span> R&#125; into the type of &#123;<span class="doctag">@code</span> T&#125;. Instances are</span></span><br><span class="line"><span class="comment"> * created by &#123;<span class="doctag">@linkplain</span> Factory a factory&#125; which is</span></span><br><span class="line"><span class="comment"> * &#123;<span class="doctag">@linkplain</span> Retrofit.Builder#addCallAdapterFactory(Factory) installed&#125; into the &#123;<span class="doctag">@link</span> Retrofit&#125;</span></span><br><span class="line"><span class="comment"> * instance.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">CallAdapter</span>&lt;R, T&gt; &#123;</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Returns the value type that this adapter uses when converting the HTTP response body to a Java</span></span><br><span class="line"><span class="comment">   * object. For example, the response type for &#123;<span class="doctag">@code</span> Call&lt;Repo&gt;&#125; is &#123;<span class="doctag">@code</span> Repo&#125;. This type</span></span><br><span class="line"><span class="comment">   * is used to prepare the &#123;<span class="doctag">@code</span> call&#125; passed to &#123;<span class="doctag">@code</span> #adapt&#125;.</span></span><br><span class="line"><span class="comment">   * &lt;p&gt;</span></span><br><span class="line"><span class="comment">   * Note: This is typically not the same type as the &#123;<span class="doctag">@code</span> returnType&#125; provided to this call</span></span><br><span class="line"><span class="comment">   * adapter&#x27;s factory.</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  Type <span class="title function_">responseType</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Returns an instance of &#123;<span class="doctag">@code</span> T&#125; which delegates to &#123;<span class="doctag">@code</span> call&#125;.</span></span><br><span class="line"><span class="comment">   * &lt;p&gt;</span></span><br><span class="line"><span class="comment">   * For example, given an instance for a hypothetical utility, &#123;<span class="doctag">@code</span> Async&#125;, this instance would</span></span><br><span class="line"><span class="comment">   * return a new &#123;<span class="doctag">@code</span> Async&lt;R&gt;&#125; which invoked &#123;<span class="doctag">@code</span> call&#125; when run.</span></span><br><span class="line"><span class="comment">   * &lt;pre&gt;&lt;code&gt;</span></span><br><span class="line"><span class="comment">   * &amp;#64;Override</span></span><br><span class="line"><span class="comment">   * public &amp;lt;R&amp;gt; Async&amp;lt;R&amp;gt; adapt(final Call&amp;lt;R&amp;gt; call) &#123;</span></span><br><span class="line"><span class="comment">   *   return Async.create(new Callable&amp;lt;Response&amp;lt;R&amp;gt;&amp;gt;() &#123;</span></span><br><span class="line"><span class="comment">   *     &amp;#64;Override</span></span><br><span class="line"><span class="comment">   *     public Response&amp;lt;R&amp;gt; call() throws Exception &#123;</span></span><br><span class="line"><span class="comment">   *       return call.execute();</span></span><br><span class="line"><span class="comment">   *     &#125;</span></span><br><span class="line"><span class="comment">   *   &#125;);</span></span><br><span class="line"><span class="comment">   * &#125;</span></span><br><span class="line"><span class="comment">   * &lt;/code&gt;&lt;/pre&gt;</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  T <span class="title function_">adapt</span><span class="params">(Call&lt;R&gt; call)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Creates &#123;<span class="doctag">@link</span> CallAdapter&#125; instances based on the return type of &#123;<span class="doctag">@linkplain</span></span></span><br><span class="line"><span class="comment">   * Retrofit#create(Class) the service interface&#125; methods.</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Factory</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Returns a call adapter for interface methods that return &#123;<span class="doctag">@code</span> returnType&#125;, or null if it</span></span><br><span class="line"><span class="comment">     * cannot be handled by this factory.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> CallAdapter&lt;?, ?&gt; get(Type returnType, Annotation[] annotations,</span><br><span class="line">        Retrofit retrofit);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Extract the upper bound of the generic parameter at &#123;<span class="doctag">@code</span> index&#125; from &#123;<span class="doctag">@code</span> type&#125;. For</span></span><br><span class="line"><span class="comment">     * example, index 1 of &#123;<span class="doctag">@code</span> Map&lt;String, ? extends Runnable&gt;&#125; returns &#123;<span class="doctag">@code</span> Runnable&#125;.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">static</span> Type <span class="title function_">getParameterUpperBound</span><span class="params">(<span class="type">int</span> index, ParameterizedType type)</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> Utils.getParameterUpperBound(index, type);</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="comment">     * Extract the raw class type from &#123;<span class="doctag">@code</span> type&#125;. For example, the type representing</span></span><br><span class="line"><span class="comment">     * &#123;<span class="doctag">@code</span> List&lt;? extends Runnable&gt;&#125; returns &#123;<span class="doctag">@code</span> List.class&#125;.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">static</span> Class&lt;?&gt; getRawType(Type type) &#123;</span><br><span class="line">      <span class="keyword">return</span> Utils.getRawType(type);</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>但是，别怕，泛型 R 代表是 ResponseBody，泛型 T 代表最终的转换类型，所以，我们的 CallAdapter 这么写：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">RxCallAdapterWrapper</span>&lt;R&gt; <span class="keyword">implements</span> <span class="title class_">CallAdapter</span>&lt;R, Observable&lt;?&gt;&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Retrofit mRetrofit;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> CallAdapter&lt;R, ?&gt; mWrappedCallAdapter;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// ...省略 N 行</span></span><br></pre></td></tr></table></figure><h4 id="CallAdapterFactory-需要做点儿什么"><a href="#CallAdapterFactory-需要做点儿什么" class="headerlink" title="CallAdapterFactory 需要做点儿什么"></a>CallAdapterFactory 需要做点儿什么</h4><p>其实需要做的工作很少：</p><ol><li>因为 <code>ResponseBody -&gt; Observable</code> 的工作 RxJavaCallAdapterFactory 已经做了，我们也没必要重新造轮子，包装它！</li><li>我们需要外部传入一个错误分发器。</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">RxErrorHandlerCallAdapterFactory</span> <span class="keyword">extends</span> <span class="title class_">CallAdapter</span>.Factory &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> RxJavaCallAdapterFactory mRxJavaCallAdapterFactory;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> RxErrorDispatcher mRxErrorDispatcher;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">RxErrorHandlerCallAdapterFactory</span><span class="params">(RxErrorDispatcher errorDispatcher)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mRxJavaCallAdapterFactory = RxJavaCallAdapterFactory.create();</span><br><span class="line">    mRxErrorDispatcher = errorDispatcher;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 省略 N 行</span></span><br></pre></td></tr></table></figure><p>OK，到这里，只需要把最后干活的 CallAdapter 写完，就已经完成大部分工作了，不废话，直接上代码：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">RxCallAdapterWrapper</span>&lt;R&gt; <span class="keyword">implements</span> <span class="title class_">CallAdapter</span>&lt;R, Observable&lt;?&gt;&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Retrofit mRetrofit;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> CallAdapter&lt;R, ?&gt; mWrappedCallAdapter;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> RxErrorDispatcher mRxErrorDispatcher;</span><br><span class="line"></span><br><span class="line">    RxCallAdapterWrapper(Retrofit retrofit, CallAdapter&lt;R, ?&gt; wrappedCallAdapter,</span><br><span class="line">        RxErrorDispatcher rxErrorDispatcher) &#123;</span><br><span class="line"></span><br><span class="line">      mRetrofit = retrofit;</span><br><span class="line">      mWrappedCallAdapter = wrappedCallAdapter;</span><br><span class="line">      mRxErrorDispatcher = rxErrorDispatcher;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span> <span class="keyword">public</span> Type <span class="title function_">responseType</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> mWrappedCallAdapter.responseType();</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="meta">@SuppressWarnings(&quot;unchecked&quot;)</span> <span class="meta">@Override</span> <span class="keyword">public</span> Observable&lt;?&gt; adapt(Call&lt;R&gt; call) &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> ((Observable) mWrappedCallAdapter.adapt(call)).onErrorResumeNext(</span><br><span class="line"></span><br><span class="line">          <span class="keyword">new</span> <span class="title class_">Func1</span>&lt;Throwable, Observable&gt;() &#123;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span> <span class="keyword">public</span> Observable <span class="title function_">call</span><span class="params">(Throwable throwable)</span> &#123;</span><br><span class="line"></span><br><span class="line">              <span class="keyword">return</span> Observable.error(mRxErrorDispatcher.dispatchError(throwable, mRetrofit));</span><br><span class="line">            &#125;</span><br><span class="line">          &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>我们关注最后一个方法，这里在 <code>((Observable) mWrappedCallAdapter.adapt(call))</code> 后，我们已经得到了 Observable，接上我们的 onErrorResumeNext 操作符，至于具体错误如何分发，就交给 mRxErrorDispatcher 吧！</p><h4 id="RxErrorDispatcher"><a href="#RxErrorDispatcher" class="headerlink" title="RxErrorDispatcher"></a>RxErrorDispatcher</h4><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">RxErrorDispatcher</span> &#123;</span><br><span class="line"></span><br><span class="line">  Throwable <span class="title function_">dispatchError</span><span class="params">(Throwable throwable, Retrofit retrofit)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>参数里为毛还有一个 Retrofit 呢？额，这里，如果你们的后端和 youyou 一样的话，应该是不需要的，而我这里有些历史遗留问题，所以…额</p><p>如果你仔细看过开头的流程图的话，会发现，如果错误产生，response.body() 返回的是 null，错误信息需要从 response.errorBody() 里取得，为什么会这样子呢，熟练地丢锅给后端…</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Rx%E9%94%99%E8%AF%AF%E6%8B%A6%E6%88%AA%E5%92%8C%E5%88%86%E5%8F%91/%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BC%9A%E8%BF%99%E6%A0%B7%E5%AD%90%E5%91%A2.jpg" alt="为什么会这样子呢"></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> &lt;T&gt; T <span class="title function_">getErrorBodyAs</span><span class="params">(Response response, Class&lt;T&gt; type, Retrofit retrofit)</span></span><br><span class="line">      <span class="keyword">throws</span> IOException &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (response == <span class="literal">null</span> || response.errorBody() == <span class="literal">null</span>) &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Converter&lt;ResponseBody, T&gt; converter = retrofit.responseBodyConverter(type, <span class="keyword">new</span> <span class="title class_">Annotation</span>[<span class="number">0</span>]);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> converter.convert(response.errorBody());</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>就这样，我多了一段这样的代码来解析<strong>服务器端</strong>返回的错误信息，我能怎么办，我也很绝望啊！</p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2>]]>
    </content>
    <id>https://hellovass.github.io/2017/04/15/Rx%20%E9%94%99%E8%AF%AF%E6%8B%A6%E6%88%AA%E5%92%8C%E5%88%86%E5%8F%91/</id>
    <link href="https://hellovass.github.io/2017/04/15/Rx%20%E9%94%99%E8%AF%AF%E6%8B%A6%E6%88%AA%E5%92%8C%E5%88%86%E5%8F%91/"/>
    <published>2017-04-15T13:57:46.000Z</published>
    <summary>
      <![CDATA[<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=434659672&auto=0&height=66"></iframe>

<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><blockquote>
<p>这感觉已经不对</p>
<p>我最后才了解</p>
<p>一页页不忍翻阅</p>
<p>的情节你好累</p>
</blockquote>
<p>这次要做的事是按照业务重构网络层的<strong>错误拦截和分发</strong>，仅以这段歌词献给两位前同事。</p>]]>
    </summary>
    <title>Rx 错误拦截和分发</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>这篇文章是关于自己从入<strong>守望先锋</strong>坑，到真正退坑这些天来的思考。</p><span id="more"></span><h2 id="辣鸡游戏"><a href="#辣鸡游戏" class="headerlink" title="辣鸡游戏"></a>辣鸡游戏</h2><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E8%BF%9F%E5%88%B0%E7%9A%84%E6%AD%A3%E4%B9%89%E4%B8%8D%E6%98%AF%E6%AD%A3%E4%B9%89/%E8%BF%99%E6%A0%B7%E7%9A%84%E6%B8%B8%E6%88%8F%E6%98%AF%E4%B8%8D%E4%BC%9A%E5%B8%A6%E6%9D%A5%E7%AC%91%E5%AE%B9%E7%9A%84.jpg" alt="这样的游戏是不会带来笑容的"></p><h2 id="占坑待填"><a href="#占坑待填" class="headerlink" title="占坑待填"></a>占坑待填</h2>]]>
    </content>
    <id>https://hellovass.github.io/2017/04/15/%E8%BF%9F%E5%88%B0%E7%9A%84%E6%AD%A3%E4%B9%89%E4%B8%8D%E6%98%AF%E6%AD%A3%E4%B9%89/</id>
    <link href="https://hellovass.github.io/2017/04/15/%E8%BF%9F%E5%88%B0%E7%9A%84%E6%AD%A3%E4%B9%89%E4%B8%8D%E6%98%AF%E6%AD%A3%E4%B9%89/"/>
    <published>2017-04-15T04:49:14.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>这篇文章是关于自己从入<strong>守望先锋</strong>坑，到真正退坑这些天来的思考。</p>]]>
    </summary>
    <title>迟到的正义不是正义</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%86%8D%E7%9C%8B%E9%BE%99%E4%B8%8E%E8%99%8E/%E9%BE%99%E4%B8%8E%E8%99%8E.png" alt="龙与虎"></p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><blockquote><p>世界上有任何人都没有见过的东西 </p><p>它很温柔 非常甜美 </p><p>大概 如果可以看见的话 </p><p>谁都会想要的吧 </p><p>正因为如此 </p><p>才会谁也没有见过它 </p><p>为了让人无法那么轻易地得到 </p><p>世界才把它藏了起来 </p><p>但是总有一天会有人找到 </p><p>应该得到的那个唯一的人 </p><p>一定能把它找出来</p></blockquote><p>出自《龙与虎》第一集片头和第二十五集片尾</p><span id="more"></span><h2 id="这是怎样的一个故事"><a href="#这是怎样的一个故事" class="headerlink" title="这是怎样的一个故事"></a>这是怎样的一个故事</h2><h3 id="很有趣"><a href="#很有趣" class="headerlink" title="很有趣"></a>很有趣</h3><p>原本<strong>男主</strong>和<strong>女主</strong>并没有擦出什么<strong>恋爱的火花</strong>，而是对彼此的<strong>基（姬）友</strong>感兴趣♂，并因此<strong>达成共识</strong>，统一了<strong>龙与虎战线</strong>。而最后，<strong>男主</strong>和<strong>女主</strong>才发现自己真正喜欢的是对方。虽然，故事的大体框架有点儿老套，但是，这条<strong>感情线</strong>的转变却不突兀，让屏幕前的单身狗含着幸福的眼泪吞下了狗粮…</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%86%8D%E7%9C%8B%E9%BE%99%E4%B8%8E%E8%99%8E/%E5%A4%9A%E4%B9%88%E7%BE%8E%E5%A6%99.jpg" alt="多么美妙"></p><h3 id="那编剧怎么把他俩凑一起呢"><a href="#那编剧怎么把他俩凑一起呢" class="headerlink" title="那编剧怎么把他俩凑一起呢"></a>那编剧怎么把他俩凑一起呢</h3><p>那当然是选择原谅<strong>她&#x2F;他</strong>咯（滑稽）</p><p>开个玩笑，当然是确认自己的心意后，和<strong>他&#x2F;她</strong>在一起咯。</p><h4 id="大河线"><a href="#大河线" class="headerlink" title="大河线"></a>大河线</h4><p>刚开始，我对<strong>大河</strong>的印象停留在一个很可爱、很任性的妹纸上，看不出她有什么很吸引人的地方。慢慢的，随着故事进展，当我知道<strong>大河</strong>为什么孤零零住在那间公寓时，我又开始同情这只<strong>萌虎</strong>。表面上<strong>猛虎</strong>无坚不摧，是一个强势有钱的妹纸，实际上，她只不过是一个渴望得到家人关爱、渴望被别人理解、渴望一场普普通通恋爱的女孩。对了，这个故事的暗线就是<strong>主角与家人的亲情</strong>。</p><p>圣诞节到了，<strong>大家都懂得，自古圣诞搞事情</strong>，做好了进入<strong>白学现场</strong>的打算，我进入了这一段的剧情。从这一段开始，编剧开始带我们真正认识大河了。这是一个非常有同情心的好孩子，以<strong>圣诞老人的名义</strong>买礼物送给福利院的孩子们，只是希望他们相信<strong>这个世界</strong>还是有人关爱他们的。当她最后察觉到自己的<strong>真实心意</strong>，发现自己已经无可救药的喜欢上了这个一直在扮演自己父亲角色的<strong>高须龙儿</strong>时，想到自己将要失去龙儿时，一直坚强的大河终于泪流满面，大声呼喊龙儿的名字…</p><p>圣诞，是四个人的失恋…可以，这很白！</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%86%8D%E7%9C%8B%E9%BE%99%E4%B8%8E%E8%99%8E/%E7%99%BD%E5%AD%A6.jpg" alt="白学"></p><p>当大河明白挚友<strong>小实</strong>的心意（其实是喜欢龙儿的），她竭尽自己所能去独立，<strong>想要抹去和龙儿整整 20 集的回忆</strong>，<strong>想要离开照顾了她整整 20 集的龙儿</strong>，她不过是希望挚友能获得幸福…牛逼的是，闺蜜<strong>小实</strong>也是抱着让<strong>大河</strong>幸福的想法而躲开自己喜欢的<strong>高须龙儿</strong>。啥都不说了，我默默烧掉了手中的<strong>白色相簿</strong>…</p><h4 id="龙儿线"><a href="#龙儿线" class="headerlink" title="龙儿线"></a>龙儿线</h4><p>龙儿的<strong>凶恶眼神</strong>和<strong>发色</strong>我们就不吐槽了，毕竟都是<strong>继承</strong>过来的，他能怎办，他也很绝望啊！但是，龙儿的善良、责任感、料理、清洁功力，我给 666 好吧！</p><p>龙儿，前期都是以坚强能干的姿态走过来的，直到<strong>麻麻泰子</strong>因<strong>工作过度劳累</strong>晕倒产生了<strong>动摇</strong>。这里，龙儿<strong>将自己内心的柔弱一面</strong>暴露了出来，他不是一个万能的人，只是一个对未来充满迷茫的16岁普通高中生，需要别人的关怀和依靠。</p><p>就在这时，这里动画的表现手法非常巧妙，大河紧紧拉住了龙儿，就像安慰小孩子般地安慰<strong>龙儿</strong>，“龙儿，没事的…没事的…”，然后画面切换到<strong>龙儿想起泰子安慰小时候哭泣的自己</strong>。这次，<strong>龙儿</strong>从<strong>大河</strong>这个被照顾了 20 集的问题女孩身上感受到了关爱。</p><p>两颗心在冬夜的寒风中依偎…</p><h4 id="助攻线"><a href="#助攻线" class="headerlink" title="助攻线"></a>助攻线</h4><p>国家欠基（姬）友系列，这里剧本也处理地非常好，配角只能做自己能做的事情——那就是引导主角做出自己的选择。</p><blockquote><p>至于最后能留下她的只有她期望的那个人。</p></blockquote><h2 id="也很难得"><a href="#也很难得" class="headerlink" title="也很难得"></a>也很难得</h2><p>这个故事还是现实的。</p><p>最后<strong>龙儿</strong>与<strong>大河</strong>并没有做出什么<strong>私奔</strong>的举动，而是趁机把<strong>泰子</strong>诱骗到多年不见的亲生父母家，使家人重聚，冰释前嫌。大河也在这件事情中明白了<strong>家人</strong>一词的力量，决定回到自己的亲生母亲那里。</p><p>两人约定——两个人的幸福不是真正的幸福，大家的幸福才是真正的幸福，虽然花费的时间要长一点，但，那才是最幸福的。</p><p>最后，也没有辜负众多单身狗们的期望，大河与母亲重归于好，亲情线作为本作的<strong>暗线</strong>也打出了 gg！！！</p><h2 id="毕业典礼"><a href="#毕业典礼" class="headerlink" title="毕业典礼"></a>毕业典礼</h2><p>一年后，稍带伤感的毕业典礼上，龙儿似乎看到了<strong>2-C班</strong>有某个熟悉的身影</p><p>“急促的脚步声…”</p><p>“…”</p><p>“熟练地走到那个柜子面前”</p><p>“我喜欢你…”</p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><blockquote><p>那会是什么呢？</p><p>夏夜里</p><p>闷热的空气中，想象着</p><p>窗外天空已经消失了的星空</p><p>寻找着属于我的那一颗很小很小</p><p>却又拼命发出光芒的星星</p></blockquote><h2 id="橙色少女心"><a href="#橙色少女心" class="headerlink" title="橙色少女心"></a>橙色少女心</h2><p><embed height="415" width="544" quality="high" allowfullscreen="true" type="application/x-shockwave-flash" src="//static.hdslb.com/miniloader.swf" flashvars="aid=8406004&page=1" pluginspage="//www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash"></embed></p>]]>
    </content>
    <id>https://hellovass.github.io/2017/04/14/%E5%86%8D%E7%9C%8B%E3%80%8E%E9%BE%99%E4%B8%8E%E8%99%8E%E3%80%8F/</id>
    <link href="https://hellovass.github.io/2017/04/14/%E5%86%8D%E7%9C%8B%E3%80%8E%E9%BE%99%E4%B8%8E%E8%99%8E%E3%80%8F/"/>
    <published>2017-04-14T04:44:14.000Z</published>
    <summary>
      <![CDATA[<p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%86%8D%E7%9C%8B%E9%BE%99%E4%B8%8E%E8%99%8E/%E9%BE%99%E4%B8%8E%E8%99%8E.png" alt="龙与虎"></p>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><blockquote>
<p>世界上有任何人都没有见过的东西 </p>
<p>它很温柔 非常甜美 </p>
<p>大概 如果可以看见的话 </p>
<p>谁都会想要的吧 </p>
<p>正因为如此 </p>
<p>才会谁也没有见过它 </p>
<p>为了让人无法那么轻易地得到 </p>
<p>世界才把它藏了起来 </p>
<p>但是总有一天会有人找到 </p>
<p>应该得到的那个唯一的人 </p>
<p>一定能把它找出来</p>
</blockquote>
<p>出自《龙与虎》第一集片头和第二十五集片尾</p>]]>
    </summary>
    <title>再看『龙与虎』</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Android" scheme="https://hellovass.github.io/tags/Android/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>感慨：Retrofit2 虽好，但是，有时候总感觉 Java 这门语言还是美中不足啊！</p><h2 id="恼人的问题"><a href="#恼人的问题" class="headerlink" title="恼人的问题"></a>恼人的问题</h2><p>想象一下，有这么几个 Api：</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 获取手机验证码</span></span><br><span class="line"><span class="meta">@POST(&quot;/user/sms&quot;)</span> Observable&lt;ServerResult&lt;SmsCode&gt;&gt; <span class="title function_">fetchSmsCode</span><span class="params">(<span class="meta">@Body</span> PhoneNum phoneNum)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取 AuthCode</span></span><br><span class="line"><span class="meta">@POST(&quot;/authcode&quot;)</span> Observable&lt;ServerResult&lt;AuthCode&gt;&gt; <span class="title function_">fetchAuthCode</span><span class="params">(<span class="meta">@Body</span> SmsCodeAndOtherParams smsCodeAndOtherParams)</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><span id="more"></span><h3 id="为什么很烦呢？"><a href="#为什么很烦呢？" class="headerlink" title="为什么很烦呢？"></a>为什么很烦呢？</h3><h4 id="需要起名字"><a href="#需要起名字" class="headerlink" title="需要起名字"></a>需要起名字</h4><p>因为需要用 Gson 解析来解析 json，所以我们需要按照 server 返回的 json 来定义我们的<strong>请求体</strong>（被 <code>@Body</code> 注解的参数）以及<strong>响应体</strong>。然而，我这人最烦的就是命名了，本来词汇量就匮乏，还要想名字？</p><h4 id="需要新建-class"><a href="#需要新建-class" class="headerlink" title="需要新建 class"></a>需要新建 class</h4><p>上面两个 Api，我们需要定义 SmsCode、PhoneNum、AuthCode、SmsCodeAndOtherParams 四个类，然后在类里按照 json 的 key 定义对应的成员变量。</p><h4 id="调用-Api-的时候，代码好脏啊"><a href="#调用-Api-的时候，代码好脏啊" class="headerlink" title="调用 Api 的时候，代码好脏啊"></a>调用 Api 的时候，代码好脏啊</h4><p>我们需要 new 参数，举栗：调用 fetchSmsCode 前，我们需要 <code>new PhoneNum()</code> 并且给它的成员变量赋值，当然如果我们是爱干净的好孩子的话可能会用 Builder 模式来做，但是谁来写这么多的 builder 呢？这可是体力活啊</p><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><h4 id="问个问题，如果是-JS-的话，这段代码该怎么写呢？"><a href="#问个问题，如果是-JS-的话，这段代码该怎么写呢？" class="headerlink" title="问个问题，如果是 JS 的话，这段代码该怎么写呢？"></a>问个问题，如果是 JS 的话，这段代码该怎么写呢？</h4><p>我想，大概是这样的：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> smsCode = <span class="title function_">etchSmsCode</span>(&#123;<span class="string">&quot;phoneNumber&quot;</span>:<span class="string">&quot;159XXXXXXXX&quot;</span>&#125;)</span><br></pre></td></tr></table></figure><p>哇，真爽，不用新建 class，不用费力想名字，毕竟我们做 POST 请求的时候，只是想 POST 一个匿名的 JsonObject 而已，新建 class 什么的，真的没必要。</p><h4 id="理想的-Api"><a href="#理想的-Api" class="headerlink" title="理想的 Api"></a>理想的 Api</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 获取验证码</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="meta">@POST(&quot;/user/sms&quot;)</span> Observable&lt;Response&lt;ServerResult&lt;Params&gt;&gt;&gt; <span class="title function_">fetchSmsCode</span><span class="params">(</span></span><br><span class="line"><span class="params">       <span class="meta">@Body</span> Params params)</span>;</span><br><span class="line"></span><br><span class="line">   <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 获取 AuthCode</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="meta">@POST(&quot;/authcode&quot;)</span> Observable&lt;Response&lt;ServerResult&lt;Params&gt;&gt;&gt; <span class="title function_">fetchAuthCode</span><span class="params">(</span></span><br><span class="line"><span class="params">       <span class="meta">@Body</span> Params params)</span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 获取验证码</span></span><br><span class="line">PeroRetrofitWrapperStore.getInstance()</span><br><span class="line">        .provideApi(PeroApi.UserApi.class)</span><br><span class="line">        .fetchSmsCode(<span class="keyword">new</span> <span class="title class_">Params</span>.Builder().put(<span class="string">&quot;phoneNumber&quot;</span>, phoneNum).create())</span><br><span class="line">        <span class="comment">// ... 省略若干代码</span></span><br></pre></td></tr></table></figure><p>虽然没有 JS 那么简洁，但是看起来已经没有那么恼人了。那么问题来了，该怎么魔改，实现这样的愿望呢？</p><h4 id="利用-TypeAdapterFactory-这个接口"><a href="#利用-TypeAdapterFactory-这个接口" class="headerlink" title="利用 TypeAdapterFactory 这个接口"></a>利用 TypeAdapterFactory 这个接口</h4><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">@Override</span> <span class="keyword">public</span> Converter.Factory <span class="title function_">createGsonConverterFactory</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">Gson</span> <span class="variable">gson</span> <span class="operator">=</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">GsonBuilder</span>().registerTypeAdapterFactory(provideParamsAdapterFactory()).create();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> GsonConverterFactory.create(gson);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>这段代码里，我创建了一个 <code>ParamsAdapterFactory</code> （继承自 TypeAdapterFactory），并注册到了 GsonBuilder 中。那这有什么用呢？其实非常有用，<code>TypeAdapterFactory</code> 内部会创建一个 <code>ParamsAdapter</code>，接管了 json 的序列化和反序列化！</p><p>来看看我们的 <code>ParamsAdapter</code> 做了什么？代码不长，挑重要的分析下。因为我们接管了 json 的序列化过程，在执行 POST 请求的时候，<code>fetchSmsCode(@Body parmas)</code> 方法里接受的 params 参数就会走这段代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">   <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">write</span><span class="params">(JsonWriter jsonWriter, Params params)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line"></span><br><span class="line">     <span class="keyword">if</span> (params == <span class="literal">null</span>) &#123;</span><br><span class="line"></span><br><span class="line">       jsonWriter.nullValue();</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">     jsonWriter.beginObject();</span><br><span class="line"></span><br><span class="line">     Map&lt;String, String&gt; map = params.getExtrasMap();</span><br><span class="line"></span><br><span class="line">     <span class="keyword">for</span> (String key : map.keySet()) &#123;</span><br><span class="line"></span><br><span class="line">       jsonWriter.name(key);</span><br><span class="line">       jsonWriter.value(map.get(key));</span><br><span class="line">     &#125;</span><br><span class="line"></span><br><span class="line">     jsonWriter.endObject();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其实也就是序列化过程，最后 params 会转换成我们想要的 <code>{&quot;phoneNumber&quot;:&quot;159XXXXXXX&quot;}</code> json 格式 ，接下来的事情就交给 Retrofit（像往常一样）。</p><p>同理可得，反序列化过程，就是将服务器返回的 json 解析为，我们期望的 params，代码如下：</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><br><span class="line"><span class="meta">@Override</span> <span class="keyword">public</span> Params <span class="title function_">read</span><span class="params">(JsonReader jsonReader)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (jsonReader.peek() == JsonToken.NULL) &#123;</span><br><span class="line"></span><br><span class="line">        jsonReader.nextNull();</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      Params.<span class="type">Builder</span> <span class="variable">builder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Params</span>.Builder();</span><br><span class="line"></span><br><span class="line">      jsonReader.beginObject();</span><br><span class="line"></span><br><span class="line">      <span class="keyword">while</span> (jsonReader.hasNext()) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> jsonReader.nextName();</span><br><span class="line">        <span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> jsonReader.nextString();</span><br><span class="line"></span><br><span class="line">        builder.put(key, value);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      jsonReader.endObject();</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> builder.create();</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>反序列化完毕之后，我们就可以从 params 里拿到自己想要的值，例如：<code>String authCode = params.get(&quot;authCode&quot;)</code>，这里的 Params 就相当于一个 Map，事实上它内部确实是用一个 Map&lt;String,String&gt; 来存取键值对的。</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Params</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> Map&lt;String, String&gt; mExtrasMap;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">Params</span><span class="params">(Builder builder)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mExtrasMap = builder.mExtrasMap;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> String <span class="title function_">get</span><span class="params">(String key)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> mExtrasMap.get(key);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> Map&lt;String, String&gt; <span class="title function_">getExtrasMap</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> mExtrasMap;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;Params&#123;&quot;</span> + <span class="string">&quot;mExtrasMap=&quot;</span> + mExtrasMap + <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Builder</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Map&lt;String, String&gt; mExtrasMap;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Builder</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">      mExtrasMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Builder <span class="title function_">put</span><span class="params">(String key, String value)</span> &#123;</span><br><span class="line"></span><br><span class="line">      mExtrasMap.put(key, value);</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Params <span class="title function_">create</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Params</span>(<span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>代码灰常简单，对不对？到这里，魔改原理就差不多解释清楚了。</p><p>当然，可能会有人质疑，那 up 你的意思是劳资不用自己费力写 POJO，全用你的 Params 来替代？（如果我回答不是，你会不会一棒子打过来？）</p><h5 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h5><p>这个，我一开始也没提。如果到了不是非常有必要定义 POJO 的时候，比如，你只是想要 POST 一个 phoneNumber 或者 authCode 的时候，真的没必要为此定义 POJO，多累呢！遇到这种情况，能创建一个匿名的 params 就创建呗，<strong>省下来的时间还能陪陪学妹</strong>，何乐而不为！</p><p>也有童鞋会说，老板，你把序列化和反序列化全部接管了，如果我没有用你的 Params ，会不会解析异常？（这点我肯定考虑到了好吧，不然菊花肯定都没了）</p><h5 id="类型检查"><a href="#类型检查" class="headerlink" title="类型检查"></a>类型检查</h5><p>这里我们判断 rawType，如果是 Params 的话，就返回我们的 ParamsAdaper，否则返回 null，表示不支持。</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">@Override</span> <span class="keyword">public</span> &lt;T&gt; TypeAdapter&lt;T&gt; <span class="title function_">create</span><span class="params">(Gson gson, TypeToken&lt;T&gt; typeToken)</span> &#123;</span><br><span class="line"></span><br><span class="line">    Class&lt;T&gt; rawType = (Class&lt;T&gt;) typeToken.getRawType();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (Params.class.isAssignableFrom(rawType)) &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> (TypeAdapter&lt;T&gt;) <span class="keyword">new</span> <span class="title class_">ParamsAdapter</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><h4 id="完整的-TypeAdapterFactory-代码"><a href="#完整的-TypeAdapterFactory-代码" class="headerlink" title="完整的 TypeAdapterFactory 代码"></a>完整的 TypeAdapterFactory 代码</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><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ParamsAdapterFactory</span></span><br><span class="line">    <span class="keyword">implements</span> <span class="title class_">TypeAdapterFactory</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> &lt;T&gt; TypeAdapter&lt;T&gt; <span class="title function_">create</span><span class="params">(Gson gson, TypeToken&lt;T&gt; typeToken)</span> &#123;</span><br><span class="line"></span><br><span class="line">    Class&lt;T&gt; rawType = (Class&lt;T&gt;) typeToken.getRawType();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (Params.class.isAssignableFrom(rawType)) &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> (TypeAdapter&lt;T&gt;) <span class="keyword">new</span> <span class="title class_">ParamsAdapter</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ParamsAdapter</span> <span class="keyword">extends</span> <span class="title class_">TypeAdapter</span>&lt;Params&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">write</span><span class="params">(JsonWriter jsonWriter, Params params)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (params == <span class="literal">null</span>) &#123;</span><br><span class="line"></span><br><span class="line">        jsonWriter.nullValue();</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">      jsonWriter.beginObject();</span><br><span class="line"></span><br><span class="line">      Map&lt;String, String&gt; map = params.getExtrasMap();</span><br><span class="line"></span><br><span class="line">      <span class="keyword">for</span> (String key : map.keySet()) &#123;</span><br><span class="line"></span><br><span class="line">        jsonWriter.name(key);</span><br><span class="line">        jsonWriter.value(map.get(key));</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      jsonWriter.endObject();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span> <span class="keyword">public</span> Params <span class="title function_">read</span><span class="params">(JsonReader jsonReader)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (jsonReader.peek() == JsonToken.NULL) &#123;</span><br><span class="line"></span><br><span class="line">        jsonReader.nextNull();</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      Params.<span class="type">Builder</span> <span class="variable">builder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Params</span>.Builder();</span><br><span class="line"></span><br><span class="line">      jsonReader.beginObject();</span><br><span class="line"></span><br><span class="line">      <span class="keyword">while</span> (jsonReader.hasNext()) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> jsonReader.nextName();</span><br><span class="line">        <span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> jsonReader.nextString();</span><br><span class="line"></span><br><span class="line">        builder.put(key, value);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      jsonReader.endObject();</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> builder.create();</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><blockquote><p>PS：灵感来自 <a href="https://github.com/rharter/auto-value-gson">auto-value-factory</a>。</p></blockquote>]]>
    </content>
    <id>https://hellovass.github.io/2017/04/09/%E9%AD%94%E6%94%B9%20TypeAdapterFactory/</id>
    <link href="https://hellovass.github.io/2017/04/09/%E9%AD%94%E6%94%B9%20TypeAdapterFactory/"/>
    <published>2017-04-09T12:18:13.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>感慨：Retrofit2 虽好，但是，有时候总感觉 Java 这门语言还是美中不足啊！</p>
<h2 id="恼人的问题"><a href="#恼人的问题" class="headerlink" title="恼人的问题"></a>恼人的问题</h2><p>想象一下，有这么几个 Api：</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 获取手机验证码</span></span><br><span class="line"><span class="meta">@POST(&quot;/user/sms&quot;)</span> Observable&lt;ServerResult&lt;SmsCode&gt;&gt; <span class="title function_">fetchSmsCode</span><span class="params">(<span class="meta">@Body</span> PhoneNum phoneNum)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取 AuthCode</span></span><br><span class="line"><span class="meta">@POST(&quot;/authcode&quot;)</span> Observable&lt;ServerResult&lt;AuthCode&gt;&gt; <span class="title function_">fetchAuthCode</span><span class="params">(<span class="meta">@Body</span> SmsCodeAndOtherParams smsCodeAndOtherParams)</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>]]>
    </summary>
    <title>魔改 TypeAdapterFactory</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Android" scheme="https://hellovass.github.io/tags/Android/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>将构造 <code>Retrofit</code> 时所需要的材料隔离开来，利用<strong>依赖倒置</strong>这个原则，优雅地烘烤出美味的 <code>Retrofit</code> 实例。</p><h2 id="类图"><a href="#类图" class="headerlink" title="类图"></a>类图</h2><p>来自《Head First 设计模式》抽象工厂一节中<strong>披萨店卖披萨</strong>的启发，我们也将 <code>Retrofit</code> 看做一样商品，来设计它。</p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E7%83%98%E7%84%99Retrofit/%E4%BE%9D%E8%B5%96%E5%80%92%E7%BD%AE.png" alt="依赖倒置"></p><span id="more"></span><h3 id="RetrofitWrapper"><a href="#RetrofitWrapper" class="headerlink" title="RetrofitWrapper"></a>RetrofitWrapper</h3><p>我们需要的产品，内部包含了一个 <code>Retrofit</code> 的引用。我们将这个类抽象，构造 <code>Retrofit</code> 的原料来自另一个抽象类：<code>RetrofitWrapperFactory</code>，代码如下：</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 产品类，包装了一个 Retrofit</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">RetrofitWrapper</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">protected</span> Retrofit mRetrofit;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">RetrofitWrapper</span><span class="params">(RetrofitWrapperFactory retrofitWrapperFactory)</span> &#123;</span><br><span class="line"></span><br><span class="line">    mRetrofit = <span class="keyword">new</span> <span class="title class_">Retrofit</span>.Builder().baseUrl(retrofitWrapperFactory.provideBaseUrl())</span><br><span class="line">        .client(retrofitWrapperFactory.createOkHttpClient()) <span class="comment">// 提供 OkHttp 实例</span></span><br><span class="line">        .addConverterFactory(retrofitWrapperFactory.createGsonConverterFactory()) <span class="comment">// 提供 GsonConvertFactory 实例</span></span><br><span class="line">        .addCallAdapterFactory(retrofitWrapperFactory.createRxJavaCallAdapter()) <span class="comment">// 提供RxJavaCallAdapter 的实例</span></span><br><span class="line">        .build();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> Retrofit <span class="title function_">getRetrofit</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> mRetrofit;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="RetrofitWrapperFactory"><a href="#RetrofitWrapperFactory" class="headerlink" title="RetrofitWrapperFactory"></a>RetrofitWrapperFactory</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * RetrofitWrapper 抽象工厂，提供 RetrofitWrapper 需要的原料</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">RetrofitWrapperFactory</span> &#123;</span><br><span class="line"></span><br><span class="line">  String <span class="title function_">provideBaseUrl</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">  OkHttpClient <span class="title function_">createOkHttpClient</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">  Converter.Factory <span class="title function_">createGsonConverterFactory</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">  CallAdapter.Factory <span class="title function_">createRxJavaCallAdapter</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="RetrofitWrapperStore"><a href="#RetrofitWrapperStore" class="headerlink" title="RetrofitWrapperStore"></a>RetrofitWrapperStore</h3><p>兜售 <code>RetrofitWrapper</code> 的商店，这里商店类还继承了一个 <code>RetrofitApiProvider</code> 接口，用来对外暴露创建 <code>Retrofit Api Service</code> 的接口。</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * RetrofitWrapper 商店类</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">RetrofitWrapperStore</span> <span class="keyword">implements</span> <span class="title class_">RetrofitApiProvider</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">protected</span> <span class="keyword">abstract</span> RetrofitWrapper <span class="title function_">createRetrofitWrapper</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">RetrofitApiProvider</span> &#123;</span><br><span class="line"></span><br><span class="line">  &lt;T&gt; T <span class="title function_">provideApi</span><span class="params">(Class&lt;T&gt; service)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="举栗时间"><a href="#举栗时间" class="headerlink" title="举栗时间"></a>举栗时间</h2><h3 id="具体产品类"><a href="#具体产品类" class="headerlink" title="具体产品类"></a>具体产品类</h3><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PeroRetrofitWrapper</span> <span class="keyword">extends</span> <span class="title class_">RetrofitWrapper</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">PeroRetrofitWrapper</span><span class="params">(RetrofitWrapperFactory retrofitWrapperFactory)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(retrofitWrapperFactory);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="具体工厂类"><a href="#具体工厂类" class="headerlink" title="具体工厂类"></a>具体工厂类</h3><figure class="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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PeroRetrofitWrapperFactory</span> <span class="keyword">implements</span> <span class="title class_">RetrofitWrapperFactory</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">DEFAULT_CONNECT_TIME_OUT</span> <span class="operator">=</span> <span class="number">10</span>; <span class="comment">// 默认超时时间</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">TEST_SERVER_URL</span> <span class="operator">=</span> <span class="string">&quot;http://192.168.199.182:3000&quot;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> OkHttpClient <span class="title function_">createOkHttpClient</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">OkHttpClient</span>.Builder().connectTimeout(DEFAULT_CONNECT_TIME_OUT, TimeUnit.SECONDS)</span><br><span class="line">        .addInterceptor(provideHttpLoggingInterceptor())</span><br><span class="line">        .addNetworkInterceptor(provideTokenInterceptor())</span><br><span class="line">        .build();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> Interceptor <span class="title function_">provideHttpLoggingInterceptor</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">HttpLoggingInterceptor</span> <span class="variable">httpLoggingInterceptor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HttpLoggingInterceptor</span>();</span><br><span class="line">    httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> httpLoggingInterceptor;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> Interceptor <span class="title function_">provideTokenInterceptor</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">TokenInterceptor</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> CallAdapter.Factory <span class="title function_">createRxJavaCallAdapter</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> RxJavaCallAdapterFactory.create();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> Converter.Factory <span class="title function_">createGsonConverterFactory</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">Gson</span> <span class="variable">gson</span> <span class="operator">=</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">GsonBuilder</span>().registerTypeAdapterFactory(provideParamsAdapterFactory()).create();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> GsonConverterFactory.create(gson);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> TypeAdapterFactory <span class="title function_">provideParamsAdapterFactory</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ParamsAdapterFactory</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> String <span class="title function_">provideBaseUrl</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> TEST_SERVER_URL;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="具体商店类"><a href="#具体商店类" class="headerlink" title="具体商店类"></a>具体商店类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">PeroRetrofitWrapperStore</span> <span class="keyword">extends</span> <span class="title class_">RetrofitWrapperStore</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> RetrofitWrapper mRetrofitWrapper;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">PeroRetrofitWrapperStore</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    mRetrofitWrapper = createRetrofitWrapper();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">SingletonHolder</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">PeroRetrofitWrapperStore</span> <span class="variable">sInstance</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PeroRetrofitWrapperStore</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">static</span> PeroRetrofitWrapperStore <span class="title function_">getInstance</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> SingletonHolder.sInstance;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">protected</span> RetrofitWrapper <span class="title function_">createRetrofitWrapper</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">PeroRetrofitWrapper</span>(<span class="keyword">new</span> <span class="title class_">PeroRetrofitWrapperFactory</span>());</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> &lt;T&gt; T <span class="title function_">provideApi</span><span class="params">(Class&lt;T&gt; service)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> mRetrofitWrapper.getRetrofit().create(service);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="如何调用？"><a href="#如何调用？" class="headerlink" title="如何调用？"></a>如何调用？</h3><p>在实际项目中，<code>PeroRetrofitWrapper</code> 和 <code>PeroRetrofitWrapperFactory</code> 可以作为 <code>PeroRetrofitWrapperStore</code> 的静态内部类，从而屏蔽外部可见性。毕竟，现实生活中，我们买披萨也是去<strong>商店</strong>购买，而不会跑到<strong>原料工厂</strong>自己动手造披萨对吧2333。</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></pre></td><td class="code"><pre><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">void</span> <span class="title function_">fetchPinCode</span><span class="params">(<span class="keyword">final</span> String phoneNum)</span> &#123;</span><br><span class="line"></span><br><span class="line">  PeroRetrofitWrapperStore.getInstance()</span><br><span class="line">      .provideApi(PeroApi.UserApi.class)</span><br><span class="line">      .fetchSmsCode(<span class="keyword">new</span> <span class="title class_">Params</span>.Builder().put(<span class="string">&quot;phoneNumber&quot;</span>, phoneNum).create())</span><br><span class="line">      <span class="comment">// 省略...</span></span><br></pre></td></tr></table></figure><h2 id="思考-改进"><a href="#思考-改进" class="headerlink" title="思考&amp;改进"></a>思考&amp;改进</h2><p>示例代码里已经将 <code>RetrofitWrapper</code> 单例化了，而创建 <code>Service Api</code>，其实还是在 <code>new object</code>。</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">@Override</span> <span class="keyword">public</span> &lt;T&gt; T <span class="title function_">provideApi</span><span class="params">(Class&lt;T&gt; service)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> mRetrofitWrapper.getRetrofit().create(service);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>如果有需要，也可以将 <code>Api Service</code> 进行单例化。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>有 <code>new</code> 关键字的地方我们就应该谨慎，是否<strong>耦合</strong>得太厉害，是否可以用工厂模式来解耦。当然，因为我的项目里暂时还没有 <code> Dagger2</code>，所以自己动手，实现解耦，如果有 <code>Dagger2</code> 的话，就不用这么麻烦了233。</p>]]>
    </content>
    <id>https://hellovass.github.io/2017/04/07/%E4%BC%98%E9%9B%85%E5%9C%B0%E7%83%98%E7%83%A4%20Retrofit/</id>
    <link href="https://hellovass.github.io/2017/04/07/%E4%BC%98%E9%9B%85%E5%9C%B0%E7%83%98%E7%83%A4%20Retrofit/"/>
    <published>2017-04-07T06:19:18.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>将构造 <code>Retrofit</code> 时所需要的材料隔离开来，利用<strong>依赖倒置</strong>这个原则，优雅地烘烤出美味的 <code>Retrofit</code> 实例。</p>
<h2 id="类图"><a href="#类图" class="headerlink" title="类图"></a>类图</h2><p>来自《Head First 设计模式》抽象工厂一节中<strong>披萨店卖披萨</strong>的启发，我们也将 <code>Retrofit</code> 看做一样商品，来设计它。</p>
<p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E7%83%98%E7%84%99Retrofit/%E4%BE%9D%E8%B5%96%E5%80%92%E7%BD%AE.png" alt="依赖倒置"></p>]]>
    </summary>
    <title>优雅地烘焙 Retrofit</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>了解 Go 语言的特性，尽量轻松地掌握这门简洁、有趣却又强大的新语言。</p><span id="more"></span><h2 id="feature"><a href="#feature" class="headerlink" title="feature"></a>feature</h2><h3 id="函数定义"><a href="#函数定义" class="headerlink" title="函数定义"></a>函数定义</h3><p>举栗，一个加法函数：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;errors&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Add</span><span class="params">(a <span class="type">int</span>, b <span class="type">int</span>)</span></span> (result <span class="type">int</span>, <span class="type">error</span> <span class="type">error</span>) &#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> a &lt; <span class="number">0</span> || b &lt; <span class="number">0</span> &#123;</span><br><span class="line"><span class="type">error</span> = errors.New(<span class="string">&quot;参数不能为负数&quot;</span>)</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="keyword">return</span> a + b, <span class="literal">nil</span> <span class="comment">// 支持多重返回</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>如果<strong>参数列表</strong>中若干个响铃的参数类型相同，比如上述栗子中的 a,b，则可以省略前面变量的<strong>类型声明</strong>，举栗：</p><figure class="highlight go"><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"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Add</span><span class="params">(a, b <span class="type">int</span>)</span></span> (result <span class="type">int</span>, <span class="type">error</span> <span class="type">error</span>) &#123;</span><br><span class="line"><span class="comment">//...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果返回值列表中多个返回值的类型相同，也可以用相同方式合并。<br>如果函数只有一个返回值，也可以这么写：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Add</span><span class="params">(a, b <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="comment">//...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="函数调用"><a href="#函数调用" class="headerlink" title="函数调用"></a>函数调用</h3><blockquote><p>Note: 小写字母开头的函数只在本包内可见，大写字母开头的函数才能被其他包使用。<br><strong>这个规则也适用于类型和变量</strong>。</p></blockquote><h3 id="不定参数"><a href="#不定参数" class="headerlink" title="不定参数"></a>不定参数</h3><h4 id="不定参数类型"><a href="#不定参数类型" class="headerlink" title="不定参数类型"></a>不定参数类型</h4><p>不定参数是指函数传入的参数个数为不定数量。为了做到这点，首相需要将函数定义为接受不定参数类型：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">myFunc</span><span class="params">(args ...<span class="type">int</span>)</span></span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _, arg := <span class="keyword">range</span> args &#123;</span><br><span class="line"></span><br><span class="line">fmt.Println(arg)</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>调用方式，like：myFunc(2, 3 , 4)</p><p>像这种 …type 格式的类型只能作为函数的参数类型存在，并且必须是最后一个参数。（这点和 Java 5 的特性蛮像的哎）</p><p>内部实现上来说，类型 …type 本质上就是一个数组切片，也就是 []type。想象如果没有这个<strong>语法糖</strong>，我们应该这么写：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">myFunc</span><span class="params">(args []<span class="type">int</span>)</span></span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _, arg := <span class="keyword">range</span> args &#123;</span><br><span class="line">fmt.Println(arg)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>其实看起来也还行，但是调用起来就比较麻烦了，形如：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">myFunc([]<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">7</span>, <span class="number">9</span>&#125;)</span><br></pre></td></tr></table></figure><p>我们不得不加上 []int{1,3,5,7,9} 来构造一个数组切片实例（烦</p><h2 id="参考书籍"><a href="#参考书籍" class="headerlink" title="参考书籍"></a>参考书籍</h2><p>《Go语言编程》</p>]]>
    </content>
    <id>https://hellovass.github.io/2017/04/04/%E5%88%9D%E8%AF%86%20Go%20%E8%AF%AD%E8%A8%80/</id>
    <link href="https://hellovass.github.io/2017/04/04/%E5%88%9D%E8%AF%86%20Go%20%E8%AF%AD%E8%A8%80/"/>
    <published>2017-04-04T13:46:14.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>了解 Go 语言的特性，尽量轻松地掌握这门简洁、有趣却又强大的新语言。</p>]]>
    </summary>
    <title>初识 Go 语言</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Android" scheme="https://hellovass.github.io/tags/Android/"/>
    <content>
      <![CDATA[<h2 id="误区"><a href="#误区" class="headerlink" title="误区"></a>误区</h2><p>shadowsocks 在 windows 上是支持 socks 代理和 http 代理，但在 OSX 上只是支持 socks5 代理，属于局部代理。Android Studio 本身支持 socks5 代理，但是 gradle 只支持 http 代理，这也导致了虽然开着 shadowsocks 却无法更新 SDK 或者下载 gradle 依赖。</p><p>所以在 OSX 上这么设置，实际上是无效的。<br>![Android Studio Proxy Settings](<a href="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Android">https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Android</a> Studio代理指南&#x2F;代理设置.jpg)</p><span id="more"></span><h2 id="怎么破"><a href="#怎么破" class="headerlink" title="怎么破"></a>怎么破</h2><p>当然是选择把 socks 代理转为 http 代理咯。</p><ol><li><p>最新版的 shadowsocks 已经支持将 socks 转为 http 的 feature 了，所以先去<a href="https://github.com/shadowsocks/ShadowsocksX-NG">官网</a>下载最新的 shadowsocks 版本。</p></li><li><p>安装好之后，启动 shadowsocks，查看<strong>偏好设置</strong>，选择 HTTP 一栏<br>![Shadowsocks 偏好设置](<a href="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Android">https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Android</a> Studio代理指南&#x2F;Shadowsocks偏好设置.jpg)</p></li><li><p>设置 Android Studio Proxy<br>![Android Studio 代理设置](<a href="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Android">https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Android</a> Studio代理指南&#x2F;Android Studio代理设置.jpg)</p></li></ol><h2 id="终于，在-OSX-上也可以愉快地更新-SDK-和下载依赖了。"><a href="#终于，在-OSX-上也可以愉快地更新-SDK-和下载依赖了。" class="headerlink" title="终于，在 OSX 上也可以愉快地更新 SDK 和下载依赖了。"></a>终于，在 OSX 上也可以愉快地更新 SDK 和下载依赖了。</h2>]]>
    </content>
    <id>https://hellovass.github.io/2017/04/04/%E7%9C%9F%C2%B7Android%20Studio%20%E4%BB%A3%E7%90%86%E8%AE%BE%E7%BD%AE%E6%8C%87%E5%8D%97/</id>
    <link href="https://hellovass.github.io/2017/04/04/%E7%9C%9F%C2%B7Android%20Studio%20%E4%BB%A3%E7%90%86%E8%AE%BE%E7%BD%AE%E6%8C%87%E5%8D%97/"/>
    <published>2017-04-04T05:13:47.000Z</published>
    <summary>
      <![CDATA[<h2 id="误区"><a href="#误区" class="headerlink" title="误区"></a>误区</h2><p>shadowsocks 在 windows 上是支持 socks 代理和 http 代理，但在 OSX 上只是支持 socks5 代理，属于局部代理。Android Studio 本身支持 socks5 代理，但是 gradle 只支持 http 代理，这也导致了虽然开着 shadowsocks 却无法更新 SDK 或者下载 gradle 依赖。</p>
<p>所以在 OSX 上这么设置，实际上是无效的。<br>![Android Studio Proxy Settings](<a href="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Android">https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Android</a> Studio代理指南&#x2F;代理设置.jpg)</p>]]>
    </summary>
    <title>真·Android Studio 代理设置指南</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%BF%83/%E5%A4%8F%E7%9B%AE%E6%BC%B1%E7%9F%B3%E5%BF%83.jpg" alt="心"></p><span id="more"></span><h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2>]]>
    </content>
    <id>https://hellovass.github.io/2016/12/30/%E3%80%8E%E5%BF%83%E3%80%8F/</id>
    <link href="https://hellovass.github.io/2016/12/30/%E3%80%8E%E5%BF%83%E3%80%8F/"/>
    <published>2016-12-30T16:27:03.000Z</published>
    <summary>
      <![CDATA[<p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%BF%83/%E5%A4%8F%E7%9B%AE%E6%BC%B1%E7%9F%B3%E5%BF%83.jpg" alt="心"></p>]]>
    </summary>
    <title>『心』</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E8%B5%B7%E9%A3%8E%E4%BA%86/%E8%B5%B7%E9%A3%8E%E4%BA%86.jpg" alt="起风了"></p><span id="more"></span><h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><blockquote><p>起风了</p><p>唯有努力生存</p><p>如果，还没有能力改变风的方向</p><p>…</p></blockquote>]]>
    </content>
    <id>https://hellovass.github.io/2016/12/27/%E8%B5%B7%E9%A3%8E%E4%BA%86%EF%BC%8C%E5%94%AF%E6%9C%89%E5%8A%AA%E5%8A%9B%E7%94%9F%E5%AD%98/</id>
    <link href="https://hellovass.github.io/2016/12/27/%E8%B5%B7%E9%A3%8E%E4%BA%86%EF%BC%8C%E5%94%AF%E6%9C%89%E5%8A%AA%E5%8A%9B%E7%94%9F%E5%AD%98/"/>
    <published>2016-12-27T12:25:52.000Z</published>
    <summary>
      <![CDATA[<p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E8%B5%B7%E9%A3%8E%E4%BA%86/%E8%B5%B7%E9%A3%8E%E4%BA%86.jpg" alt="起风了"></p>]]>
    </summary>
    <title>起风了，唯有努力生存</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="有时候真的很无奈"><a href="#有时候真的很无奈" class="headerlink" title="有时候真的很无奈"></a>有时候真的很无奈</h2><p>激烈的市场竞争中，在那个摇摇欲坠不知道什么时候就会爆炸的基础上继续添加，添加新功能，一遍又一遍写着那些令人作呕的 fucking code 才能养活自己。</p><span id="more"></span><h2 id="然而，这并不是我想要的"><a href="#然而，这并不是我想要的" class="headerlink" title="然而，这并不是我想要的"></a>然而，这并不是我想要的</h2>]]>
    </content>
    <id>https://hellovass.github.io/2016/12/19/%E8%BE%A3%E9%B8%A1%E4%BB%A3%E7%A0%81/</id>
    <link href="https://hellovass.github.io/2016/12/19/%E8%BE%A3%E9%B8%A1%E4%BB%A3%E7%A0%81/"/>
    <published>2016-12-19T05:48:14.000Z</published>
    <summary>
      <![CDATA[<h2 id="有时候真的很无奈"><a href="#有时候真的很无奈" class="headerlink" title="有时候真的很无奈"></a>有时候真的很无奈</h2><p>激烈的市场竞争中，在那个摇摇欲坠不知道什么时候就会爆炸的基础上继续添加，添加新功能，一遍又一遍写着那些令人作呕的 fucking code 才能养活自己。</p>]]>
    </summary>
    <title>辣鸡代码</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="写给-24-周岁的自己"><a href="#写给-24-周岁的自己" class="headerlink" title="写给 24 周岁的自己"></a>写给 24 周岁的自己</h2><blockquote><p>生日快乐！</p></blockquote><span id="more"></span><h2 id="以及感谢还惦记着我的大家"><a href="#以及感谢还惦记着我的大家" class="headerlink" title="以及感谢还惦记着我的大家"></a>以及感谢还惦记着我的大家</h2><blockquote><p>阿里嘎多！</p></blockquote>]]>
    </content>
    <id>https://hellovass.github.io/2016/12/06/%E8%93%9D%E8%BF%87%EF%BC%8C24%E5%91%A8%E5%B2%81%E4%BA%86/</id>
    <link href="https://hellovass.github.io/2016/12/06/%E8%93%9D%E8%BF%87%EF%BC%8C24%E5%91%A8%E5%B2%81%E4%BA%86/"/>
    <published>2016-12-06T16:50:18.000Z</published>
    <summary>
      <![CDATA[<h2 id="写给-24-周岁的自己"><a href="#写给-24-周岁的自己" class="headerlink" title="写给 24 周岁的自己"></a>写给 24 周岁的自己</h2><blockquote>
<p>生日快乐！</p>
</blockquote>]]>
    </summary>
    <title>蓝过，24周岁了</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%90%9B%E5%90%8D%E8%B0%81%E5%9C%A8%E8%A2%AB%E8%99%90/%E7%A7%92%E9%80%9F%E4%BA%94%E5%8E%98%E7%B1%B3.jpg" alt="秒速五厘米"></p><h2 id="if"><a href="#if" class="headerlink" title="if"></a>if</h2><blockquote><p>择一城，<br>遇一人，<br>终一生…</p></blockquote><span id="more"></span><h2 id="one-more-time-one-more-chance"><a href="#one-more-time-one-more-chance" class="headerlink" title="one more time one more chance"></a>one more time one more chance</h2><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=432511403&auto=0&height=66"></iframe><h2 id="记"><a href="#记" class="headerlink" title="记"></a>记</h2><p>一个月前敲下 <code>hexo new 『君名』·谁在被虐</code> 这个命令的时候，我就对这部电影充满期待。后来的一段日子，沉迷工作，不省人事，忽然发现『君名』已来。这个特殊的日子，还是没有妹纸约，于是和几位同事<strong>抱团取暖</strong>，一起去被<strong>新海诚</strong>虐。</p><h2 id="感"><a href="#感" class="headerlink" title="感"></a>感</h2><p>新海诚这个人，问题很大!</p><h3 id="刀刀到肉，字字诛心"><a href="#刀刀到肉，字字诛心" class="headerlink" title="刀刀到肉，字字诛心"></a>刀刀到肉，字字诛心</h3><p>《秒速五厘米》跟我讲述了一对恋人因为漫长时间和沉重的人生阻隔而无法在一起，最终接受命运安排的故事，我很难受。</p><p>《君名》，结局是美好的，中间的故事活泼欢快，还有动荡曲折，男女主角的努力，对心底那份感情的执着也让我感动，但是却找不到一段能表达我内心活动的文字，某天，在网上看到这么一段话很应景：</p><blockquote><p>每个人的心里都有一团火，路过的人只看到烟，但是总有一个人，总有那么一个人能看到这火，然后走过来，陪我一起。</p></blockquote><blockquote><p>我在人群中，看到了他的火，我快步走过去，生怕慢一点他就会被淹没在岁月的尘埃里，我带着我的热情，我的冷漠，我的狂暴，我的温和，以及对爱情无理由的相信，走得上气不接下气，妆也花了，结结巴巴地对他说：“你叫什么名字？”</p></blockquote><blockquote><p>从你叫什么名字开始，后来，有了一切。</p></blockquote><p>妙啊，太贴切了！</p><h3 id="妹纸-恋爱"><a href="#妹纸-恋爱" class="headerlink" title="妹纸 &amp; 恋爱"></a>妹纸 &amp; 恋爱</h3><p>这是一个困扰了大部分理工科学生四年的问题，如何找到一个妹纸，让<strong>世界线</strong>变动，开启一段<strong>人生</strong>，而不是在<strong>原来的世界线</strong>里过着一段悲惨的<strong>狗生</strong>（<del>出去买杯奶茶都要被虐的那种</del>)。电影中的绳结、口嚼酒、灵魂互换，生动形象地解释了——遇到一个看到你心中那团火的人，接受他&#x2F;她的热情、冷漠、温和、狂暴，陌生的灵魂结合在一起，即使记忆消失，心底的那份感情也时常牵动自己的身体。</p><p>我也渴望，能找到绳结的另一半，体验被别人依赖的感觉，察觉她的颦眉蹙頞。在我停顿思考时，她能明白我心中所想；在我放声大笑时，她能感受到我掩藏在内心的哀伤。她总能看到我心中的那团火，在她面前我也不用掩饰做作…</p><h3 id="很喜欢，也很难过"><a href="#很喜欢，也很难过" class="headerlink" title="很喜欢，也很难过"></a>很喜欢，也很难过</h3><p>《君名》少了《秒速五厘米》的沉重和哀伤，多了一丝欢快和希望，像我们传达了爱情也可以跨越时间和地域的阻隔的美好愿景。</p><p>电影落幕，回到现实：</p><p>人生一定会遇到那个对的人吗？</p><blockquote><p>像他明天就会来那样期待，<br>像他永远也不会来那样生活。</p></blockquote><h3 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h3><p>横在<strong>桂树</strong>和<strong>明里</strong>中间的那辆火车是我多年来的一个心结，这次算是解开了吧。</p><blockquote><p>one more time, one more chance</p></blockquote>]]>
    </content>
    <id>https://hellovass.github.io/2016/12/02/%E3%80%8E%E5%90%9B%E5%90%8D%E3%80%8F%C2%B7%E8%B0%81%E5%9C%A8%E8%A2%AB%E8%99%90/</id>
    <link href="https://hellovass.github.io/2016/12/02/%E3%80%8E%E5%90%9B%E5%90%8D%E3%80%8F%C2%B7%E8%B0%81%E5%9C%A8%E8%A2%AB%E8%99%90/"/>
    <published>2016-12-02T11:30:00.000Z</published>
    <summary>
      <![CDATA[<p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E5%90%9B%E5%90%8D%E8%B0%81%E5%9C%A8%E8%A2%AB%E8%99%90/%E7%A7%92%E9%80%9F%E4%BA%94%E5%8E%98%E7%B1%B3.jpg" alt="秒速五厘米"></p>
<h2 id="if"><a href="#if" class="headerlink" title="if"></a>if</h2><blockquote>
<p>择一城，<br>遇一人，<br>终一生…</p>
</blockquote>]]>
    </summary>
    <title>『君名』·谁在被虐</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="算法" scheme="https://hellovass.github.io/categories/algorithm/"/>
    <category term="算法" scheme="https://hellovass.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<h2 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h2><p>有一个待排序的栈，现在想将该栈从顶到底按照从大到小的顺序排序，只允许申请一个栈。除此之外，可以申请新的变量，但不能申请新的数据结构。</p><span id="more"></span><h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 使用一个帮助栈对传入的栈进行拍戏</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> stack 待排序的 stack</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> &lt;T&gt;   泛型</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 使用一个帮助栈对传入的栈进行拍戏</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> stack 待排序的 stack</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> &lt;T&gt;   泛型</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">static</span> &lt;T <span class="keyword">extends</span> <span class="title class_">Comparable</span>&lt;T&gt;&gt; <span class="keyword">void</span> <span class="title function_">sort</span><span class="params">(Stack&lt;T&gt; stack)</span> &#123;</span><br><span class="line">       Stack&lt;T&gt; helpStack = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line">       <span class="keyword">while</span> (!stack.isEmpty()) &#123;</span><br><span class="line">           <span class="type">T</span> <span class="variable">currentElement</span> <span class="operator">=</span> stack.pop();</span><br><span class="line">           <span class="keyword">while</span> (!helpStack.isEmpty() &amp;&amp; currentElement.compareTo(helpStack.peek()) &gt; <span class="number">0</span>) &#123;</span><br><span class="line">               stack.push(helpStack.pop());</span><br><span class="line">           &#125;</span><br><span class="line">           helpStack.push(currentElement);</span><br><span class="line">       &#125;</span><br><span class="line"></span><br><span class="line">       <span class="keyword">while</span> (!helpStack.isEmpty()) &#123;</span><br><span class="line">           stack.push(helpStack.pop());</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;</span><br><span class="line">   </span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://hellovass.github.io/2016/11/17/%E7%94%A8%E4%B8%80%E4%B8%AA%E6%A0%88%E5%AE%9E%E7%8E%B0%E5%8F%A6%E4%B8%80%E4%B8%AA%E6%A0%88%E7%9A%84%E6%8E%92%E5%BA%8F/</id>
    <link href="https://hellovass.github.io/2016/11/17/%E7%94%A8%E4%B8%80%E4%B8%AA%E6%A0%88%E5%AE%9E%E7%8E%B0%E5%8F%A6%E4%B8%80%E4%B8%AA%E6%A0%88%E7%9A%84%E6%8E%92%E5%BA%8F/"/>
    <published>2016-11-17T16:39:04.000Z</published>
    <summary>
      <![CDATA[<h2 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h2><p>有一个待排序的栈，现在想将该栈从顶到底按照从大到小的顺序排序，只允许申请一个栈。除此之外，可以申请新的变量，但不能申请新的数据结构。</p>]]>
    </summary>
    <title>用一个栈实现另一个栈的排序</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="算法" scheme="https://hellovass.github.io/categories/algorithm/"/>
    <category term="算法" scheme="https://hellovass.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<h2 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h2><p>实现一个猫狗队列</p><span id="more"></span><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 宠物类</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Pet</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> String mType;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Pet</span><span class="params">(String type)</span> &#123;</span><br><span class="line">        mType = type;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getType</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> mType;</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">// 狗</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Dog</span> <span class="keyword">extends</span> <span class="title class_">Pet</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Dog</span><span class="params">(String type)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(type);</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">// 喵</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Cat</span> <span class="keyword">extends</span> <span class="title class_">Pet</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Cat</span><span class="params">(String type)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(type);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="实现一种猫狗队列的结构，要求："><a href="#实现一种猫狗队列的结构，要求：" class="headerlink" title="实现一种猫狗队列的结构，要求："></a>实现一种猫狗队列的结构，要求：</h3><ul><li><p>add 方法将 cat 或者 dog 放入到队列中</p></li><li><p>pollAll 方法将队列中所有的实例按照入队的先后顺序依次弹出</p></li><li><p>pollCat 方法将队列中的 cat 按照入队先后顺序依次弹出</p></li><li><p>pollDog 方法将队列中的 dog 按照入队先后顺序依次弹出</p></li><li><p>isEmpty 方法检查队列中是否还有 dog 或者 cat 的实例</p></li><li><p>isCatQueueEmpty 方法检查 cat 队列是否还有 cat</p></li><li><p>isDogQueueEmpty 方法检查 dog 队列是否还有 dog</p></li></ul><h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><h3 id="使用组合，增加一个时间戳的字段"><a href="#使用组合，增加一个时间戳的字段" class="headerlink" title="使用组合，增加一个时间戳的字段"></a>使用组合，增加一个时间戳的字段</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PetWithTimestamp</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Pet mPet;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> mTimestamp;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">PetWithTimestamp</span><span class="params">(Pet pet, <span class="type">long</span> timestamp)</span> &#123;</span><br><span class="line">        mPet = pet;</span><br><span class="line">        mTimestamp = timestamp;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Pet <span class="title function_">getPet</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> mPet;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getTimestamp</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> mTimestamp;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getType</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> mPet.getType();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="CatDogQueue-类设计"><a href="#CatDogQueue-类设计" class="headerlink" title="CatDogQueue 类设计"></a>CatDogQueue 类设计</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CatDogQueue</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Queue&lt;PetWithTimestamp&gt; mCatQueue;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Queue&lt;PetWithTimestamp&gt; mDogQueue;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">CatDogQueue</span><span class="params">()</span> &#123;</span><br><span class="line">        mCatQueue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">        mDogQueue = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">(Pet pet)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (pet == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;pet can&#x27;t be null&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">switch</span> (pet.getType()) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">case</span> <span class="string">&quot;cat&quot;</span>:</span><br><span class="line">                mCatQueue.add(<span class="keyword">new</span> <span class="title class_">PetWithTimestamp</span>(pet, System.currentTimeMillis()));</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">case</span> <span class="string">&quot;dog&quot;</span>:</span><br><span class="line">                mDogQueue.add(<span class="keyword">new</span> <span class="title class_">PetWithTimestamp</span>(pet, System.currentTimeMillis()));</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">default</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"></span><br><span class="line">    <span class="keyword">public</span> Pet <span class="title function_">pollAll</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (!mCatQueue.isEmpty() &amp;&amp; !mDogQueue.isEmpty()) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (mCatQueue.peek().getTimestamp() &lt; mDogQueue.peek().getTimestamp()) &#123;</span><br><span class="line">                <span class="keyword">return</span> mCatQueue.poll().getPet();</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> mDogQueue.poll().getPet();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (!mCatQueue.isEmpty()) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">return</span> mCatQueue.poll().getPet();</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (!mDogQueue.isEmpty()) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">return</span> mDogQueue.poll().getPet();</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;queue is empty!&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Cat <span class="title function_">pollCat</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!mCatQueue.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">return</span> (Cat) mCatQueue.poll().getPet();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;dog queue is empty!&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Dog <span class="title function_">pollDog</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!mDogQueue.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">return</span> (Dog) mDogQueue.poll().getPet();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;dog queue is empty!&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isEmpty</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> mCatQueue.isEmpty() &amp;&amp; mCatQueue.isEmpty();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isCatQueueEmpty</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> mCatQueue.isEmpty();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isDogQueueEmpty</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> mDogQueue.isEmpty();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://hellovass.github.io/2016/11/17/%E7%8C%AB%E7%8B%97%E9%98%9F%E5%88%97/</id>
    <link href="https://hellovass.github.io/2016/11/17/%E7%8C%AB%E7%8B%97%E9%98%9F%E5%88%97/"/>
    <published>2016-11-17T06:42:27.000Z</published>
    <summary>
      <![CDATA[<h2 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h2><p>实现一个猫狗队列</p>]]>
    </summary>
    <title>猫狗队列</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="算法" scheme="https://hellovass.github.io/categories/algorithm/"/>
    <category term="算法" scheme="https://hellovass.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<h2 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h2><p>如何仅用递归函数和栈逆序一个栈</p><span id="more"></span><h2 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h2><h3 id="递归函数一，将-stack-栈底的元素返回并且移除"><a href="#递归函数一，将-stack-栈底的元素返回并且移除" class="headerlink" title="递归函数一，将 stack 栈底的元素返回并且移除"></a>递归函数一，将 stack 栈底的元素返回并且移除</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取栈底元素</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> stack 传入的栈</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> &lt;E&gt;   返回的元素类型</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 栈底元素</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> &lt;E&gt; E <span class="title function_">getAndRemoveLastElement</span><span class="params">(Stack&lt;E&gt; stack)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">E</span> <span class="variable">result</span> <span class="operator">=</span> stack.pop();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (stack.isEmpty()) &#123;</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="type">E</span> <span class="variable">last</span> <span class="operator">=</span> getAndRemoveLastElement(stack);</span><br><span class="line">        stack.push(result);</span><br><span class="line">        <span class="keyword">return</span> last;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="递归函数二，逆序一个栈，会调用到上面提到的-getAndRemoveLastElement-方法"><a href="#递归函数二，逆序一个栈，会调用到上面提到的-getAndRemoveLastElement-方法" class="headerlink" title="递归函数二，逆序一个栈，会调用到上面提到的 getAndRemoveLastElement 方法"></a>递归函数二，逆序一个栈，会调用到上面提到的 <code>getAndRemoveLastElement</code> 方法</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 逆序一个栈</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> stack 被操作的栈</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> &lt;E&gt;   栈中元素类型</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;E&gt; <span class="keyword">void</span> <span class="title function_">reverse</span><span class="params">(Stack&lt;E&gt; stack)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (stack.isEmpty()) &#123;</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="type">E</span> <span class="variable">element</span> <span class="operator">=</span> getAndRemoveLastElement(stack);</span><br><span class="line">    reverse(stack);</span><br><span class="line"></span><br><span class="line">    stack.push(element);</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h2><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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">        Stack&lt;Integer&gt; testStack = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= <span class="number">5</span>; i++) &#123;</span><br><span class="line">            testStack.push(i);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        StackReverseUtil.reverse(testStack);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://hellovass.github.io/2016/11/15/%E4%BD%BF%E7%94%A8%E9%80%92%E5%BD%92%E9%80%86%E5%BA%8F%E4%B8%80%E4%B8%AA%E6%A0%88/</id>
    <link href="https://hellovass.github.io/2016/11/15/%E4%BD%BF%E7%94%A8%E9%80%92%E5%BD%92%E9%80%86%E5%BA%8F%E4%B8%80%E4%B8%AA%E6%A0%88/"/>
    <published>2016-11-15T16:13:46.000Z</published>
    <summary>
      <![CDATA[<h2 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h2><p>如何仅用递归函数和栈逆序一个栈</p>]]>
    </summary>
    <title>使用递归逆序一个栈</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="算法" scheme="https://hellovass.github.io/categories/algorithm/"/>
    <category term="算法" scheme="https://hellovass.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>编写一个类，用两个栈实现队列，支持队列的基本操作：add、poll、peek。</p><span id="more"></span><h2 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h2><p>栈的特点是先进后出，队列的特点是先进先出，使用两个栈正好能把顺序反过来实现类似队列的操作。</p><h2 id="方案"><a href="#方案" class="headerlink" title="方案"></a>方案</h2><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TwoStackQueue</span>&lt;T <span class="keyword">extends</span> <span class="title class_">Comparable</span>&lt;T&gt;&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Stack&lt;T&gt; mStack1;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Stack&lt;T&gt; mStack2;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">TwoStackQueue</span><span class="params">()</span> &#123;</span><br><span class="line">        mStack1 = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line">        mStack2 = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">(T item)</span> &#123;</span><br><span class="line">        mStack1.push(item);</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="comment">     * 移除并返回队首的元素</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> T <span class="title function_">poll</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (mStack1.isEmpty() &amp;&amp; mStack2.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Queue is empty!&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (mStack2.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">while</span> (!mStack1.isEmpty()) &#123;</span><br><span class="line">                mStack2.push(mStack1.pop());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> mStack2.pop();</span><br><span class="line"></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="comment">     * 返回队列头部的元素</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> T <span class="title function_">peek</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (mStack1.isEmpty() &amp;&amp; mStack2.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Queue is empty!&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (mStack2.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">while</span> (!mStack1.isEmpty()) &#123;</span><br><span class="line">                mStack2.push(mStack1.pop());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> mStack2.peek();</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><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://hellovass.github.io/2016/11/14/%E7%94%B1%E4%B8%A4%E4%B8%AA%E6%A0%88%E7%BB%84%E6%88%90%E7%9A%84%E9%98%9F%E5%88%97/</id>
    <link href="https://hellovass.github.io/2016/11/14/%E7%94%B1%E4%B8%A4%E4%B8%AA%E6%A0%88%E7%BB%84%E6%88%90%E7%9A%84%E9%98%9F%E5%88%97/"/>
    <published>2016-11-14T18:52:35.000Z</published>
    <summary>
      <![CDATA[<h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>编写一个类，用两个栈实现队列，支持队列的基本操作：add、poll、peek。</p>]]>
    </summary>
    <title>由两个栈组成的队列</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="算法" scheme="https://hellovass.github.io/categories/algorithm/"/>
    <category term="算法" scheme="https://hellovass.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>实现一个特殊的栈，在实现栈的基本功能的基础上，再实现返回栈中最小元素的操作</p><span id="more"></span><h2 id="要求"><a href="#要求" class="headerlink" title="要求"></a>要求</h2><ul><li><p>pop、push、getMin 操作的时间复杂度都是 O(1)</p></li><li><p>设计的栈类型可以使用现成的栈结构</p></li></ul><h2 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h2><p>这个类的设计上，采用两个栈，一个用来保存当前栈中的元素，其功能等同于一个正常的栈，记为 mStack;另一个栈用来保存每一步的最小值，记为 mMinStack.</p><h3 id="方案一："><a href="#方案一：" class="headerlink" title="方案一："></a>方案一：</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyStack1</span>&lt;T <span class="keyword">extends</span> <span class="title class_">Comparable</span>&lt;T&gt;&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Stack&lt;T&gt; mStack;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Stack&lt;T&gt; mMinStack;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">MyStack1</span><span class="params">()</span> &#123;</span><br><span class="line">        mStack = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line">        mMinStack = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">push</span><span class="params">(T item)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (mMinStack.isEmpty()) &#123;</span><br><span class="line">            mMinStack.push(item);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (item.compareTo(getMin()) &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">            mMinStack.push(item);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        mStack.push(item);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> T <span class="title function_">pop</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (mMinStack.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;your stack is empty.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">T</span> <span class="variable">item</span> <span class="operator">=</span> mStack.pop();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (item.compareTo(getMin()) == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> mMinStack.pop();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> item;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> T <span class="title function_">getMin</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (mMinStack.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;your stack is empty.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> mMinStack.peek();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="方案二"><a href="#方案二" class="headerlink" title="方案二"></a>方案二</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyStack2</span>&lt;T <span class="keyword">extends</span> <span class="title class_">Comparable</span>&lt;T&gt;&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Stack&lt;T&gt; mStack;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Stack&lt;T&gt; mMinStack;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">push</span><span class="params">(T item)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (mMinStack.isEmpty()) &#123;</span><br><span class="line">            mMinStack.push(item);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (item.compareTo(getMin()) &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">            mMinStack.push(item);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">T</span> <span class="variable">minItem</span> <span class="operator">=</span> mMinStack.peek();</span><br><span class="line">            mMinStack.push(minItem);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        mStack.push(item);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> T <span class="title function_">pop</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (mMinStack.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;your stack is empty.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        mMinStack.pop();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> mStack.pop();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> T <span class="title function_">getMin</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (mMinStack.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;your stack is empty.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> mMinStack.peek();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://hellovass.github.io/2016/11/14/%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AA%E6%9C%89%20getMin%20%E5%8A%9F%E8%83%BD%E7%9A%84%E6%A0%88/</id>
    <link href="https://hellovass.github.io/2016/11/14/%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AA%E6%9C%89%20getMin%20%E5%8A%9F%E8%83%BD%E7%9A%84%E6%A0%88/"/>
    <published>2016-11-14T06:51:30.000Z</published>
    <summary>
      <![CDATA[<h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>实现一个特殊的栈，在实现栈的基本功能的基础上，再实现返回栈中最小元素的操作</p>]]>
    </summary>
    <title>设计一个有 getMin 功能的栈</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="折腾" scheme="https://hellovass.github.io/categories/tinkering/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><blockquote><p>我曾幻想<br>有一颗巨树屹立在世界的中心，它的枝干构成了整个世界<br>我的小屋，和其它千千万万个小屋一样<br>静静地，静静地<br>依偎在世界树的脚下<br>…</p></blockquote><h2 id="故事是从这里开始的"><a href="#故事是从这里开始的" class="headerlink" title="故事是从这里开始的"></a>故事是从这里开始的</h2><span id="more"></span><p>那年，我大二，室友 Jury 开了一个 MC 的服务器，然后名为**《在 Minecraft 中活下去》**的故事就这么开始了。。。</p><h3 id="树上的大本营"><a href="#树上的大本营" class="headerlink" title="树上的大本营"></a>树上的大本营</h3><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Minecraft%E4%B9%8B%E6%97%85/%E6%A0%91%E4%B8%8A%E7%9A%84%E5%A4%A7%E6%9C%AC%E8%90%A5.jpeg" alt="树上的大本营"><br><br>Introduction: </p><blockquote><p>建在树上的大本营，游戏里大部分玩家的家都在这里<br>@建筑师 Jury、ludi<br>@地主 HelloVass，因为<strong>养鸡场、牧场、甘蔗田、农田</strong>都是我的…</p></blockquote><h3 id="天空石头城堡"><a href="#天空石头城堡" class="headerlink" title="天空石头城堡"></a>天空石头城堡</h3><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Minecraft%E4%B9%8B%E6%97%85/%E5%A4%A9%E7%A9%BA%E7%9F%B3%E5%A4%B4%E5%9F%8E%E5%A0%A1.jpeg" alt="天空石头城堡"></p><p>Introduction:</p><blockquote><p>房顶还没有建好 o(╯□╰)o<br>@建筑师 Jury</p></blockquote><h3 id="剑塔"><a href="#剑塔" class="headerlink" title="剑塔"></a>剑塔</h3><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Minecraft%E4%B9%8B%E6%97%85/%E5%89%91%E5%A1%94.jpeg" alt="剑塔"></p><p>Introduction:</p><blockquote><p>吾为所持剑之骨<br>此身为剑之骨<br>钢铁为身 而火焰为血<br>…<br>@建筑师 ludi</p></blockquote><h3 id="极客部落纪念碑"><a href="#极客部落纪念碑" class="headerlink" title="极客部落纪念碑"></a>极客部落纪念碑</h3><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Minecraft%E4%B9%8B%E6%97%85/%E6%9E%81%E5%AE%A2%E9%83%A8%E8%90%BD%E5%AE%A3%E4%BC%A0%E7%89%8C.jpeg" alt="极客部落纪念碑"></p><p>Introduction:</p><blockquote><p>社团的纪念碑<br>@建筑师 ludi</p></blockquote><h3 id="新干线"><a href="#新干线" class="headerlink" title="新干线"></a>新干线</h3><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Minecraft%E4%B9%8B%E6%97%85/%E9%93%81%E9%81%93%E4%BA%A4%E9%80%9A.jpeg" alt="新干线"></p><p>Introduction:</p><blockquote><p>游戏里的高速铁路系统，适合玩家到处串门或是作死地去远方冒险<br>@建筑师 Jury</p></blockquote><h3 id="我们的学院"><a href="#我们的学院" class="headerlink" title="我们的学院"></a>我们的学院</h3><p>Introduction:</p><blockquote><p>完成度60%<br>@建筑师 Jury</p></blockquote><h2 id="我的小屋"><a href="#我的小屋" class="headerlink" title="我的小屋"></a>我的小屋</h2><p>Introduction</p><blockquote><p>简陋到爆的<strong>土坯房</strong>，之前还真是用泥土块造的房子，后来改用橡木了！<br>面朝大海，春暖花开。</p></blockquote><h3 id="小屋本体"><a href="#小屋本体" class="headerlink" title="小屋本体"></a>小屋本体</h3><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Minecraft%E4%B9%8B%E6%97%85/%E5%B0%8F%E5%B1%8B%E6%9C%AC%E4%BD%93.jpg" alt="小屋本体"></p><h3 id="周围的樱花林"><a href="#周围的樱花林" class="headerlink" title="周围的樱花林"></a>周围的樱花林</h3><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Minecraft%E4%B9%8B%E6%97%85/%E5%AE%B6%E6%97%81%E7%9A%84%E6%A8%B1%E8%8A%B1%E6%9E%97.jpg" alt="小屋旁的樱花林"></p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Minecraft%E4%B9%8B%E6%97%85/%E6%A8%B1%E8%8A%B1%E6%9E%97%E8%BF%9C%E6%99%AF.jpg" alt="樱花林远景"></p><h3 id="今天的天气不错，出来逛逛"><a href="#今天的天气不错，出来逛逛" class="headerlink" title="今天的天气不错，出来逛逛"></a>今天的天气不错，出来逛逛</h3><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Minecraft%E4%B9%8B%E6%97%85/%E4%BB%8A%E5%A4%A9%E7%9A%84%E5%A4%A9%E6%B0%94%E7%9C%9F%E5%A5%BD.jpg" alt="今天的天气不错"></p><h3 id="辛勤的农场主"><a href="#辛勤的农场主" class="headerlink" title="辛勤的农场主"></a>辛勤的农场主</h3><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Minecraft%E4%B9%8B%E6%97%85/%E6%94%B6%E5%8D%97%E7%93%9C.jpg" alt="收南瓜"></p><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/Minecraft%E4%B9%8B%E6%97%85/%E5%B0%8F%E9%BA%A6%E5%92%8C%E7%94%98%E8%94%97.jpg" alt="小麦和甘蔗"></p><p>农场就在屋顶，外出挖矿、探险之后就去屋顶收获一波，把收来的作物做成<strong>面包、糖</strong>储藏起来。</p><h3 id="畜牧业"><a href="#畜牧业" class="headerlink" title="畜牧业"></a>畜牧业</h3><p>小屋的下面就是我的牧场，饲养了很多<strong>小鸡君、小猪君、羊羊君、牛牛君、</strong>。</p><h3 id="挖矿与探险"><a href="#挖矿与探险" class="headerlink" title="挖矿与探险"></a>挖矿与探险</h3><p>在这个服务器里，我认识了一个<strong>手残</strong>却又喜欢<strong>挖矿与探险</strong>的妹纸——watermark。我俩在一起挖矿的时候，那场面简直不要太欢乐！</p><ol><li>矿洞挖的太深，只能顺着梯子慢慢往下滑，她因为手残做<strong>自由落体</strong>直接落地摔死</li><li>不小心挖到岩浆，然后在<strong>岩浆里游泳</strong></li><li>试图去抢救落在岩浆附近的钻石，手残后又到<strong>岩浆里游泳</strong><br>…</li></ol><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><blockquote><p>我只想安安静静地在家里种田、放羊<br>面朝大海<br>春暖花开<br>梦里的那片樱花<br>散落的时候<br>真的，好美，好美<br>…</p></blockquote>]]>
    </content>
    <id>https://hellovass.github.io/2016/11/08/HelloVass%20%E7%9A%84%20Minecraft%20%E4%B9%8B%E6%97%85/</id>
    <link href="https://hellovass.github.io/2016/11/08/HelloVass%20%E7%9A%84%20Minecraft%20%E4%B9%8B%E6%97%85/"/>
    <published>2016-11-08T16:07:23.000Z</published>
    <summary>
      <![CDATA[<h2 id="写在最前"><a href="#写在最前" class="headerlink" title="写在最前"></a>写在最前</h2><blockquote>
<p>我曾幻想<br>有一颗巨树屹立在世界的中心，它的枝干构成了整个世界<br>我的小屋，和其它千千万万个小屋一样<br>静静地，静静地<br>依偎在世界树的脚下<br>…</p>
</blockquote>
<h2 id="故事是从这里开始的"><a href="#故事是从这里开始的" class="headerlink" title="故事是从这里开始的"></a>故事是从这里开始的</h2>]]>
    </summary>
    <title>HelloVass 的 Minecraft 之旅</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><blockquote><p>隐约雷鸣 阴霾天空 但盼风雨来 能留你在此</p></blockquote><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E6%83%86%E6%80%85%E7%9A%84%E9%9B%A8%E5%AD%A3/%E8%A8%80%E5%8F%B6%E4%B9%8B%E5%BA%AD%E4%B8%83%E6%9C%88.png" alt="七月"></p><blockquote><p>阴霾天空 隐约雷鸣 即使天无雨 我亦留此地 </p></blockquote><p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E6%83%86%E6%80%85%E7%9A%84%E9%9B%A8%E5%AD%A3/%E8%BD%B2%E7%88%B7%E7%9A%84%E5%85%AB%E6%9C%88.png" alt="八月"></p><span id="more"></span><h2 id="蓝受，香菇"><a href="#蓝受，香菇" class="headerlink" title="蓝受，香菇"></a>蓝受，香菇</h2><p>赋闲在家的日子，思绪一直很乱，时光有时候真的很残忍，就这样默默在你身边流淌，不紧不慢，一回过神来，就感觉失去很多。</p><h2 id="突然的失业"><a href="#突然的失业" class="headerlink" title="突然的失业"></a>突然的失业</h2><blockquote><p>世界上有两样东西不可直视，一是太阳，二是人心。</p></blockquote><p>2016.7.13，从家里赶回杭州，离开校园，感觉自己真正踏入了社会，<strong>但是并没有</strong>。现实，并没有想象中<strong>那么美好</strong>，以为自己能顺利工作几年，积累<strong>人生经验，闷声发大财</strong>。</p><p>我活在梦里，忽略了公司正在走向没落的事实。</p><ul><li><p>公司的职员似乎少了一半，似乎某个项目组都凭空消失了</p></li><li><p>公司不再提供租房补贴</p></li><li><p>周围的大家似乎越来越散漫，任务变得<strong>意外轻松</strong></p></li><li><p>Android 小组的两位员工相继离职，只剩下我和另外两位开发？</p></li><li><p>iOS 小组那边似乎也少了三个人</p></li><li><p>…</p></li></ul><p><strong>虽然也听到老大说由于今年的资本寒冬</strong>，许多公司都很紧张，进行各种措施减少开支，但是依旧安慰自己，“可能我想多了，怎么说我司也是去年10月份才拿到的 A 轮，哪有那么容易就花光了… ”</p><p>我安抚自己，逐渐将注意力放到工作中。任务其实不多，就是把之前写的几个库的 bug 修复，然后修复<strong>视频录制</strong>功能的一个 bug —— 在某个机型上后置摄像头无法对焦，导致预览的时候十分模糊。</p><h3 id="视频录制功能的-bug"><a href="#视频录制功能的-bug" class="headerlink" title="视频录制功能的 bug"></a>视频录制功能的 bug</h3><p>新版的 App 加入了一个 IM App 常见的功能 —— 视频录制。确实是个蛮常见的功能，但是因为 Android 的碎片化特点，要把这个功能做好，适配市场上大多数的机型确实也不太容易。正好自己音视频开发的经验也不是太多，于是开始捣鼓项目中的<strong>视频录制</strong>功能。</p><h3 id="有点儿失望"><a href="#有点儿失望" class="headerlink" title="有点儿失望"></a>有点儿失望</h3><p>因为项目里用的是 Android 原生提供的 API —— <a href="https://developer.android.com/guide/topics/media/camera.html">Camera</a> 、<a href="https://developer.android.com/reference/android/media/MediaRecorder.html">MediaRecorder</a> 来实现的<strong>视频录制</strong>，这些 API 并不能有效地解决各个机型的适配问题，在某些机型上甚至还会发生 Crash。深入项目源码之后发现，<strong>对视频的采样竟然是硬编码的方式</strong>（一时语塞）。</p><h3 id="小收获-困惑"><a href="#小收获-困惑" class="headerlink" title="小收获&amp;困惑"></a>小收获&amp;困惑</h3><p>理解了如何利用 <a href="https://developer.android.com/guide/topics/media/camera.html">Camera</a> 和 SurfaceView 实现录屏时候的<strong>预览</strong>，同时也解决了某个机型上无法自动对焦的问题。但是，在自己使用 MediaRecorder 实现录制视频到本地的时候却发生了一个 JNI 层的报错，在网上搜索了很久尝试了很多种方式也无法有效解决，这点很郁闷（囧）…</p><h3 id="恍恍惚惚"><a href="#恍恍惚惚" class="headerlink" title="恍恍惚惚"></a>恍恍惚惚</h3><p>日子过得很快，转眼就到了八月，这段时间虽然经常听到同事说公司最近资金有点儿紧张什么的，但是<strong>万万没想到啊，  2016.8.15 居然没发工资！！！</strong></p><p>“不会是真的没钱了吧，这就很尴尬了…”</p><p>第二天，看到各位前辈们<strong>无心学习</strong>，在座位上漫不经心地浏览网页，最离谱的是 iOS 那边的小哥居然直接找公司投简历了？<del>WTF</del> 额，好吧，公司赢了，我大概知道什么情况了。那天，很恍惚，和大家聊了很久，在公司一直等待 <strong>CEO</strong> 本人的回复。下午， CEO 来了之后，老大进去和他聊了很久，等他回来的时候一脸凝重，我们也明白，<strong>公司是真的要散了</strong>。</p><p>“轲神，公司不行了，赶紧找下家吧…”</p><p>“其实公司行不行我也无所谓的，只要工资能结清就行”，我当时心里这么想，然而我还是太天真了。</p><h2 id="毫无价值的承诺"><a href="#毫无价值的承诺" class="headerlink" title="毫无价值的承诺"></a>毫无价值的承诺</h2><p><strong>在这里我只给出自己的结论，也懒得写具体的经过，因为心烦</strong>。这是一个完全对自己的承诺不负责任的家伙！！！我简直不敢想象这样的人是如何拿到 A 轮融资的？？？</p><h2 id="赋闲在家"><a href="#赋闲在家" class="headerlink" title="赋闲在家"></a>赋闲在家</h2><p>这段时间，宁波一直在下着大雨，雨点很密，硬生生地从空中砸向地面，哗啦啦地作响。我的心情也很糟，望向窗外那乌云密布的世界，简直就是自己的内心写照，烦。</p><h3 id="奢望"><a href="#奢望" class="headerlink" title="奢望"></a>奢望</h3><p>”万一拿到拖欠的工资，我一定要出去嗨一把，请兄弟几个大吃一顿，跟他们讲这辣鸡公司的故事…”，我这么憧憬着。</p><h3 id="差不多是一个废轲了"><a href="#差不多是一个废轲了" class="headerlink" title="差不多是一个废轲了"></a>差不多是一个废轲了</h3><p>我发现，我真的想多了，工资始终没到账。</p><h3 id="动漫-守望先锋"><a href="#动漫-守望先锋" class="headerlink" title="动漫&amp;守望先锋"></a>动漫&amp;守望先锋</h3><p>这段时间，差不多都是宅在家里，身无分文，也不想找父母要钱出去浪。靠着看看动漫，和几个朋友玩玩儿守望先锋这款辣鸡游戏排遣内心的烦闷。</p><h3 id="笔试-面试"><a href="#笔试-面试" class="headerlink" title="笔试&amp;面试"></a>笔试&amp;面试</h3><h4 id="宁波银行"><a href="#宁波银行" class="headerlink" title="宁波银行"></a>宁波银行</h4><p>伪装成 17 届的应届生参加了宁波银行在学校里的笔试，题目都还挺基础，但是涵盖的面挺广，<del>然而计算机网络、操作系统、数据结构相关的很多知识都忘了</del>，面试的机会都木有，gg。</p><h4 id="拉勾网-智联招聘"><a href="#拉勾网-智联招聘" class="headerlink" title="拉勾网&amp;智联招聘"></a>拉勾网&amp;智联招聘</h4><p>给宁波的一些互联网公司投简历，怎么说呢，感觉简历的处理效率实在有点慢，<del>其实工资也有点儿低</del>…</p><h3 id="C-C-、数据结构和算法"><a href="#C-C-、数据结构和算法" class="headerlink" title="C &#x2F; C++、数据结构和算法"></a>C &#x2F; C++、数据结构和算法</h3><p>总得给自己找点儿事情做，闲着无聊，开始重拾丢在大学的一些基础…</p><h2 id="重新开始"><a href="#重新开始" class="headerlink" title="重新开始"></a>重新开始</h2><p>我知道大家是爱我的，尤其是身边的一些了解我最近遭遇的朋友，都在鼓励我积极向上，重新开始…</p><h2 id="又想起"><a href="#又想起" class="headerlink" title="又想起"></a>又想起</h2><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/惆怅的雨季/杭城的雨雾.jpeg" width="400px"/>]]>
    </content>
    <id>https://hellovass.github.io/2016/10/17/%E6%83%86%E6%80%85%E7%9A%84%E9%9B%A8%E5%AD%A3/</id>
    <link href="https://hellovass.github.io/2016/10/17/%E6%83%86%E6%80%85%E7%9A%84%E9%9B%A8%E5%AD%A3/"/>
    <published>2016-10-17T17:12:19.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><blockquote>
<p>隐约雷鸣 阴霾天空 但盼风雨来 能留你在此</p>
</blockquote>
<p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E6%83%86%E6%80%85%E7%9A%84%E9%9B%A8%E5%AD%A3/%E8%A8%80%E5%8F%B6%E4%B9%8B%E5%BA%AD%E4%B8%83%E6%9C%88.png" alt="七月"></p>
<blockquote>
<p>阴霾天空 隐约雷鸣 即使天无雨 我亦留此地 </p>
</blockquote>
<p><img src="https://hellovass-blog-1257365569.cos.ap-shanghai.myqcloud.com/blog/%E6%83%86%E6%80%85%E7%9A%84%E9%9B%A8%E5%AD%A3/%E8%BD%B2%E7%88%B7%E7%9A%84%E5%85%AB%E6%9C%88.png" alt="八月"></p>]]>
    </summary>
    <title>惆怅的雨季</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Android" scheme="https://hellovass.github.io/tags/Android/"/>
    <content>
      <![CDATA[<h2 id="先来谈谈-ThreadLocal"><a href="#先来谈谈-ThreadLocal" class="headerlink" title="先来谈谈 ThreadLocal"></a>先来谈谈 ThreadLocal</h2><h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><blockquote><p>ThreadLocal 是一个线程内部的数据存储类，通过他可以在指定的线程中存储数据，数据存储以后，只有在指定线程中可以获取到存储的数据，对于其他线程来说则无法获取到数据。</p></blockquote><h3 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h3><blockquote><p>某些数据以线程为作用域并且不同线程具有不同的数据副本</p></blockquote><span id="more"></span><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><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 class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Looper</span> &#123;</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * API Implementation Note:</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * This class contains the code required to set up and manage an event loop</span></span><br><span class="line"><span class="comment">     * based on MessageQueue.  APIs that affect the state of the queue should be</span></span><br><span class="line"><span class="comment">     * defined on MessageQueue or Handler rather than on Looper itself.  For example,</span></span><br><span class="line"><span class="comment">     * idle handlers and sync barriers are defined on the queue whereas preparing the</span></span><br><span class="line"><span class="comment">     * thread, looping, and quitting are defined on the looper.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">TAG</span> <span class="operator">=</span> <span class="string">&quot;Looper&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// sThreadLocal.get() will return null unless you&#x27;ve called prepare().</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;Looper&gt; sThreadLocal = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;Looper&gt;();</span><br></pre></td></tr></table></figure><p>Handler 需要获取<strong>当前线程</strong>的 Looper，这时候 Looper 的作用域就是线程并且不同线程具有不同的 Looper。</p><h3 id="通过一个小-Demo-测试下"><a href="#通过一个小-Demo-测试下" class="headerlink" title="通过一个小 Demo 测试下"></a>通过一个小 Demo 测试下</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><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 class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> ThreadLocal&lt;String&gt; mThreadLocal = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;String&gt;();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">        printInMainThread();</span><br><span class="line"></span><br><span class="line">        printInThreadOne();</span><br><span class="line"></span><br><span class="line">        printInThreadTwo();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">printInMainThread</span><span class="params">()</span> &#123;</span><br><span class="line">        mThreadLocal.set(<span class="string">&quot;main thread&quot;</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;[Thread#main] mThreadLocal = &quot;</span> + mThreadLocal.get());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">printInThreadTwo</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="string">&quot;Thread#1&quot;</span>) &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">                mThreadLocal.set(<span class="string">&quot;thread 1&quot;</span>);</span><br><span class="line">                System.out.println(<span class="string">&quot;[Thread#1] mThreadLocal = &quot;</span> + mThreadLocal.get());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;.start();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">printInThreadOne</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="string">&quot;Thread#2&quot;</span>) &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">                mThreadLocal.set(<span class="string">&quot;thread 2&quot;</span>);</span><br><span class="line">                System.out.println(<span class="string">&quot;[Thread#2] mThreadLocal = &quot;</span> + mThreadLocal.get());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;.start();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从控制台输出可以看到，虽然在不同的线程中访问的是<strong>同一个</strong> ThreadLocal，但是通过 ThreadLocal.get( ) 这个方法得到的<strong>值</strong>却是不一样的，这就很有趣了！</p><h3 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h3><p>ThreadLocal 的 set( ) 方法，如下：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(T value)</span> &#123;</span><br><span class="line">    <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">    <span class="type">ThreadLocalMap</span> <span class="variable">map</span> <span class="operator">=</span> getMap(t);</span><br><span class="line">    <span class="keyword">if</span> (map != <span class="literal">null</span>)</span><br><span class="line">        map.set(<span class="built_in">this</span>, value);</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        createMap(t, value);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>根据<strong>当前线程</strong>调用 <code>getMap(Thread t)</code> 方法找到对应的 ThreadLocalMap，如果 map 不等于 null，则调用 ThreadLocalMap 的 <code>set(ThreadLocal&lt;?&gt; key, Object value)</code> 方法。</p><blockquote><p>NOTE: ThreadLocalMap 是 ThreadLocal 的一个静态内部类</p></blockquote><p>看下这个 <code>set(ThreadLocal&lt;?&gt; key, Object value)</code> 方法：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(ThreadLocal&lt;?&gt; key, Object value)</span> &#123;</span><br><span class="line"></span><br><span class="line">        Entry[] tab = table;</span><br><span class="line">        <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> tab.length;</span><br><span class="line">        <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> key.threadLocalHashCode &amp; (len-<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">Entry</span> <span class="variable">e</span> <span class="operator">=</span> tab[i];</span><br><span class="line">             e != <span class="literal">null</span>;</span><br><span class="line">             e = tab[i = nextIndex(i, len)]) &#123;</span><br><span class="line">            ThreadLocal&lt;?&gt; k = e.get();</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (k == key) &#123;</span><br><span class="line">                e.value = 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="keyword">if</span> (k == <span class="literal">null</span>) &#123;</span><br><span class="line">                replaceStaleEntry(key, value, i);</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        tab[i] = <span class="keyword">new</span> <span class="title class_">Entry</span>(key, value);</span><br><span class="line">        <span class="type">int</span> <span class="variable">sz</span> <span class="operator">=</span> ++size;</span><br><span class="line">        <span class="keyword">if</span> (!cleanSomeSlots(i, sz) &amp;&amp; sz &gt;= threshold)</span><br><span class="line">            rehash();</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>Entry 是 ThreadLocalMap 的一个静态内部类，如下：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">ThreadLocalMap</span> &#123;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...省略N行注释</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Entry</span> <span class="keyword">extends</span> <span class="title class_">WeakReference</span>&lt;ThreadLocal&lt;?&gt;&gt; &#123;</span><br><span class="line">  </span><br><span class="line">        Object value;</span><br><span class="line">        </span><br><span class="line">        Entry(ThreadLocal&lt;?&gt; k, Object v) &#123;</span><br><span class="line">            <span class="built_in">super</span>(k);</span><br><span class="line">            value = v;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><blockquote><p>NOTE：这里 ThreadMap 在选择 key 的时候并没有直接选择 ThreadLocal 实例，而是 ThreadLocal 实例的弱引用</p></blockquote><p>再看看 ThreadLocal 的 <code>get()</code> 方法和 ThreadLocalMap 的 <code>getEntry(ThreadLocal&lt;?&gt; key)</code> 方法</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="keyword">public</span> T <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">    <span class="type">ThreadLocalMap</span> <span class="variable">map</span> <span class="operator">=</span> getMap(t);</span><br><span class="line">    <span class="keyword">if</span> (map != <span class="literal">null</span>) &#123;</span><br><span class="line">        ThreadLocalMap.<span class="type">Entry</span> <span class="variable">e</span> <span class="operator">=</span> map.getEntry(<span class="built_in">this</span>);</span><br><span class="line">        <span class="keyword">if</span> (e != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">            <span class="type">T</span> <span class="variable">result</span> <span class="operator">=</span> (T)e.value;</span><br><span class="line">            <span class="keyword">return</span> result;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> setInitialValue();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> Entry <span class="title function_">getEntry</span><span class="params">(ThreadLocal&lt;?&gt; key)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> key.threadLocalHashCode &amp; (table.length - <span class="number">1</span>);</span><br><span class="line">        <span class="type">Entry</span> <span class="variable">e</span> <span class="operator">=</span> table[i];</span><br><span class="line">        <span class="keyword">if</span> (e != <span class="literal">null</span> &amp;&amp; e.get() == key)</span><br><span class="line">            <span class="keyword">return</span> e;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            <span class="keyword">return</span> getEntryAfterMiss(key, i, e);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>根据<strong>当前线程</strong>找到对应的 <code>ThreadLocalMap</code>，如果 map 不等于空，接着调用 ThreadLocalMap 的 <code>getEntry(ThreadLocal&lt;?&gt; key)</code> 方法得到对应的 ThreadLocalMap.Entry 。如果 map 等于空，则调用 <code>setInitialValue()</code> 提供的值（默认是 null）。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> T <span class="title function_">initialValue</span><span class="params">()</span> &#123;</span><br><span class="line">     <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该方法可以由开发者来<strong>重写</strong>，提供一个初始值。</p><h2 id="搞基三剑客"><a href="#搞基三剑客" class="headerlink" title="搞基三剑客"></a>搞基三剑客</h2><p>开发者日常接触最多的可能是 Handler，而支撑 Handler 运行机制的实际上还有 MessageQueue 和 Looper 这两个好基友。</p><h3 id="MessageQueue"><a href="#MessageQueue" class="headerlink" title="MessageQueue"></a>MessageQueue</h3><p>中文名称消息队列，实际上的数据结构并不是<strong>队列</strong>，而是一个<strong>链表</strong>，主要支持两个操作——消息入队和消息出队。</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="title function_">enqueueMessage</span><span class="params">(Message msg, <span class="type">long</span> <span class="keyword">when</span>)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (msg.target == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Message must have a target.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (msg.isInUse()) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(msg + <span class="string">&quot; This message is already in use.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (mQuitting) &#123;</span><br><span class="line">                <span class="type">IllegalStateException</span> <span class="variable">e</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(</span><br><span class="line">                        msg.target + <span class="string">&quot; sending message to a Handler on a dead thread&quot;</span>);</span><br><span class="line">                Log.w(TAG, e.getMessage(), e);</span><br><span class="line">                msg.recycle();</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            msg.markInUse();</span><br><span class="line">            msg.<span class="keyword">when</span> = <span class="keyword">when</span>;</span><br><span class="line">            <span class="type">Message</span> <span class="variable">p</span> <span class="operator">=</span> mMessages;</span><br><span class="line">            <span class="type">boolean</span> needWake;</span><br><span class="line">            <span class="keyword">if</span> (p == <span class="literal">null</span> || <span class="keyword">when</span> == <span class="number">0</span> || <span class="keyword">when</span> &lt; p.<span class="keyword">when</span>) &#123;</span><br><span class="line">                <span class="comment">// New head, wake up the event queue if blocked.</span></span><br><span class="line">                msg.next = p;</span><br><span class="line">                mMessages = msg;</span><br><span class="line">                needWake = mBlocked;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// Inserted within the middle of the queue.  Usually we don&#x27;t have to wake</span></span><br><span class="line">                <span class="comment">// up the event queue unless there is a barrier at the head of the queue</span></span><br><span class="line">                <span class="comment">// and the message is the earliest asynchronous message in the queue.</span></span><br><span class="line">                needWake = mBlocked &amp;&amp; p.target == <span class="literal">null</span> &amp;&amp; msg.isAsynchronous();</span><br><span class="line">                Message prev;</span><br><span class="line">                <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">                    prev = p;</span><br><span class="line">                    p = p.next;</span><br><span class="line">                    <span class="keyword">if</span> (p == <span class="literal">null</span> || <span class="keyword">when</span> &lt; p.<span class="keyword">when</span>) &#123;</span><br><span class="line">                        <span class="keyword">break</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">if</span> (needWake &amp;&amp; p.isAsynchronous()) &#123;</span><br><span class="line">                        needWake = <span class="literal">false</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                msg.next = p; <span class="comment">// invariant: p == prev.next</span></span><br><span class="line">                prev.next = msg;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// We can assume mPtr != 0 because mQuitting is false.</span></span><br><span class="line">            <span class="keyword">if</span> (needWake) &#123;</span><br><span class="line">                nativeWake(mPtr);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><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><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><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br></pre></td><td class="code"><pre><span class="line">Message <span class="title function_">next</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// Return here if the message loop has already quit and been disposed.</span></span><br><span class="line">        <span class="comment">// This can happen if the application tries to restart a looper after quit</span></span><br><span class="line">        <span class="comment">// which is not supported.</span></span><br><span class="line">        <span class="keyword">final</span> <span class="type">long</span> <span class="variable">ptr</span> <span class="operator">=</span> mPtr;</span><br><span class="line">        <span class="keyword">if</span> (ptr == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> <span class="variable">pendingIdleHandlerCount</span> <span class="operator">=</span> -<span class="number">1</span>; <span class="comment">// -1 only during first iteration</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">nextPollTimeoutMillis</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="keyword">if</span> (nextPollTimeoutMillis != <span class="number">0</span>) &#123;</span><br><span class="line">                Binder.flushPendingCommands();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            nativePollOnce(ptr, nextPollTimeoutMillis);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">                <span class="comment">// Try to retrieve the next message.  Return if found.</span></span><br><span class="line">                <span class="keyword">final</span> <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> SystemClock.uptimeMillis();</span><br><span class="line">                <span class="type">Message</span> <span class="variable">prevMsg</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">                <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> mMessages;</span><br><span class="line">                <span class="keyword">if</span> (msg != <span class="literal">null</span> &amp;&amp; msg.target == <span class="literal">null</span>) &#123;</span><br><span class="line">                    <span class="comment">// Stalled by a barrier.  Find the next asynchronous message in the queue.</span></span><br><span class="line">                    <span class="keyword">do</span> &#123;</span><br><span class="line">                        prevMsg = msg;</span><br><span class="line">                        msg = msg.next;</span><br><span class="line">                    &#125; <span class="keyword">while</span> (msg != <span class="literal">null</span> &amp;&amp; !msg.isAsynchronous());</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (msg != <span class="literal">null</span>) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (now &lt; msg.<span class="keyword">when</span>) &#123;</span><br><span class="line">                        <span class="comment">// Next message is not ready.  Set a timeout to wake up when it is ready.</span></span><br><span class="line">                        nextPollTimeoutMillis = (<span class="type">int</span>) Math.min(msg.<span class="keyword">when</span> - now, Integer.MAX_VALUE);</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        <span class="comment">// Got a message.</span></span><br><span class="line">                        mBlocked = <span class="literal">false</span>;</span><br><span class="line">                        <span class="keyword">if</span> (prevMsg != <span class="literal">null</span>) &#123;</span><br><span class="line">                            prevMsg.next = msg.next;</span><br><span class="line">                        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                            mMessages = msg.next;</span><br><span class="line">                        &#125;</span><br><span class="line">                        msg.next = <span class="literal">null</span>;</span><br><span class="line">                        <span class="keyword">if</span> (DEBUG) Log.v(TAG, <span class="string">&quot;Returning message: &quot;</span> + msg);</span><br><span class="line">                        msg.markInUse();</span><br><span class="line">                        <span class="keyword">return</span> msg;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">// No more messages.</span></span><br><span class="line">                    nextPollTimeoutMillis = -<span class="number">1</span>;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// Process the quit message now that all pending messages have been handled.</span></span><br><span class="line">                <span class="keyword">if</span> (mQuitting) &#123;</span><br><span class="line">                    dispose();</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// If first time idle, then get the number of idlers to run.</span></span><br><span class="line">                <span class="comment">// Idle handles only run if the queue is empty or if the first message</span></span><br><span class="line">                <span class="comment">// in the queue (possibly a barrier) is due to be handled in the future.</span></span><br><span class="line">                <span class="keyword">if</span> (pendingIdleHandlerCount &lt; <span class="number">0</span></span><br><span class="line">                        &amp;&amp; (mMessages == <span class="literal">null</span> || now &lt; mMessages.<span class="keyword">when</span>)) &#123;</span><br><span class="line">                    pendingIdleHandlerCount = mIdleHandlers.size();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (pendingIdleHandlerCount &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="comment">// No idle handlers to run.  Loop and wait some more.</span></span><br><span class="line">                    mBlocked = <span class="literal">true</span>;</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (mPendingIdleHandlers == <span class="literal">null</span>) &#123;</span><br><span class="line">                    mPendingIdleHandlers = <span class="keyword">new</span> <span class="title class_">IdleHandler</span>[Math.max(pendingIdleHandlerCount, <span class="number">4</span>)];</span><br><span class="line">                &#125;</span><br><span class="line">                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// Run the idle handlers.</span></span><br><span class="line">            <span class="comment">// We only ever reach this code block during the first iteration.</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; pendingIdleHandlerCount; i++) &#123;</span><br><span class="line">                <span class="keyword">final</span> <span class="type">IdleHandler</span> <span class="variable">idler</span> <span class="operator">=</span> mPendingIdleHandlers[i];</span><br><span class="line">                mPendingIdleHandlers[i] = <span class="literal">null</span>; <span class="comment">// release the reference to the handler</span></span><br><span class="line"></span><br><span class="line">                <span class="type">boolean</span> <span class="variable">keep</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    keep = idler.queueIdle();</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">                    Log.wtf(TAG, <span class="string">&quot;IdleHandler threw exception&quot;</span>, t);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (!keep) &#123;</span><br><span class="line">                    <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">                        mIdleHandlers.remove(idler);</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 class="comment">// Reset the idle handler count to 0 so we do not run them again.</span></span><br><span class="line">            pendingIdleHandlerCount = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// While calling an idle handler, a new message could have been delivered</span></span><br><span class="line">            <span class="comment">// so go back and look again for a pending message without waiting.</span></span><br><span class="line">            nextPollTimeoutMillis = <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h3 id="Looper"><a href="#Looper" class="headerlink" title="Looper"></a>Looper</h3><p>字面意思，循环者，在 Android 的消息机制中扮演的是消息循环的角色。具体来说，是它负责从 MessageQueue 中查看是否有新的消息投递进来，如果有则立即处理；如果没有，就会阻塞在哪里。</p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="title function_">Looper</span><span class="params">(<span class="type">boolean</span> quitAllowed)</span> &#123;</span><br><span class="line">    mQueue = <span class="keyword">new</span> <span class="title class_">MessageQueue</span>(quitAllowed);</span><br><span class="line">    mThread = Thread.currentThread();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在构造方法中创建了一个 MessageQueue。</p><h4 id="prepare-方法"><a href="#prepare-方法" class="headerlink" title="prepare( ) 方法"></a>prepare( ) 方法</h4><p>初学 Android 的时候我们经常会写这样的一段代码，如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Thread</span>() &#123;</span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    Looper.prepare();</span><br><span class="line">    <span class="type">Handler</span> <span class="variable">handler</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Handler</span>() &#123;</span><br><span class="line">      <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleMessage</span><span class="params">(Message msg)</span> &#123;</span><br><span class="line">        <span class="comment">//  do some hard work</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    Looper.loop();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;.start();</span><br></pre></td></tr></table></figure><p>如果没有调用 Looper.prepare( ) 这个方法，应用就会 Crash。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">prepare</span><span class="params">()</span> &#123;</span><br><span class="line">    prepare(<span class="literal">true</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">prepare</span><span class="params">(<span class="type">boolean</span> quitAllowed)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (sThreadLocal.get() != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Only one Looper may be created per thread&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    sThreadLocal.set(<span class="keyword">new</span> <span class="title class_">Looper</span>(quitAllowed));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>很明显，每一个线程只允许有一个 Looper，否则就会抛出 <code>RuntimeException</code>。</p><p>接下来，Looper 中最重要的一个方法 <code>loop()</code>，如下所示：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">loop</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">Looper</span> <span class="variable">me</span> <span class="operator">=</span> myLooper();</span><br><span class="line">    <span class="keyword">if</span> (me == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;No Looper; Looper.prepare() wasn&#x27;t called on this thread.&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">MessageQueue</span> <span class="variable">queue</span> <span class="operator">=</span> me.mQueue;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Make sure the identity of this thread is that of the local process,</span></span><br><span class="line">    <span class="comment">// and keep track of what that identity token actually is.</span></span><br><span class="line">    Binder.clearCallingIdentity();</span><br><span class="line">    <span class="keyword">final</span> <span class="type">long</span> <span class="variable">ident</span> <span class="operator">=</span> Binder.clearCallingIdentity();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> queue.next(); <span class="comment">// might block</span></span><br><span class="line">        <span class="keyword">if</span> (msg == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// No message indicates that the message queue is quitting.</span></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">// This must be in a local variable, in case a UI event sets the logger</span></span><br><span class="line">        <span class="keyword">final</span> <span class="type">Printer</span> <span class="variable">logging</span> <span class="operator">=</span> me.mLogging;</span><br><span class="line">        <span class="keyword">if</span> (logging != <span class="literal">null</span>) &#123;</span><br><span class="line">            logging.println(<span class="string">&quot;&gt;&gt;&gt;&gt;&gt; Dispatching to &quot;</span> + msg.target + <span class="string">&quot; &quot;</span> +</span><br><span class="line">                    msg.callback + <span class="string">&quot;: &quot;</span> + msg.what);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">final</span> <span class="type">long</span> <span class="variable">traceTag</span> <span class="operator">=</span> me.mTraceTag;</span><br><span class="line">        <span class="keyword">if</span> (traceTag != <span class="number">0</span>) &#123;</span><br><span class="line">            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            msg.target.dispatchMessage(msg);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (traceTag != <span class="number">0</span>) &#123;</span><br><span class="line">                Trace.traceEnd(traceTag);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (logging != <span class="literal">null</span>) &#123;</span><br><span class="line">            logging.println(<span class="string">&quot;&lt;&lt;&lt;&lt;&lt; Finished to &quot;</span> + msg.target + <span class="string">&quot; &quot;</span> + msg.callback);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Make sure that during the course of dispatching the</span></span><br><span class="line">        <span class="comment">// identity of the thread wasn&#x27;t corrupted.</span></span><br><span class="line">        <span class="keyword">final</span> <span class="type">long</span> <span class="variable">newIdent</span> <span class="operator">=</span> Binder.clearCallingIdentity();</span><br><span class="line">        <span class="keyword">if</span> (ident != newIdent) &#123;</span><br><span class="line">            Log.wtf(TAG, <span class="string">&quot;Thread identity changed from 0x&quot;</span></span><br><span class="line">                    + Long.toHexString(ident) + <span class="string">&quot; to 0x&quot;</span></span><br><span class="line">                    + Long.toHexString(newIdent) + <span class="string">&quot; while dispatching to &quot;</span></span><br><span class="line">                    + msg.target.getClass().getName() + <span class="string">&quot; &quot;</span></span><br><span class="line">                    + msg.callback + <span class="string">&quot; what=&quot;</span> + msg.what);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        msg.recycleUnchecked();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>loop()</code> 方法内是一个死循环，唯一能跳出的条件是 <code>MessageQueue.next()</code> 返回了 null，否则 loop 将会无限循环下去。注意，loop 方法调用的 <code>MessageQueue.next()</code> 是一个阻塞操作，没有消息时，会阻塞在那里，这也是loop 会阻塞的原因。</p><blockquote><p>PS：关于 Looper 的阻塞，很多人还会产生这么一个<a href="https://www.zhihu.com/question/34652589">疑问</a>。</p></blockquote><p>注意，这里的 msg.target 就是<strong>发送消息的 Handler 对象</strong>，所以，最后 Handler 发送的消息又交给了它的 <code>dispatchMessage()</code> 方法处理！<del>记得第一次看这个逻辑的时候我也是懵逼的，为毛绕这么大一个圈消息又交给自己处理，MDZZ！</del>实际上，仔细看的话会发现，这时候 <code>msg.target.dispatchMessage()</code> 这个方法是在<strong>创建 Handler 的子线程</strong>中执行的！简单来说，<strong>代码巧妙地切换到指定的这个新线程中去执行了</strong>。</p><h3 id="Handler"><a href="#Handler" class="headerlink" title="Handler"></a>Handler</h3><h4 id="构造方法-1"><a href="#构造方法-1" 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><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">Handler</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>(<span class="literal">null</span>, <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">Handler</span><span class="params">(Callback callback, <span class="type">boolean</span> async)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (FIND_POTENTIAL_LEAKS) &#123;</span><br><span class="line">        <span class="keyword">final</span> Class&lt;? <span class="keyword">extends</span> <span class="title class_">Handler</span>&gt; klass = getClass();</span><br><span class="line">        <span class="keyword">if</span> ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &amp;&amp;</span><br><span class="line">                (klass.getModifiers() &amp; Modifier.STATIC) == <span class="number">0</span>) &#123;</span><br><span class="line">            Log.w(TAG, <span class="string">&quot;The following Handler class should be static or leaks might occur: &quot;</span> +</span><br><span class="line">                klass.getCanonicalName());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    mLooper = Looper.myLooper();</span><br><span class="line">    <span class="keyword">if</span> (mLooper == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(</span><br><span class="line">            <span class="string">&quot;Can&#x27;t create handler inside thread that has not called Looper.prepare()&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    mQueue = mLooper.mQueue;</span><br><span class="line">    mCallback = callback;</span><br><span class="line">    mAsynchronous = async;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Handler 默认的构造方法会检查 Looper 是否为空，如果为空则会报错 <strong>Can’t create handler inside thread that has not called Looper.prepare()</strong>。</p><p>至于为毛在 Activity 我们创建 Handler 实例的时候没有报错呢？因为在 ActivityThread 中已经调用了 <code>Looper.prepareMainLooper()</code>，如下所示：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    <span class="comment">// 省略N行...</span></span><br><span class="line">    </span><br><span class="line">    Looper.prepareMainLooper();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略N行...</span></span><br><span class="line">    Looper.loop();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>而 <code>prepareMainLooper()</code> 正调用了 <code>prepare()</code>，如下所示：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">prepareMainLooper</span><span class="params">()</span> &#123;</span><br><span class="line">    prepare(<span class="literal">false</span>);</span><br><span class="line">    <span class="keyword">synchronized</span> (Looper.class) &#123;</span><br><span class="line">        <span class="keyword">if</span> (sMainLooper != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;The main Looper has already been prepared.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        sMainLooper = myLooper();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://hellovass.github.io/2016/10/05/Android%20%E7%9A%84%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6/</id>
    <link href="https://hellovass.github.io/2016/10/05/Android%20%E7%9A%84%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6/"/>
    <published>2016-10-05T13:12:52.000Z</published>
    <summary>
      <![CDATA[<h2 id="先来谈谈-ThreadLocal"><a href="#先来谈谈-ThreadLocal" class="headerlink" title="先来谈谈 ThreadLocal"></a>先来谈谈 ThreadLocal</h2><h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><blockquote>
<p>ThreadLocal 是一个线程内部的数据存储类，通过他可以在指定的线程中存储数据，数据存储以后，只有在指定线程中可以获取到存储的数据，对于其他线程来说则无法获取到数据。</p>
</blockquote>
<h3 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h3><blockquote>
<p>某些数据以线程为作用域并且不同线程具有不同的数据副本</p>
</blockquote>]]>
    </summary>
    <title>Android 的消息机制</title>
    <updated>2026-04-19T11:40:53.351Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="技术" scheme="https://hellovass.github.io/categories/tech/"/>
    <category term="Android" scheme="https://hellovass.github.io/tags/Android/"/>
    <content>
      <![CDATA[<h2 id="简单说明"><a href="#简单说明" class="headerlink" title="简单说明"></a>简单说明</h2><p>手动调用 View 的 <code>measure(int widthMeasureSpec,int heightMeasureSpec)</code> 方法来得到 View 的宽高。</p><span id="more"></span><h2 id="根据-View-的-LayoutParams-以下几种情况"><a href="#根据-View-的-LayoutParams-以下几种情况" class="headerlink" title="根据 View 的 LayoutParams 以下几种情况"></a>根据 View 的 LayoutParams 以下几种情况</h2><h3 id="具体数值（dp-px）"><a href="#具体数值（dp-px）" class="headerlink" title="具体数值（dp&#x2F;px）"></a>具体数值（dp&#x2F;px）</h3><p>举个栗子，宽高都是 100 px，这时候，有我们来手动拼装合适的 MeasureSpec：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 手动测量 View 的宽高,当 View 的宽高是精确值时</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   * <span class="doctag">@param</span> target 需要测量的 View</span></span><br><span class="line"><span class="comment">   * <span class="doctag">@param</span> widthSize 宽度</span></span><br><span class="line"><span class="comment">   * <span class="doctag">@param</span> heightSize 高度</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">meaureManually</span><span class="params">(View target, <span class="type">int</span> widthSize, <span class="type">int</span> heightSize)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">widthMeasureSpec</span> <span class="operator">=</span> MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);</span><br><span class="line">    <span class="type">int</span> <span class="variable">heightMeasureSpec</span> <span class="operator">=</span> MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);</span><br><span class="line">    target.measure(widthMeasureSpec, heightMeasureSpec);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><h3 id="wrap-content"><a href="#wrap-content" class="headerlink" title="wrap_content"></a>wrap_content</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 手动测量 View 的宽高,当 View 的宽高是 wrap_content 时</span></span><br><span class="line"><span class="comment">   *</span></span><br><span class="line"><span class="comment">   * <span class="doctag">@param</span> target 需要测量的View</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">measueManually</span><span class="params">(View target)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">widthMeasureSpec</span> <span class="operator">=</span> MeasureSpec.makeMeasureSpec((<span class="number">1</span> &lt;&lt; <span class="number">30</span>) - <span class="number">1</span>, MeasureSpec.AT_MOST);</span><br><span class="line">    <span class="type">int</span> <span class="variable">heightMeasureSpec</span> <span class="operator">=</span> MeasureSpec.makeMeasureSpec((<span class="number">1</span> &lt;&lt; <span class="number">30</span>) - <span class="number">1</span>, MeasureSpec.AT_MOST);</span><br><span class="line">    target.measure(widthMeasureSpec, heightMeasureSpec);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>这里有点儿小技巧，我将本来应该传入的 <code>widthSize</code> 和 <code>heightSize</code> 改为了 <code>(1&lt;&lt;30)-1</code> ,看过 MeasureSpec 的源码就可以知道，这个特殊的 int 值就是 View 理论上能支持的最大值。</p><blockquote><p>View 的尺寸使用 30 位二进制来表示，也就是说最大是 30 个 1（即 2^30 -1），也就是 (1&lt;&lt;30)-1。</p></blockquote><h3 id="match-parent"><a href="#match-parent" class="headerlink" title="match_parent"></a>match_parent</h3><p>上一段 ViewGroup 的源码：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Does the hard part of measureChildren: figuring out the MeasureSpec to</span></span><br><span class="line"><span class="comment">     * pass to a particular child. This method figures out the right MeasureSpec</span></span><br><span class="line"><span class="comment">     * for one dimension (height or width) of one child view.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * The goal is to combine information from our MeasureSpec with the</span></span><br><span class="line"><span class="comment">     * LayoutParams of the child to get the best possible results. For example,</span></span><br><span class="line"><span class="comment">     * if the this view knows its size (because its MeasureSpec has a mode of</span></span><br><span class="line"><span class="comment">     * EXACTLY), and the child has indicated in its LayoutParams that it wants</span></span><br><span class="line"><span class="comment">     * to be the same size as the parent, the parent should ask the child to</span></span><br><span class="line"><span class="comment">     * layout given an exact size.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> spec The requirements for this view</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> padding The padding of this view for the current dimension and</span></span><br><span class="line"><span class="comment">     *        margins, if applicable</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> childDimension How big the child wants to be in the current</span></span><br><span class="line"><span class="comment">     *        dimension</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> a MeasureSpec integer for the child</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">getChildMeasureSpec</span><span class="params">(<span class="type">int</span> spec, <span class="type">int</span> padding, <span class="type">int</span> childDimension)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">specMode</span> <span class="operator">=</span> MeasureSpec.getMode(spec);</span><br><span class="line">        <span class="type">int</span> <span class="variable">specSize</span> <span class="operator">=</span> MeasureSpec.getSize(spec);</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> <span class="variable">size</span> <span class="operator">=</span> Math.max(<span class="number">0</span>, specSize - padding);</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> <span class="variable">resultSize</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">resultMode</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">switch</span> (specMode) &#123;</span><br><span class="line">        <span class="comment">// Parent has imposed an exact size on us</span></span><br><span class="line">        <span class="keyword">case</span> MeasureSpec.EXACTLY:</span><br><span class="line">            <span class="keyword">if</span> (childDimension &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                resultSize = childDimension;</span><br><span class="line">                resultMode = MeasureSpec.EXACTLY;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (childDimension == LayoutParams.MATCH_PARENT) &#123;</span><br><span class="line">                <span class="comment">// Child wants to be our size. So be it.</span></span><br><span class="line">                <span class="comment">// 如果换我们来构造此时的 MeasureSpec，我们需要知道这个 size 的大小，然而并不能，所以放弃</span></span><br><span class="line">                resultSize = size;</span><br><span class="line">                resultMode = MeasureSpec.EXACTLY;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (childDimension == LayoutParams.WRAP_CONTENT) &#123;</span><br><span class="line">                <span class="comment">// Child wants to determine its own size. It can&#x27;t be</span></span><br><span class="line">                <span class="comment">// bigger than us.</span></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">            <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Parent has imposed a maximum size on us</span></span><br><span class="line">        <span class="keyword">case</span> MeasureSpec.AT_MOST:</span><br><span class="line">            <span class="keyword">if</span> (childDimension &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// Child wants a specific size... so be it</span></span><br><span class="line">                resultSize = childDimension;</span><br><span class="line">                resultMode = MeasureSpec.EXACTLY;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (childDimension == LayoutParams.MATCH_PARENT) &#123;</span><br><span class="line">                <span class="comment">// Child wants to be our size, but our size is not fixed.</span></span><br><span class="line">                <span class="comment">// Constrain child to not be bigger than us.</span></span><br><span class="line">                <span class="comment">// 如果换我们来构造此时的 MeasureSpec，我们需要知道这个 size 的大小，然而并不能，所以放弃</span></span><br><span class="line">                resultSize = size;</span><br><span class="line">                resultMode = MeasureSpec.AT_MOST;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (childDimension == LayoutParams.WRAP_CONTENT) &#123;</span><br><span class="line">                <span class="comment">// Child wants to determine its own size. It can&#x27;t be</span></span><br><span class="line">                <span class="comment">// bigger than us.</span></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">            <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Parent asked to see how big we want to be</span></span><br><span class="line">        <span class="keyword">case</span> MeasureSpec.UNSPECIFIED:</span><br><span class="line">            <span class="keyword">if</span> (childDimension &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// Child wants a specific size... let him have it</span></span><br><span class="line">                resultSize = childDimension;</span><br><span class="line">                resultMode = MeasureSpec.EXACTLY;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (childDimension == LayoutParams.MATCH_PARENT) &#123;</span><br><span class="line">                <span class="comment">// Child wants to be our size... find out how big it should</span></span><br><span class="line">                <span class="comment">// be</span></span><br><span class="line">                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? <span class="number">0</span> : size;</span><br><span class="line">                resultMode = MeasureSpec.UNSPECIFIED;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (childDimension == LayoutParams.WRAP_CONTENT) &#123;</span><br><span class="line">                <span class="comment">// Child wants to determine its own size.... find out how</span></span><br><span class="line">                <span class="comment">// big it should be</span></span><br><span class="line">                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? <span class="number">0</span> : size;</span><br><span class="line">                resultMode = MeasureSpec.UNSPECIFIED;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//noinspection ResourceType</span></span><br><span class="line">        <span class="keyword">return</span> MeasureSpec.makeMeasureSpec(resultSize, resultMode);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>发现如果要构造在 View 的宽高为 match_parent 时的 MeasureSpec，<strong>需要</strong>知道 parentSize，而此时因为无法知道 parentSize，所以<strong>直接放弃</strong>。</p><h2 id="NOTE"><a href="#NOTE" class="headerlink" title="NOTE"></a>NOTE</h2><p>注意网上的一些<strong>奇怪代码</strong>，还是举两个常见的栗子：</p><h3 id="栗子一"><a href="#栗子一" class="headerlink" title="栗子一"></a>栗子一</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">view.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);</span><br></pre></td></tr></table></figure><h3 id="栗子二"><a href="#栗子二" class="headerlink" title="栗子二"></a>栗子二</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">widthMeasureSpec</span> <span class="operator">=</span> MeasureSpec.makeMeasureSpec(-<span class="number">1</span>, MeasureSpec.UNSPECIFIED);</span><br><span class="line"><span class="type">int</span> <span class="variable">heightMeasureSpec</span> <span class="operator">=</span> MeasureSpec.makeMeasureSpec(-<span class="number">1</span>, MeasureSpec.UNSPECIFIED);</span><br><span class="line">view.measure(widthMeasureSpec, heightMeasureSpec);</span><br></pre></td></tr></table></figure><p>第二个栗子最搞，现在在 Android Studio 里这么写编译器会直接给出错误提示，但是我以前经常能看到这样的代码（<del>不明白原理的时候，我以前也这么做2333</del>）</p>]]>
    </content>
    <id>https://hellovass.github.io/2016/10/04/%E6%89%8B%E5%8A%A8%E6%B5%8B%E9%87%8F%20View%20%E7%9A%84%E5%AE%BD%E9%AB%98/</id>
    <link href="https://hellovass.github.io/2016/10/04/%E6%89%8B%E5%8A%A8%E6%B5%8B%E9%87%8F%20View%20%E7%9A%84%E5%AE%BD%E9%AB%98/"/>
    <published>2016-10-04T08:24:57.000Z</published>
    <summary>
      <![CDATA[<h2 id="简单说明"><a href="#简单说明" class="headerlink" title="简单说明"></a>简单说明</h2><p>手动调用 View 的 <code>measure(int widthMeasureSpec,int heightMeasureSpec)</code> 方法来得到 View 的宽高。</p>]]>
    </summary>
    <title>手动测量 View 的宽高</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
  <entry>
    <author>
      <name>HelloVass</name>
    </author>
    <category term="生活" scheme="https://hellovass.github.io/categories/life/"/>
    <category term="随笔" scheme="https://hellovass.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<blockquote><p>夜深</p></blockquote><blockquote><p>靠着车窗<br>杭城的雨雾让人迷茫<br>看不清前进的方向</p></blockquote><span id="more"></span><blockquote><p>朦胧的月色<br>我想起<br>隐藏在代码里的混乱<br>让我在夜里不安 惆怅</p></blockquote>]]>
    </content>
    <id>https://hellovass.github.io/2015/10/20/%E6%9D%AD%E5%9F%8E%E7%9A%84%E9%9B%A8%E9%9B%BE/</id>
    <link href="https://hellovass.github.io/2015/10/20/%E6%9D%AD%E5%9F%8E%E7%9A%84%E9%9B%A8%E9%9B%BE/"/>
    <published>2015-10-20T10:30:00.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p>夜深</p>
</blockquote>
<blockquote>
<p>靠着车窗<br>杭城的雨雾让人迷茫<br>看不清前进的方向</p>
</blockquote>]]>
    </summary>
    <title>杭城的雨雾</title>
    <updated>2026-04-19T11:40:53.352Z</updated>
  </entry>
</feed>
