<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title><![CDATA[ 开源实验室]]></title>
    <description><![CDATA[张涛的开源实验室]]></description>
    <link><![CDATA[ https://www.kymjs.com/]]></link>
    <pubDate><![CDATA[2026-06-16]]></pubDate>
    <lastBuildDate><![CDATA[2026-06-16]]></lastBuildDate>
    
    <item>
        <title><![CDATA[张涛 - 一个月，纯VibeCoding，全平台云笔记APP]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/code/2026/06/15/01/</br>我做这个云笔记的初衷其实挺简单：找不到一个好用的全平台笔记。 大部分都是鸿蒙不能用；鸿蒙能用的，又不支持私有化存储；能私有化存储的，体验又很差。换了好几个，从 Notion 到 Obsidian 到 Joplin，没一个能完整满足我的需求。 - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2026-06-15]]></pubDate>
        <link><![CDATA[https://kymjs.com/code/2026/06/15/01/]]></link>
        <tags>
          
          <tag>AI</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>code</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/code/2026/06/15/01/</br>&lt;h2 id=&quot;引子&quot;&gt;引子&lt;/h2&gt;

&lt;p&gt;起名这东西，AI 比我会。&lt;br /&gt;
我做这个云笔记的初衷其实挺简单：找不到一个好用的全平台笔记。&lt;br /&gt;
大部分都是鸿蒙不能用；鸿蒙能用的，又不支持私有化存储；能私有化存储的，体验又很差。换了好几个，从 Notion 到 Obsidian 到 Joplin，没一个能完整满足我的需求。&lt;br /&gt;
实在没办法，才自己写的。&lt;br /&gt;
但这又牵扯出另一个问题：我自己其实已经很久没写”完整的产品”了。  2025年底我研究&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transformer&lt;/code&gt;源码的时候，发现做 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;team leader&lt;/code&gt; 这么多年以后，我擅长的领域不知不觉从 “怎么写代码” 漂移到了 “怎么排需求、怎么带人”。这种漂移是舒服的，但我隐隐觉得不对。&lt;/p&gt;

&lt;p&gt;一个老程序员如果还不会用 AI 写代码，那他大概率要被时代丢下了。 &lt;br /&gt;
这话听着像公众号标题党，但我是认真说的。我从 2014 年开始写 Android 开源框架，KJFrameForAndroid、TheMVP、TheRouter 到现在还有人star着用。&lt;br /&gt;
但说实话，过去两年我打开IDE的次数屈指可数。我花在写技术方案PPT上的时间，比写代码多十倍。&lt;br /&gt;
转折发生在2026年初。我装了一台NAS（这个之前写过），本意是搭家庭影院和备份照片。结果看到RAGFlow不需要GPU也能跑，我顺手搭了个AI客服知识库——那个项目从0到1我没写一行代码，全靠提示词。搭完那天我突然想：要不我把这个”找不到好用的全平台笔记”的痛点真做了？  &lt;br /&gt;
想到就做。我打开Cursor，开了个新项目，第一行提示词是：&lt;br /&gt;
“做一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HarmonyOS&lt;/code&gt;上的云笔记APP，本地优先，Markdown写作，支持WebDAV同步。”&lt;/p&gt;

&lt;p&gt;然后我靠在椅背上，等着看AI怎么回答。&lt;/p&gt;

&lt;p&gt;一个月后，它变成了轻羽云笔记。全平台（Android/iOS/HarmonyOS/macOS/Windows/Linux）覆盖、后端Go写的、付费订阅链路也跑通了。我全程没碰过一行代码——不夸张地说，我连&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git commit&lt;/code&gt;都是让AI写的。
这篇文章不是教程，也不是软广，中间讲的每一段都是真实发生的事。我想用一个月的时间，回答一个我自己的问题：AI到底能把一个独立开发者推到哪里？&lt;/p&gt;

&lt;h3 id=&quot;第1周脚手架与第一端&quot;&gt;第1周：脚手架与第一端&lt;/h3&gt;

&lt;p&gt;第一周的目标很简单：让鸿蒙端能跑起来。&lt;br /&gt;
这个目标定得不高，但纯Vibe Coding的第一行提示词就让我吃了亏。&lt;br /&gt;
我以为AI对鸿蒙生态已经熟透了，结果它写出来的ArkTS代码看着像那么回事，DevEco Studio一编译就报错——@ohos.net.http的几个属性它都记错版本号了。改了三轮，每次都是换个错误方式报错。我开始怀疑：到底是AI不行，还是我对它的预期太高？&lt;/p&gt;

&lt;p&gt;我重新想了一下这事。问题不是AI不行，是我的提示词太”具体”了。&lt;br /&gt;
我之前写提示词有个坏习惯，喜欢告诉AI”用什么API、什么结构”。这是写代码的人才有的肌肉记忆——”我清楚底层，我指挥你”。但这种提示词反而把AI绑死了：它只能在你画的圈里选，圈画错了就一起错。&lt;br /&gt;
后来我换了种写法。不告诉AI用什么API、什么文件结构，只讲我要什么：
“做一个HarmonyOS云笔记。用户能新建一篇笔记，输入Markdown文本，编辑后保存到本地。打开APP能看到所有笔记的列表，点列表项能进编辑页。”&lt;/p&gt;

&lt;p&gt;就这么简单。结果AI给我的方案，反而比之前三轮都靠谱：用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@ohos.data.relationalStore&lt;/code&gt;做存储，UI用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ListContainer&lt;/code&gt;，整个数据流清晰到我几乎不用改。&lt;br /&gt;
周末的时候，鸿蒙端记事+数据库+列表展示，端到端跑通了。我截图发到群里有朋友问：”这真是一行代码都没手写？”我说真没写，连样式表的颜色值都是让AI挑的。&lt;br /&gt;
这时候它还叫”轻羽”，没加”云笔记”三个字。我当时想的是——能不能先把单端做扎实，云同步的事往后放。事实证明，这个保守决定救了我。&lt;/p&gt;

&lt;h3 id=&quot;第2周六端扩张&quot;&gt;第2周：六端扩张&lt;/h3&gt;

&lt;p&gt;第二周开始，我忍不住了。
单端能用不稀奇。我要的是全平台：Android、iOS、HarmonyOS、macOS、Windows、Linux——一个都不能少。&lt;br /&gt;
原因也很直接：我自己就是多设备用户。能把我逼成多手机用户，也只能怪鸿蒙生态太垃圾，手上必须备一个备用机。然后就变成了Mac上写东西、iPhone 跟 Harmony上看、iPad 上改，这种体验不是单端 APP 能给的。&lt;br /&gt;
如果轻羽只能跑在鸿蒙上，那它就只能是我给鸿蒙生态做的一个玩具，不是一个真正的产品。&lt;/p&gt;

&lt;p&gt;我让 AI 出了一版多端扩张计划。它给的方案是：除了鸿蒙端继续用 ets （HarmonyOS 生态还在早期，Flutter 对鸿蒙的支持也不算稳，索性走原生），其他五端统一用 Flutter 写一套，共享后端 API 和数据库结构。&lt;br /&gt;
这套方案听起来合理，但落地的时候出了几个我必须自己决策的坑。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;坑一：Flutter多端UI不能直接复制粘贴，但比”原生翻译”好得多。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我第一版让AI照着鸿蒙端的UI逻辑，输出 Flutter 在 Android 和 iOS 上的代码。AI 给的方案是”先按 Material Design 写一版，再用平台适配组件做风格切换”。这种思路比让 AI 直接复制鸿蒙逻辑要聪明——至少不会出现”鸿蒙的翻译腔”。&lt;br /&gt;
但实际跑起来，AI给的版本还是有几个我没预料到的问题：&lt;br /&gt;
同一个按钮，在 Android 上的 Material Ink 反馈跟手，到了 iOS 上会有 0.2 秒的延迟感。AI的解释是 Flutter 的 GestureRecognizer 在 iOS 端会等系统级手势仲裁。这不是 bug，是设计——但我必须自己决定要不要接受这个”原生味”。&lt;/p&gt;

&lt;p&gt;Android 端的 Edge-to-Edge 沉浸式状态栏，Flutter 默认是不开的。&lt;br /&gt;
AI 第一版代码打开 APP 能看到状态栏下面有一道白边。这种细节不修，用户一眼就觉得”这APP不专业”。&lt;/p&gt;

&lt;p&gt;iOS 的 Cupertino 组件，AI 给的代码能用，但有些 icon 在 Material 和 Cupertino 之间切换时，渲染出来的线条粗细不一致——这是字体回退机制在搞鬼。&lt;/p&gt;

&lt;p&gt;最后我跟AI说：”别复制逻辑，每端重新想它的原生交互。” AI 第二版给的方案是：Flutter 端按 Android 走 Material3、iOS/macOS 走 Cupertino、Windows/Linux 走 Fluent 风格，鸿蒙端因为是 ets 原生，跟着 HarmonyOS Design 走——每端的”原生味”才真正出来。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;坑二：WebDAV同步冲突，AI的方案有盲区。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;本地优先的笔记，多端同时编辑就一定有冲突。AI 第一版给的方案是：时间戳较新者胜出。这听起来没毛病，但有个 1000ms 内的边界问题——两台设备时钟有偏差，或者一端在断网时改了再连网，”较新”这个判断并不总是对的。&lt;br /&gt;
我让 AI 加了个 1000ms 的”模糊判定”：时间差在 1 秒以内的版本，视为相同，不做覆盖。这是个细节，但决定了你会不会在某天发现自己的笔记内容神秘丢失。&lt;/p&gt;

&lt;p&gt;Flutter 在 Android 上的具体坑：&lt;br /&gt;
Flutter 的 HTTP 库 dio 在某些 Android 版本上会出现诡异的 “SSL handshake failed” ——同样的代码，在开发机上是好的，打包到 Android 7 的旧设备上就是连不上 WebDAV。AI 最后定位到是 Android 的网络安全配置默认不允许 cleartext traffic，需要单独写一份 network_security_config.xml，再在 AndroidManifest 里引用。这种”老设备兼容”问题，AI 不踩一遍我根本想不起来。 &lt;br /&gt;
还好是这些坑之前处理都有经验，直接跟 AI 讲一下思路，让他自己解决了。&lt;/p&gt;

&lt;p&gt;Flutter 在 iOS 上的具体坑：
iOS 17 之后，App Transport Security（ATS）对 HTTP 请求的限制更严了。AI 第一版直接用 dio 发 HTTP 请求，结果在 iOS 17 的真机上 WebDAV 连不上。最后换成了走 iOS 原生 URLSession，配置了Info.plist 里的 NSAppTransportSecurity 例外，才彻底解决。&lt;br /&gt;
这里有一个反直觉的点：Flutter 代码”能跑 “≠” 在 iOS 上能跑”。 你得让 AI 把 Platform Channel 那层打开，用 Swift 写底层。&lt;/p&gt;

&lt;p&gt;周末的时候，六端全部跑通了笔记的本地功能——鸿蒙是ets原生，其余五端是 Flutter，代码共享率超过 90%。 同步、冲突、UI、数据库——所有”产品能用”的部分都到位了。&lt;/p&gt;

&lt;p&gt;用了一个月，后面几周都是基于原始雏形做扩展。从一个想法到能在六端跑起来的轻羽云笔记。如果你想看成品，可以访问官网 &lt;a href=&quot;https://note.kymjs.com&quot;&gt;note.kymjs.com&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&quot;第3周账号体系与支付&quot;&gt;第3周：账号体系与支付&lt;/h2&gt;

&lt;p&gt;前两周是”做产品”，第三周开始是”做生意”。&lt;br /&gt;
用户系统、订阅、支付——这三个东西我之前在团队里都是交给别人做的，我自己从来没有从0到1搭过。这次让AI生成的第一版方案，我看完后只有一个反应：漏洞百出。&lt;/p&gt;

&lt;p&gt;不是AI不行，是这三件事必须有人做产品决策。&lt;br /&gt;
多登录的坑：AI把”绑定”和”登录”搞混了。&lt;/p&gt;

&lt;p&gt;我需要支持三种登录：微信（覆盖国内）、华为账号（鸿蒙生态）、Apple（iOS和合规要求）。最后坑爹的是，发现微信登录还得 300 块钱认证费，还每年一次。我果断放弃了，我都还没挣到 300 块，就让我花 300 块，开什么玩笑。&lt;br /&gt;
AI 第一版给的方案是：用户每次用不同身份登录，就创建一个新账号。这听起来简单，但后果是——我手机上用微信登录、电脑上用 Apple 登录，就变成了两个不同账号，订阅不通用。最后定的方案是：同一个用户的多个身份可以”绑定”到同一个账号，也可以”合并”——合并时把分散的订阅、订单全部迁移过来。这个方案 AI 第二版给得很漂亮，但这个合并的边界是我自己定的。&lt;/p&gt;

&lt;p&gt;支付双轨：国内和海外要分开。&lt;br /&gt;
国内我接的是支付宝支付。海外 iOS 上架必须用 App Store 内购（IAP），不能用第三方支付绕过苹果抽成。&lt;br /&gt;
AI 给的方案是把这两套都接上，看起来没问题——直到第一次 IAP 回调的时候，遇到了一连串我没预料到的问题。&lt;br /&gt;
iOS内购（IAP）的坑，比想象的多得多，包括后来 Harmony 端接华为支付也是，一个比一个坑，华为居然还得要线下签协议，什么年代了，只能说这公司要多 SB 有多 SB。 &lt;br /&gt;
iOS 的内购不像微信支付那样有明确的 API 文档和测试环境。Flutter 要调 iOS 的内购，必须走 Platform Channel，让 AI 写 Swift 代码——这一步 AI 不能完全替我搞定。&lt;/p&gt;

&lt;p&gt;Android上架的坑：软著。&lt;br /&gt;
这是我自己没踩过、也没想到的坑。Android 上架到国内应用商店（华为、小米、OPPO、vivo、应用宝等）需要软件著作权证书。这东西不是技术问题，是合规问题。&lt;br /&gt;
我去中国版权保护中心查了一下流程：需要提交源代码、用户手册、权利归属声明等材料，整个流程走下来至少要两个月。考虑到我一周就要上架，根本等不起。最后找了一家代办机构，600块钱，两周下证，加急费另算。 &lt;br /&gt;
这个钱花得不冤。但让我印象深刻的不是”代办”这件事本身——而是 AI 在我决定代办之前，根本没提醒我 “Android 上架需要软著”这件事。如果是我一个人摸索，可能要等到上架上不去才知道有这一步。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vibe Coding最大的盲区，是合规、法律、政策这些”代码之外的东西”。AI 再强，它也不会主动告诉你”你需要办个证”&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;第三周结束的时候，账号体系、合并逻辑、订阅、微信支付、IAP、软著——全跑通了。&lt;br /&gt;
这个阶段让我对 AI 的能力边界有了很具体的认识：它能给你 90% 的实现，但剩下 10% 的产品判断、合规判断，得你自己来。 &lt;br /&gt;
如果你想直接体验，下载方式我都放在文末。&lt;/p&gt;

&lt;h2 id=&quot;第4周上架发布回收&quot;&gt;第4周：上架、发布、回收&lt;/h2&gt;

&lt;p&gt;第四周相对轻松，但也最熬人。 &lt;br /&gt;
六端打包上架，每个商店的审核要点 AI 给的清单八九不离十。鸿蒙 AGC 上架比预想顺利，但是鸿蒙的审核我是真服，来来回回给我打回四五次，然后连个跟他们应用市场的人沟通的渠道都没有，只能你留电话，他们有空了给你回拨。  最折腾的是 iOS，Apple 的审核员对订阅类应用的隐私描述要求细致到变态，AI 给我拟的初版文案被打回两次——理由是”未明确说明订阅自动续费的取消方式”。&lt;/p&gt;

&lt;p&gt;软著下证之后，Android 端上架反而是最快的：OPPO、vivo、应用宝商店，只要给的资质清单都有，基本一次过。最麻烦的是应用宝，要单独提供一份”应用自律承诺书”。&lt;/p&gt;

&lt;p&gt;上架以后第一周的数据出来了：&lt;br /&gt;
装机数六端加起来不算多，但质量不差。&lt;br /&gt;
七日留存接近 35%，这个是我没想到的，我还没有做任何推广，只能说笔记这个需求是真实存在的，只是缺一个好应用。&lt;br /&gt;
付费转化率大概 3%，付费用户突破 10 位，这个数字在大厂眼里是个笑话。但作为一个独立开发者，这是我过去七年里做过的最完整的”产品-付费”闭环。&lt;/p&gt;

&lt;p&gt;更重要的是——AI省下来的时间都去哪了？&lt;br /&gt;
按理说，AI让我不写代码，我应该轻松很多。但我花在产品决策上的时间反而更多了。&lt;br /&gt;
以前我带团队，最常做的是”评审别人的方案、指出问题”。&lt;br /&gt;
现在我直接面对 AI 的输出，没有中间人，每个判断都是我自己做。账号该不该合并、冲突阈值该定多少、隐私文案写什么、订阅档位怎么分——这些以前是产品经理+架构师+法务的活，现在全压在我一个人头上。&lt;/p&gt;

&lt;p&gt;这个发现其实有点反直觉：AI 不是让你变轻松，是让你变成一个更完整的”独立开发者”。&lt;br /&gt;
&lt;strong&gt;你必须自己懂产品、自己懂架构、自己懂商业、自己懂一点法律。AI替你跑了所有的脏活累活，但判断依然在你身上&lt;/strong&gt;。&lt;/p&gt;

&lt;h2 id=&quot;收尾&quot;&gt;收尾&lt;/h2&gt;

&lt;p&gt;写到这里，按互联网的规矩应该是”感谢阅读，欢迎关注点赞收藏”。但这篇不是公众号推文，是我自己的真实记录。&lt;br /&gt;
所以最后这一段留给我的产品——&lt;strong&gt;轻羽云笔记&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;它是一个全平台的云笔记APP：
支持 Android、iOS、HarmonyOS、macOS、Windows、Linux 六端。&lt;br /&gt;
Markdown 写作，所见即所得。&lt;br /&gt;
笔记自动云同步，换设备不丢内容。&lt;br /&gt;
WebDAV 私有部署，数据完全由你掌控，所有笔记都是 RSA 加密的。&lt;br /&gt;
密码也都是加密后记录的，完全杜绝明文传输。 &lt;br /&gt;
一键生图分享，方便发到社交平台。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;官网：&lt;a href=&quot;https://note.kymjs.com&quot;&gt;https://note.kymjs.com&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;iOS：&lt;a href=&quot;https://apps.apple.com/cn/app/paste-limitless-clipboard/id6764479359&quot;&gt;https://apps.apple.com/cn/app/paste-limitless-clipboard/id6764479359&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;HarmonyOS：&lt;a href=&quot;https://appgallery.huawei.com/app/detail?id=com.kymjs.note&quot;&gt;https://appgallery.huawei.com/app/detail?id=com.kymjs.note&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;其他端（Android/Windows/macOS/Linux）也可以在官网首页直接下载。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;至于那个最初的问题——AI 到底能把一个独立开发者推到哪里——我没有答案。&lt;br /&gt;
但有一件事是确定的：我终于能在自己的鸿蒙手机上，用上一个让我放心的全平台笔记了。它支持私有化存储，体验也不差。&lt;br /&gt;
而这个产品，正好是我自己写的。&lt;br /&gt;
顺带，这件事让我重新变成了一个”做产品的人”，而不仅仅是一个”管产品的人”。这个感觉，我以为再也回不来了。&lt;br /&gt;
写作过程学到的几件事，提示词的颗粒度比我想的重要——讲”要什么”比讲”怎么实现”好。&lt;br /&gt;
AI 能跑 90% 的活，但 10% 的产品判断得自己来。&lt;br /&gt;
独立开发的真正难点不是写代码，是一个人背起所有决策。&lt;br /&gt;
月级别确实能做一个完整产品，但前提是边界清晰。&lt;/p&gt;

]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 借助 API 手写一个 Transformer 架构]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/code/2026/02/28/01/</br>在当今AI时代，技术几乎是按周来涌现的。对于我这种古老程序员来说，虽然是体验了不少AI产品，甚至自己源码跑了 Dify，Llamaindex这些破玩意，但 AI 大模型仍然是一个神秘的黑盒子。出于本能，还是觉得应该研究一些不变的东西。 - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2026-02-28]]></pubDate>
        <link><![CDATA[https://kymjs.com/code/2026/02/28/01/]]></link>
        <tags>
          
          <tag>AI</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>code</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/code/2026/02/28/01/</br>&lt;h2 id=&quot;背景&quot;&gt;背景&lt;/h2&gt;

&lt;p&gt;之所以写这个系列文章，其实是源自这个段子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;求求你们了，别再整新玩意了，学不动了。
求求你们了，别再整新玩意儿了
Manus没用过，OpenCode还没装，
Cowork还没捂热，又来个Clawdbot.
前脚Remotion干翻剪映，后脚Pencil打倒Figma,
去年前端已死，今年iOS爆发。
上周嘲笑苹果，这周下单Mac,
每天都革了昨天的命，日日都是AGI...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在当今AI时代，技术几乎是按周来涌现的。对于我这种古老程序员来说，虽然是体验了不少AI产品，甚至自己源码跑了 Dify，Llamaindex这些破玩意，但 AI 大模型仍然是一个神秘的黑盒子。出于本能，还是觉得应该研究一些不变的东西。&lt;/p&gt;

&lt;h2 id=&quot;大模型的背景&quot;&gt;大模型的背景&lt;/h2&gt;

&lt;p&gt;要讲大模型，还是必须讲NLP，这是计算机执行各种语言任务的基础。如中文分词、子词切分、词性标注、文本分类、实体识别、关系抽取、文本摘要、机器翻译、自动问答等大模型常用的功能，都是靠NLP技术完成的。不过这些都是成熟的技术了，在搜索引擎领域都不知道玩了多少年了，不是近几年新创造的东西，但也正是因为NLP技术的进步，让我们从海量文本中提取有用信息、理解语言的深层含义成为可能，才有了大模型训练的可能。&lt;/p&gt;

&lt;h2 id=&quot;11-文本转向量&quot;&gt;1.1 文本转向量&lt;/h2&gt;

&lt;p&gt;NLP里面被使用最多的一项技术就是文本向量化。文本转向量的目的是将人类语言转化为计算机可以处理的形式，也就是将文本数据数字化。&lt;/p&gt;

&lt;p&gt;将文本数字化以后，就可以将文本中每个”字”(实际上是token，这里叫字是方便理解)放在坐标系里面去，这样后续在查的时候，就能快速知道两个字之间的关系是比较近还是比较远。当有一个字以后，下一个字应该是什么，也就是下文的Transformer架构干的事。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2026022800.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;比如字节火山引擎的一系列向量模型，就是做这件事的：如果有一段文本叫”我是张涛”，假设这段文本是4个token，每个字是一个token。那么每个token被向量模型转化后，就变成了一个数组，数组表示这个token在坐标系中的位置坐标，数组的长度就是这个向量模型的维度。比如在平面直角坐标系中，我们可以用两个数字确定一个点(x, y)，因为平面是二维的。在空间直角坐标系中，需要三个数字确定一个点(x, y, z)，因为空间是三维的。假设我们现在的向量模型是6维的，前面我们说的”我是张涛”这四个token，转化成了四个长度为6的数组，也就是一个4行6列的二维数组，也可以用矩阵表示。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;我 = [0, 1, 4, 8, 20, 3]
是 = [10, 7, 4, 18, 20, 3]
张 = [30, 5, 3, 28, 20, 6]
涛 = [5, 6, 2, 38, 20, 7]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2026022801.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;经过这样的向量化以后，我们就可以通过公式算出来每个token之间的距离是近还是远了。比如你一定知道空间中两个点之间的距离，(1, 1, 1) 和 (3, 3, 3)这两个点可以通过公式算出来是3√3 -1。算不出来的回去学高中数学去。&lt;/p&gt;

&lt;p&gt;而大模型的本质，就是一个token一个token的算，后一个字与前一个字距离近，所以前一个字“我”出来以后，就知道要再生成“是”这个字。&lt;/p&gt;

&lt;p&gt;但是上面的逻辑有两个问题需要解决：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;语序对句子的影响，我喜欢你和你喜欢我，这完全是两码事。&lt;/li&gt;
  &lt;li&gt;词语在句子中的含义，比如“老六”，在抖音上可能就是老阴比的意思，但是在你外婆跟你介绍他的七个子女的时候，你肯定不会觉得你六舅舅是老阴比。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;第一个问题，比较好解决，在所有向量模型训练和向量化时，也同步加入所在文本的位置坐标，这样就能备区分不同语序的能力。第二个问题，则由由注意力机制解决。&lt;/p&gt;

&lt;h2 id=&quot;12-注意力机制&quot;&gt;1.2 注意力机制&lt;/h2&gt;

&lt;p&gt;在 Transformer 出现之前，AI 处理文本、语音等时序数据有个致命问题：无法高效处理长文本，且难以捕捉全局关联。类似 Android 开发：就像早期 Android 系统中的 Handler 机制，必须按顺序处理消息队列，前面的任务会阻塞后面的任务。当处理长文本时，前面的信息会随着序列推进逐渐弱化，很难将”它”与”苹果”精准关联；处理几百字的长文案时，更是会出现”梯度消失”，导致模型理解偏差。&lt;/p&gt;

&lt;p&gt;为了解决这些问题，才有大佬参考计算机视觉，引入了注意力机制（Attention）的概念，搭建了完全由注意力机制构成的神经网络——Transformer，也就是大语言模型（Large Language Model，LLM）的鼻祖及核心架构。&lt;/p&gt;

&lt;p&gt;注意力机制的核心思想为当我们关注一张图片，我们往往无需看清楚全部内容而仅将注意力集中在重点部分即可。而在自然语言处理领域，我们往往也可以通过将重点注意力集中在一个或几个token，从而取得更高效高质的计算效果。&lt;/p&gt;

&lt;p&gt;注意力机制有三个核心变量： &lt;strong&gt;Query&lt;/strong&gt; （查询值）、 &lt;strong&gt;Key&lt;/strong&gt; （索引）和 &lt;strong&gt;Value&lt;/strong&gt; （完整内容）。从下面的公式你也能看出来，真正需要理解的主要是 Q 和 K。网上会有大把的文章教你这个公式。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2026022804.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里我们只用代码实现一下这个公式：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;def attention(query, key, value, dropout=None):
    # 获取键向量的维度，键向量的维度和值向量的维度相同
    d_k = query.size(-1) 
    # 计算Q与K的内积并除以根号dk
    # transpose——相当于转置
    scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)
    # Softmax
    p_attn = scores.softmax(dim=-1)
    if dropout is not None:
        p_attn = dropout(p_attn)
        # 采样
     # 根据计算结果对value进行加权求和
    return torch.matmul(p_attn, value), p_attn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;121-自注意力&quot;&gt;1.2.1 自注意力&lt;/h3&gt;

&lt;p&gt;自注意力（self-attention）是注意力机制的一种特殊形式，它允许模型在处理一个序列时，关注序列中的其他位置。&lt;/p&gt;

&lt;p&gt;当Q=K=V时，就称为自注意力机制（Self-Attention）。这在Transformer中非常重要，因为它让模型能够理解句子内部的关系。&lt;/p&gt;

&lt;p&gt;类比：这就像让句子中的每个单词都成为一个搜索关键词，去查询整个句子中其他单词的相关性。&lt;/p&gt;

&lt;p&gt;例如，对于句子”我是张涛”：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;当”我”作为Query时，会发现”是”和”张涛”与它相关性最高&lt;/li&gt;
  &lt;li&gt;当”是”作为Query时，会发现”我”和”张涛”与它相关性最高&lt;/li&gt;
  &lt;li&gt;当”张涛”作为Query时，会发现”我”和”是”与它相关性最高&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在代码实现上，其实就是把Q K V的输入全部统一成一个值&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# attention 为上文定义的注意力计算函数
attention(x, x, x)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;122-多头注意力&quot;&gt;1.2.2 多头注意力&lt;/h3&gt;

&lt;p&gt;多头注意力（Multi-Head Attention）是注意力机制的另一种变体，它允许模型同时关注序列中的多个不同位置。在多头注意力机制中，我们将输入序列分成多个子序列，然后对每个子序列应用自注意力机制，最后将这些子序列的注意力输出拼接起来。也可以将输入序列重复多次，然后每个序列应用不同的注意力机制，最后再将子序列拼接。&lt;/p&gt;

&lt;p&gt;多头注意力机制的优势在于它可以捕捉序列中不同位置之间的不同类型的依赖关系，从而提高模型的表达能力。&lt;/p&gt;

&lt;p&gt;单头注意力只能从一个角度理解句子，而多头注意力可以从多个角度同时理解句子。&lt;/p&gt;

&lt;p&gt;类比：这就像让多个不同领域的专家同时分析同一份文档：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;一位专家关注语法结构&lt;/li&gt;
  &lt;li&gt;一位专家关注语义关系&lt;/li&gt;
  &lt;li&gt;一位专家关注长距离依赖&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最终将所有专家的分析结果整合起来，得到更全面的理解。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import torch.nn as nn
import torch

class MultiHeadAttention(nn.Module):def __init__(self, args: ModelArgs, is_causal=False):# 构造函数# args: 配置对象super().__init__()# 隐藏层维度必须是头数的整数倍，因为后面我们会将输入拆成头数个矩阵assert args.dim % args.n_heads == 0# 每个头的维度，等于模型维度除以头的总数。
        self.head_dim = args.dim // args.n_heads
        self.n_heads = args.n_heads

        # Wq, Wk, Wv 参数矩阵，每个参数矩阵为 n_embd x dim# 这里通过三个组合矩阵来代替了n个参数矩阵的组合，其逻辑在于矩阵内积再拼接其实等同于拼接矩阵再内积，# 不理解的读者可以自行模拟一下，每一个线性层其实相当于n个参数矩阵的拼接
        self.wq = nn.Linear(args.n_embd, self.n_heads * self.head_dim, bias=False)
        self.wk = nn.Linear(args.n_embd, self.n_heads * self.head_dim, bias=False)
        self.wv = nn.Linear(args.n_embd, self.n_heads * self.head_dim, bias=False)# 输出权重矩阵，维度为 dim x dim（head_dim = dim / n_heads）
        self.wo = nn.Linear(self.n_heads * self.head_dim, args.dim, bias=False)# 注意力的 dropout
        self.attn_dropout = nn.Dropout(args.dropout)# 残差连接的 dropout
        self.resid_dropout = nn.Dropout(args.dropout)
        self.is_causal = is_causal

        # 创建一个上三角矩阵，用于遮蔽未来信息# 注意，因为是多头注意力，Mask 矩阵比之前我们定义的多一个维度if is_causal:
            mask = torch.full((1, 1, args.max_seq_len, args.max_seq_len), float(&quot;-inf&quot;))
            mask = torch.triu(mask, diagonal=1)# 注册为模型的缓冲区
            self.register_buffer(&quot;mask&quot;, mask)def forward(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor):# 获取批次大小和序列长度，[batch_size, seq_len, dim]
        bsz, seqlen, _ = q.shape

        # 计算查询（Q）、键（K）、值（V）,输入通过参数矩阵层，维度为 (B, T, n_embed) x (n_embed, dim) -&amp;gt; (B, T, dim)
        xq, xk, xv = self.wq(q), self.wk(k), self.wv(v)# 将 Q、K、V 拆分成多头，维度为 (B, T, n_head, dim // n_head)，然后交换维度，变成 (B, n_head, T, dim // n_head)# 因为在注意力计算中我们是取了后两个维度参与计算# 为什么要先按B*T*n_head*C//n_head展开再互换1、2维度而不是直接按注意力输入展开，是因为view的展开方式是直接把输入全部排开，# 然后按要求构造，可以发现只有上述操作能够实现我们将每个头对应部分取出来的目标
        xq = xq.view(bsz, seqlen, self.n_heads, self.head_dim)
        xk = xk.view(bsz, seqlen, self.n_heads, self.head_dim)
        xv = xv.view(bsz, seqlen, self.n_heads, self.head_dim)
        xq = xq.transpose(1, 2)
        xk = xk.transpose(1, 2)
        xv = xv.transpose(1, 2)# 注意力计算# 计算 QK^T / sqrt(d_k)，维度为 (B, nh, T, hs) x (B, nh, hs, T) -&amp;gt; (B, nh, T, T)
        scores = torch.matmul(xq, xk.transpose(2, 3)) / math.sqrt(self.head_dim)# 掩码自注意力必须有注意力掩码if self.is_causal:assert hasattr(self, &apos;mask&apos;)# 这里截取到序列长度，因为有些序列可能比 max_seq_len 短
            scores = scores + self.mask[:, :, :seqlen, :seqlen]# 计算 softmax，维度为 (B, nh, T, T)
        scores = F.softmax(scores.float(), dim=-1).type_as(xq)# 做 Dropout
        scores = self.attn_dropout(scores)# V * Score，维度为(B, nh, T, T) x (B, nh, T, hs) -&amp;gt; (B, nh, T, hs)
        output = torch.matmul(scores, xv)# 恢复时间维度并合并头。# 将多头的结果拼接起来, 先交换维度为 (B, T, n_head, dim // n_head)，再拼接成 (B, T, n_head * dim // n_head)# contiguous 函数用于重新开辟一块新内存存储，因为Pytorch设置先transpose再view会报错，# 因为view直接基于底层存储得到，然而transpose并不会改变底层存储，因此需要额外存储
        output = output.transpose(1, 2).contiguous().view(bsz, seqlen, -1)# 最终投影回残差流。
        output = self.wo(output)
        output = self.resid_dropout(output)return output
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;13-encoder-decoder架构&quot;&gt;1.3 Encoder-Decoder架构&lt;/h2&gt;

&lt;p&gt;Transformer架构采用了Encoder-Decoder架构，其中Encoder负责对输入序列进行编码，Decoder负责根据Encoder的输出生成输出序列。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2026022802.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;131-encoder&quot;&gt;1.3.1 Encoder&lt;/h3&gt;

&lt;p&gt;Encoder由多个相同的 layer 堆叠而成，每个层包含两个子层：多头自注意力层和前馈神经网络层。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;多头自注意力层&lt;/strong&gt;：多头自注意力层用于捕捉输入序列中不同位置之间的依赖关系。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;前馈神经网络层&lt;/strong&gt;：前馈神经网络层用于对多头自注意力层的输出进行非线性变换。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;多头注意力前面讲过了，前馈层实验更简单，有提供好的api直接使用：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class MLP(nn.Module):
    def __init__(self, dim: int, hidden_dim: int, dropout: float):
        super().__init__()
        # 定义第一层线性变换，从输入维度到隐藏维度
        self.w1 = nn.Linear(dim, hidden_dim, bias=False)
        # 定义第二层线性变换，从隐藏维度到输入维度
        self.w2 = nn.Linear(hidden_dim, dim, bias=False)
        # 定义dropout层，用于防止过拟合
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        # 前向传播函数
        # 首先，输入x通过第一层线性变换和RELU激活函数
        # 最后，通过第二层线性变换和dropout层
        return self.dropout(self.w2(F.relu(self.w1(x))))
    
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样在每个子层之后，都应用了残差连接和层归一化，以加速模型的训练和提高模型的稳定性。一个最简单的 Encode层的实现是这样的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class EncoderLayer(nn.Module):
  &apos;&apos;&apos;Encoder层&apos;&apos;&apos;
    def __init__(self, args):
        super().__init__()
        # 一个 Layer 中有两个 LayerNorm，分别在 Attention 之前和 MLP 之前
        self.attention_norm = LayerNorm(args.n_embd)
        # Encoder 不需要掩码，传入 is_causal=False
        self.attention = MultiHeadAttention(args, is_causal=False)
        self.fnn_norm = LayerNorm(args.n_embd)
        self.feed_forward = MLP(args.dim, args.dim, args.dropout)

    def forward(self, x):
        # Layer Norm
        norm_x = self.attention_norm(x)
        # 自注意力
        h = x + self.attention.forward(norm_x, norm_x, norm_x)
        # 经过前馈神经网络
        out = h + self.feed_forward.forward(self.fnn_norm(h))
        return out
     
# 搭建一个 Encoder，由 N 个 Encoder Layer 组成，在最后会加入一个 Layer Norm 实现规范化：   
class Encoder(nn.Module):
    &apos;&apos;&apos;Encoder 块&apos;&apos;&apos;
    def __init__(self, args):
        super(Encoder, self).__init__() 
        # 一个 Encoder 由 N 个 Encoder Layer 组成
        self.layers = nn.ModuleList([EncoderLayer(args) for _ in range(args.n_layer)])
        self.norm = LayerNorm(args.n_embd)

    def forward(self, x):
        &quot;分别通过 N 层 Encoder Layer&quot;
        for layer in self.layers:
            x = layer(x)
        return self.norm(x)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;132-decoder&quot;&gt;1.3.2 Decoder&lt;/h3&gt;

&lt;p&gt;Decoder也由多个相同的 layer 堆叠而成，每个层包含三个子层：掩码多头自注意力层、多头注意力层和前馈神经网络层。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;掩码多头自注意力层&lt;/strong&gt;：掩码多头自注意力层用于捕捉输出序列中不同位置之间的依赖关系，同时确保模型在生成输出序列时，只能关注到当前位置之前的位置。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;多头注意力层&lt;/strong&gt;：多头注意力层用于捕捉输入序列和输出序列之间的依赖关系。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;前馈神经网络层&lt;/strong&gt;：前馈神经网络层用于对多头注意力层的输出进行非线性变换。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在每个子层之后，也应用了残差连接和层归一化。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class DecoderLayer(nn.Module):
  &apos;&apos;&apos;解码层&apos;&apos;&apos;
    def __init__(self, args):
        super().__init__()
        # 一个 Layer 中有三个 LayerNorm，分别在 Mask Attention 之前、Self Attention 之前和 MLP 之前
        self.attention_norm_1 = LayerNorm(args.n_embd)
        # Decoder 的第一个部分是 Mask Attention，传入 is_causal=True
        self.mask_attention = MultiHeadAttention(args, is_causal=True)
        self.attention_norm_2 = LayerNorm(args.n_embd)
        # Decoder 的第二个部分是 类似于 Encoder 的 Attention，传入 is_causal=False
        self.attention = MultiHeadAttention(args, is_causal=False)
        self.ffn_norm = LayerNorm(args.n_embd)
        # 第三个部分是 MLP
        self.feed_forward = MLP(args.dim, args.dim, args.dropout)

    def forward(self, x, enc_out):
        # Layer Norm
        norm_x = self.attention_norm_1(x)
        # 掩码自注意力
        x = x + self.mask_attention.forward(norm_x, norm_x, norm_x)
        # 多头注意力
        norm_x = self.attention_norm_2(x)
        h = x + self.attention.forward(norm_x, enc_out, enc_out)
        # 经过前馈神经网络
        out = h + self.feed_forward.forward(self.ffn_norm(h))
        return out
        
# 同样的，搭建一个 Decoder 块：
class Decoder(nn.Module):
    &apos;&apos;&apos;解码器&apos;&apos;&apos;
    def __init__(self, args):
        super(Decoder, self).__init__() 
        # 一个 Decoder 由 N 个 Decoder Layer 组成
        self.layers = nn.ModuleList([DecoderLayer(args) for _ in range(args.n_layer)])
        self.norm = LayerNorm(args.n_embd)

    def forward(self, x, enc_out):
        &quot;Pass the input (and mask) through each layer in turn.&quot;
        for layer in self.layers:
            x = layer(x, enc_out)
        return self.norm(x)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;14-代码实现transformer架构&quot;&gt;1.4 代码实现Transformer架构&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2026022803.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;再从完整的结构看一下 Transformer，完成前面的 Encoder、Decoder 搭建以后，就完成了 Transformer 的核心部分，接下来将 Encoder、Decoder 拼接起来再加入 Embedding 层就可以搭建出完整的 Transformer 模型。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Transformer(nn.Module):
   &apos;&apos;&apos;整体模型&apos;&apos;&apos;
    def __init__(self, args):
        super().__init__()
        # 必须输入词表大小和 block size
        assert args.vocab_size is not None
        assert args.block_size is not None
        self.args = args
        self.transformer = nn.ModuleDict(dict(
            wte = nn.Embedding(args.vocab_size, args.n_embd),
            wpe = PositionalEncoding(args),
            drop = nn.Dropout(args.dropout),
            encoder = Encoder(args),
            decoder = Decoder(args),
        ))
        # 最后的线性层，输入是 n_embd，输出是词表大小
        self.lm_head = nn.Linear(args.n_embd, args.vocab_size, bias=False)

        # 初始化所有的权重
        self.apply(self._init_weights)

        # 查看所有参数的数量
        print(&quot;number of parameters: %.2fM&quot; % (self.get_num_params()/1e6,))

    &apos;&apos;&apos;统计所有参数的数量&apos;&apos;&apos;
    def get_num_params(self, non_embedding=False):
        # non_embedding: 是否统计 embedding 的参数
        n_params = sum(p.numel() for p in self.parameters())
        # 如果不统计 embedding 的参数，就减去
        if non_embedding:
            n_params -= self.transformer.wte.weight.numel()
        return n_params

    &apos;&apos;&apos;初始化权重&apos;&apos;&apos;
    def _init_weights(self, module):
        # 线性层和 Embedding 层初始化为正则分布
        if isinstance(module, nn.Linear):
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
            if module.bias is not None:
                torch.nn.init.zeros_(module.bias)
        elif isinstance(module, nn.Embedding):
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
    
    &apos;&apos;&apos;前向计算函数&apos;&apos;&apos;
    def forward(self, idx, targets=None):
        # 输入为 idx，维度为 (batch size, sequence length, 1)；targets 为目标序列，用于计算 loss
        device = idx.device
        b, t = idx.size()
        assert t &amp;lt;= self.args.block_size, f&quot;不能计算该序列，该序列长度为 {t}, 最大序列长度只有 {self.args.block_size}&quot;

        # 通过 self.transformer
        # 首先将输入 idx 通过 Embedding 层，得到维度为 (batch size, sequence length, n_embd)
        print(&quot;idx&quot;,idx.size())
        # 通过 Embedding 层
        tok_emb = self.transformer.wte(idx)
        print(&quot;tok_emb&quot;,tok_emb.size())
        # 然后通过位置编码
        pos_emb = self.transformer.wpe(tok_emb) 
        # 再进行 Dropout
        x = self.transformer.drop(pos_emb)
        # 然后通过 Encoder
        print(&quot;x after wpe:&quot;,x.size())
        enc_out = self.transformer.encoder(x)
        print(&quot;enc_out:&quot;,enc_out.size())
        # 再通过 Decoder
        x = self.transformer.decoder(x, enc_out)
        print(&quot;x after decoder:&quot;,x.size())

        if targets is not None:
            # 训练阶段，如果我们给了 targets，就计算 loss
            # 先通过最后的 Linear 层，得到维度为 (batch size, sequence length, vocab size)
            logits = self.lm_head(x)
            # 再跟 targets 计算交叉熵
            loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-1)
        else:
            # 推理阶段，我们只需要 logits，loss 为 None
            # 取 -1 是只取序列中的最后一个作为输出
            logits = self.lm_head(x[:, [-1], :]) # note: using list [-1] to preserve the time dim
            loss = None

        return logits, loss
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;15-总结&quot;&gt;1.5 总结&lt;/h2&gt;

&lt;p&gt;以下内容由 DeepSeek 生成：&lt;/p&gt;

&lt;p&gt;本文详细介绍了AI大模型的核心技术和实现方法，包括NLP基础概念、Transformer架构、预训练语言模型等内容。通过本文的学习，读者可以从零开始，深入理解AI大模型的工作原理和实现方法。&lt;/p&gt;

&lt;p&gt;AI大模型是当前人工智能领域的研究热点，它具有强大的语言理解和生成能力，正在深刻改变着我们的生活和工作方式。随着技术的不断发展，AI大模型的性能和应用场景将会不断拓展，为人类社会带来更多的便利和创新。&lt;/p&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - PT 站规则扫盲]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/stickies/2025/12/12/01/</br>最近邀请了不少新人，结果好多被封号的，浪费了邀请码实在可惜，特意再写一篇详细的 PT 站规则扫盲贴，帮助新人更好的通过新手任务。 - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2025-12-12]]></pubDate>
        <link><![CDATA[https://kymjs.com/stickies/2025/12/12/01/]]></link>
        <tags>
          
          <tag>生活</tag>
          
        </tags>
        <columnNum><![CDATA[3]]></columnNum>
        <column><![CDATA[思绪万千]]></column>
        <categories>
          
          <category>stickies</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/stickies/2025/12/12/01/</br>&lt;p&gt;最近邀请了不少新人，结果好多被封号的，浪费了邀请码实在可惜，特意再写一篇详细的 PT 站规则扫盲贴，帮助新人更好的通过新手任务。&lt;/p&gt;

&lt;h2 id=&quot;1-pt站是什么&quot;&gt;1. PT站是什么&lt;/h2&gt;

&lt;p&gt;PT（Private Tracker)站就是：一个通过私有链接，在小范围内资源共享服务的网站。所以，&lt;strong&gt;PT 的核心是共享&lt;/strong&gt;。 &lt;br /&gt;
这里的共享，可以是贡献你收藏的资源（电影/软件/电子书/音乐/照片等）也可以是共享你的网络带宽。&lt;/p&gt;

&lt;h2 id=&quot;2-pt的优缺点&quot;&gt;2. PT的优缺点&lt;/h2&gt;

&lt;h3 id=&quot;21-优点&quot;&gt;2.1 优点&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;资源丰富，找资源省时间，一搜就有。&lt;/li&gt;
  &lt;li&gt;下载速度快，没有什么 VIP 的说法，直接就是满速下载。&lt;/li&gt;
  &lt;li&gt;更新速度快，基本上网上只要有，PT就马上有了。&lt;/li&gt;
  &lt;li&gt;质量高，什么蓝光/高清/原版，全部都分类好了。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;22-缺点&quot;&gt;2.2 缺点&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;入门要求高，毕竟是私有邀请制&lt;/li&gt;
  &lt;li&gt;硬件门槛，需要一台能长期开机的电脑或者NAS&lt;/li&gt;
  &lt;li&gt;邀请制，没人邀请你，你就进不了圈子&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;3-pt-站规则&quot;&gt;3. PT 站规则&lt;/h2&gt;

&lt;p&gt;重点来了，无数人都是因为没有遵守规则，直接被封号了。下面以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;M-Team&lt;/code&gt;（馒头PT）为例，介绍 PT 站的规则，其他 PT 站规则都是类似的。&lt;/p&gt;

&lt;p&gt;核心限制有三个：&lt;strong&gt;分享率&lt;/strong&gt;，&lt;strong&gt;新手任务&lt;/strong&gt;，&lt;strong&gt;未登录时间&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;31-分享率&quot;&gt;3.1 分享率&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2025121201.png&quot; alt=&quot;M-Team PT站&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;分享率&lt;/strong&gt; 是一个计算出来的数值，公式很简单：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;分享率 = 上传量 / 下载量
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;大部分PT站对分享率的要求都是不能低于1（也就是上传量不能低于下载量）。馒头相对宽松一点，分享率要求是不能低于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.3&lt;/code&gt;，低于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.6&lt;/code&gt;会警告，低于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.3&lt;/code&gt;立刻封号，没有后悔药。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2025121202.png&quot; alt=&quot;M-Team PT站&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;上传量&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;第一节讲过，PT站的核心是共享。在你下载资源的时候，必须开启上传带宽。学计算机的人应该很清楚是什么意思，就不过多解释了。特别强调一句，大部分下载工具例如：迅雷，浏览器下载都是没有开启上传的，使用这些工具下载也会封号。&lt;/p&gt;

&lt;p&gt;PT 是依靠种子下载和上传的，背后的原理是P2P（点对点下载），所以下载的人越多，下载速度越快（你下载的时候，别人也可以从你电脑上下载你已经下载的部分，这样速度就会越来越快）。&lt;/p&gt;

&lt;p&gt;推荐的下载工具：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transmission&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uTorrent&lt;/code&gt; 或者 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qBittorrent&lt;/code&gt;，相对而言&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qBittorrent&lt;/code&gt;下载速度会快一点，但是更占内存。&lt;/p&gt;

&lt;p&gt;刷上传量最简单的办法：去下载首页最新的 free 资源，这部分资源上传下载都很活跃，很容易刷上传量。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;下载量&lt;/strong&gt;&lt;br /&gt;
从分享率的公式可以看出，要让分享率高只要两个办法，要么下载量低，要么上传量高。&lt;/p&gt;

&lt;p&gt;下载量就是看你下载的资源大小，下载多少，下载量就是多少。但是有几个例外：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2025121203.png&quot; alt=&quot;M-Team PT站&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可以看到，图中两个圈起来的图标，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Free xxh xxmin&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;50%&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;第一个 绿色的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Free&lt;/code&gt; 表示不统计下载量。后面的时间可能有，也可能没有。如果有，表示限时&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Free&lt;/code&gt;，如果没有，表示永久&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Free&lt;/code&gt;。&lt;br /&gt;
不管限时还是永久，只要是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Free&lt;/code&gt;，这一类资源你就可以随便下载，不会触发分享率警告。  但是别超过限时时间，万一限时 1 小时，你家网速又慢，下载 2 小时才能下完，那么后面超过以后的下载量，还是会被统计的。&lt;br /&gt;
所以，新手一般都是先下载&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Free&lt;/code&gt;资源来刷上传量，等上传量起来以后，再下载自己想要的资源。&lt;/p&gt;

&lt;p&gt;第二个 橙色的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;50%&lt;/code&gt;，表示只统计一半的下载量。也就是 100G 的文件，下载完只计算 50G 的下载量。&lt;/p&gt;

&lt;h3 id=&quot;32-新手任务&quot;&gt;3.2 新手任务&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2025121204.png&quot; alt=&quot;M-Team PT站&quot; /&gt;&lt;/p&gt;

&lt;p&gt;几乎所有的 PT 站都会有新手任务，不仅是作为筛选的门槛，也是作为新人上手的学习任务。例如上面是 M-Team 的新人任务。&lt;/p&gt;

&lt;p&gt;上传量和下载量不讲了，达到目标量就行，讲讲第三个魔力值。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;魔力值&lt;/strong&gt; 没什么大用，可以理解为PT站的积分，这些积分可以作为站内消费或者打赏使用，新手任务过了以后就可以不关注了，基本用不上。&lt;/p&gt;

&lt;p&gt;获取魔力值只能通过做种获取，如果你使用的是我前面推荐的几个下载工具，那你下完别动，下载工具自动就会做种了。&lt;/p&gt;

&lt;p&gt;魔力值的获取公式比较复杂，例如这个是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;M-Team&lt;/code&gt;的公式：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2025121205.png&quot; alt=&quot;M-Team PT站&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我知道，你看不懂，听我讲吧。简单概括就是&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;种子越老，获得魔力值越高
种子越大，获得魔力值越高
同时做种人数越少，获得魔力值越高
做种的数量越多，获得魔力值越高
官方种子有额外加成
做种时间超过24小时有额外加成
绑定二次验证码有额外加成
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;33-规则更新&quot;&gt;3.3 规则更新&lt;/h3&gt;

&lt;p&gt;从2026年五一开始，新生任务难度有一些提升，项目还是上面那些项目，但是要求变高了。   &lt;br /&gt;
具体的大家看下面这个信息吧：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2025121207.png&quot; alt=&quot;M-Team PT站&quot; /&gt;&lt;/p&gt;

&lt;p&gt;###3.4 未登录时间&lt;/p&gt;

&lt;p&gt;PT站还有一个不容易被关注，但很容易触发封号的限制，就是长时间没有登录网站。比如馒头要求是40天必须登录一次，要么就是你主动将账号封存，可以把40天延长到90天，但依然需要 90 天登录一次。&lt;/p&gt;

&lt;p&gt;⚠️注意：这里说的登录不是做种，或者下载操作，而是网页端的登录操作。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2025121206.png&quot; alt=&quot;M-Team PT站&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;4-其他&quot;&gt;4 其他&lt;/h2&gt;

&lt;p&gt;关于新手任务：如果家里网络上行带宽本来就低，上传量刷的很慢，还有一些骚操作。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;以通过使用魔力值买上传量。&lt;/li&gt;
  &lt;li&gt;在馒头的实验室里面，有个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;XX模式&lt;/code&gt;。打开以后，可以去下载首页的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;🔞种子&lt;/code&gt;这些种子的活跃度很高，上传量非常容易刷。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;再次强调，PT 站是私有站，加入有门槛。&lt;/p&gt;

]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 使用 RAGFlow 搭建一套 AI 客服知识库]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/code/2025/08/04/01/</br>网上一直有一种声音，叫做AI抢了大部分人的饭碗，同时也提供了大量的新的工作岗位。随着部署使用这次的 RAGFlow，就更加感受到了这一点。 - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2025-08-04]]></pubDate>
        <link><![CDATA[https://kymjs.com/code/2025/08/04/01/]]></link>
        <tags>
          
          <tag>AI</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>code</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/code/2025/08/04/01/</br>&lt;h2 id=&quot;ragflow-是什么&quot;&gt;RAGFlow 是什么&lt;/h2&gt;

&lt;p&gt;RAGFlow 是一款 开源的 RAG（检索增强生成）引擎，专注于通过深度文档理解技术，帮助企业及个人高效处理复杂格式的非结构化数据（如合同、报表、论文等），并生成带精准引用的可信回答。&lt;/p&gt;

&lt;p&gt;上面是 deepseek 给的介绍，再说说我的理解：他就是一个用于检索已有数据，提取关键内容后，整合输出的工具引擎。通俗一点来讲，就是一个比较小范围的AI搜索引擎。    &lt;br /&gt;
本文重心主要介绍使用 RAGFlow 解决实际问题上，下载/安装/部署等前置条件相关，请查看这篇文章：&lt;a href=&quot;https://kymjs.com/code/2025/07/30/01/&quot;&gt;https://kymjs.com/code/2025/07/30/01/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/ragflow001.png&quot; alt=&quot;RAGFlow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;RAGFlow 部署完成以后，可以看到顶部总共有五个TAB，我们重点看【知识库】，【聊天】，【Agent】。&lt;/p&gt;

&lt;h2 id=&quot;知识库&quot;&gt;知识库&lt;/h2&gt;

&lt;p&gt;这个是最核心的部分，用于存储全部的文档数据。文档上传以后，AI会自动解析文档，有多种解析方式，目的就是把文档中的内容记录下来，方便查询的时候，能够快速用文档中的内容回答对应的问题。&lt;/p&gt;

&lt;p&gt;点击右上角的创建知识库，名字可以随便给，比如上面的图，我创建了三个知识库，分别是测试数据，官网的文档，GitHub issue。&lt;/p&gt;

&lt;p&gt;创建完知识库以后，首先要做的第一件事就是设置好知识库对文档的解析方式。也就是下图圈起来的部分。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/ragflow002.png&quot; alt=&quot;RAGFlow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里重点介绍最常用的两种：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;General&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Q&amp;amp;A&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&quot;general-切片&quot;&gt;General 切片&lt;/h3&gt;

&lt;p&gt;“General” 分块方法支持的文件格式为MD、MDX、DOCX、XLSX、XLS (Excel 97-2003)、PPT、PDF、TXT、JPEG、JPG、PNG、TIF、GIF、CSV、JSON、EML、HTML。  此方法将简单的方法应用于块文件：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;系统将使用视觉检测模型将连续文本分割成多个片段。&lt;/li&gt;
  &lt;li&gt;接下来，这些连续的片段被合并成Token数不超过“Token数”的块。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;简单来说就是，首先基于图像分块（比如jpg这类图片）分好块以后，再提取文字或图片内容，将内容合并成一个块。没错，他就是基于块来切片的。&lt;/p&gt;

&lt;p&gt;同时你可以自定义分块的方式，比如这里，默认是根据&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt;也就是换行符来分块，你也可以自定义，根据某个特殊字符来分块，同时支持定义每个块的 token 数。需要注意的是，这个 token 数并不是绝对的，比如如果AI认为此时的块过大，也会自己拆分。&lt;/p&gt;

&lt;p&gt;这种切片方式，通常适合官网文档，或者技术博客这种长篇文章的切片。AI自己提取每段的主要内容，如果有必要，还会生成每段的关键词和对应解决的问题，当然这些关键词和问题你也可以自己手动编辑，用于应对后续问题的回复。&lt;/p&gt;

&lt;h3 id=&quot;qa-切片&quot;&gt;Q&amp;amp;A 切片&lt;/h3&gt;

&lt;p&gt;“Q&amp;amp;A” 分块方法支持 excel 和 csv/txt 文件格式。 这里三种格式我都尝试过，首选推荐用CSV格式，同样字符文档最小。其次Excel。不推荐 txt，解析起来经常错乱。&lt;/p&gt;

&lt;p&gt;如果文件是 excel 格式，则应由两个列组成 没有标题：一个提出问题，另一个用于答案， 答案列之前的问题列。多张纸是 只要列正确结构，就可以接受。
如果文件是 csv/txt 格式 以 UTF-8 编码且用 TAB 作分开问题和答案的定界符。
未能遵循上述规则的文本行将被忽略，并且 每个问答对将被认为是一个独特的部分。&lt;/p&gt;

&lt;h3 id=&quot;知识库解析&quot;&gt;知识库解析&lt;/h3&gt;

&lt;p&gt;QA切片是最精准的切片，一个问题对应一个答案，适合作为首选回答。线上最常用的方式就是首先查找QA知识库，如果没有命中再考虑从其他知识库中寻找答案，如果都没有需要的答案，再返回默认回答或者由大模型回复。 &lt;br /&gt;
要实现这样的逻辑，只需要给对应的知识库设置一个较高的优先级就行了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/ragflow003.png&quot; alt=&quot;RAGFlow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;当上传完文档并已经解析完成后，就可以双击文档，查看解析结果。如果结果的解析不满意，可以手动修改或添加内容。&lt;/p&gt;

&lt;h2 id=&quot;聊天&quot;&gt;聊天&lt;/h2&gt;

&lt;p&gt;聊天的本质是调用chat模型，来跟用户对话。但聊天允许关联任意数量的知识库，可以用于提升对话的准确性，并且允许手动定制上下文参数。可以通过API调用，也可以直接作为 iframe 嵌入到其他网页中。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/ragflow005.png&quot; alt=&quot;RAGFlow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;例如在这个图中，我就直接关联了前面声明的两个知识库【TheRouter-issue】和【TheRouter 官网文档】。  &lt;br /&gt;
对应的提示词也比较简单：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;你是一个智能助手，请总结知识库的内容来回答问题，请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时，你需要让用户自行去官网https://therouter.cn查看文档。   
以下是知识库：
{knowledge}
以上是知识库。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/ragflow004.png&quot; alt=&quot;RAGFlow&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;agent&quot;&gt;Agent&lt;/h2&gt;

&lt;p&gt;如果你之前用过Coze，再看 RAGFlow 的 Agent 应该不会陌生，本质就是一个工作流。以 Coze 这张图为例，可以在输入和输出中定义定制化的过程，通常的做法是先优化用户输入，再在知识库中搜索，如果也没有就在网页中搜索，最后再交给大模型优化内容，再返回给用户。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/ragflow006.png&quot; alt=&quot;RAGFlow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;RAGFlow 中，也基本是这个流程，但是不得不说，Coze 做的非常好，最强大的是有海量的第三方插件可供选择。前段时间也体验了一下 Coze 的开源版本，相比商业版差了太远，主要也是因为丢失了第三方插件这里的能力，剩下的东西太平庸了。&lt;/p&gt;

&lt;p&gt;RAGFlow 也是这个问题，缺少第三方的插件，仅凭官方给出的这些能力，还是太弱了。虽然提供了百度/谷歌/必应这类搜索引擎，但是搜索结果可用性还是太低了，这里必应是因为马上要下线了，其他的原因请自行领悟。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/ragflow007.png&quot; alt=&quot;RAGFlow&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;感受&quot;&gt;感受&lt;/h2&gt;

&lt;p&gt;最后说一下我的感受。网上一直有一种声音，叫做AI抢了大部分人的饭碗，同时也提供了大量的新的工作岗位。随着部署使用这次的 RAGFlow，就更加感受到了这一点。&lt;/p&gt;

&lt;p&gt;这种私有化的知识库，非常适合辅助客服的工作，大部分商业公司的线上客服或者语音客服都可以用 RAGFlow 优化，让客服有更高的效率。&lt;/p&gt;

&lt;p&gt;但同时也面临新的问题，如果要想让对话更精准，就必须有人对知识库做专业的调优。比如我第一次，就是简单粗暴的把官网全部文档直接丢给知识库，使用块切片去执行默认的切片，最后测试，大部分内容都是不可用的。&lt;/p&gt;

&lt;p&gt;如果要想可用，首先得手动二次分片，把内容转换成足够细力度的描述；其次需要修改文档的描述，现阶段大部分文档都是面向人可读性高的，但人可读性高不意味着机器可读性高，比如 TheRouter 官网的这篇文档，&lt;a href=&quot;https://therouter.cn/docs/2022/08/29/01&quot;&gt;https://therouter.cn/docs/2022/08/29/01&lt;/a&gt;。混淆配置部分，大部分Android开发都知道是什么意思，所以文档中只贴了代码，而没有任何文字描述，但如果作为知识库，应该更多解释混淆配置是什么，混淆过程中可能产生什么问题，反倒解决问题可以非常简单，直接引导查看文档，或者返回这个固定的代码。如果没有问题的描述，假设用户问题是：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;我的 app 在开发环境是正常的，但是发布到市场的包无法找到路由表&lt;/code&gt;，AI 在回答的时候就很难关联到这个问题，也不知道这段代码可以解决对应的问题。&lt;/p&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 终于搞定了，本地部署 RAGFlow]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/code/2025/07/30/01/</br>体验了一下 Coze 的开源版本，相比商业版差太多了，准备放弃的时候刚好发现 RAGFlow 不需要 GPU 也可以跑，还只支持 x86，这不就是给我 NAS 空闲资源准备的吗 - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2025-07-30]]></pubDate>
        <link><![CDATA[https://kymjs.com/code/2025/07/30/01/]]></link>
        <tags>
          
          <tag>AI</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>code</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/code/2025/07/30/01/</br>&lt;p&gt;你们都知道，我去年这时候买了个 NAS，后来一直想把这个搞成个服务器来用，期间做了本地的 maven，自己博客的对象存储，Android 各个版本 SDK 的镜像，gradle 镜像等等。&lt;/p&gt;

&lt;p&gt;但其实我的 NAS 配置还挺高的，CPU 和内存现在都还有大量的空闲，一直在想办法把他的性能榨干。&lt;/p&gt;

&lt;p&gt;体验了一下 Coze 的开源版本，相比商业版差太多了，准备放弃的时候刚好发现 RAGFlow 不需要 GPU 也可以跑，还只支持 x86，这不就是给我 NAS 空闲资源准备的吗。&lt;/p&gt;

&lt;h2 id=&quot;安装和部署&quot;&gt;安装和部署&lt;/h2&gt;

&lt;p&gt;首先看看机器性能要求：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;CPU &amp;gt;= 4 核&lt;/li&gt;
  &lt;li&gt;RAM &amp;gt;= 16 GB&lt;/li&gt;
  &lt;li&gt;Disk &amp;gt;= 50 GB&lt;/li&gt;
  &lt;li&gt;Docker &amp;gt;= 24.0.0 &amp;amp; Docker Compose &amp;gt;= v2.26.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这一步很恶心，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;群晖7.2&lt;/code&gt;自带的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose&lt;/code&gt;是2.22的，还得更新一下，更新方法直接 deepseek（这里要求 2.26 的原因是因为 yml 文件中采用了 include 语法，这个语法是 2.26 才加入的。如果不想更新，需要自己改一下 yml 把 include 的内容手动聚合到一个 yml 里面，也是可以的）：&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 下载最新版 Docker Compose（替换系统架构，如 aarch64/armv7/x86_64）&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-L&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://github.com/docker/compose/releases/latest/download/docker-compose-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;uname&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;uname&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; /usr/local/bin/docker-compose

&lt;span class=&quot;c&quot;&gt;# 授予执行权限&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo chmod&lt;/span&gt; +x /usr/local/bin/docker-compose

&lt;span class=&quot;c&quot;&gt;# 验证版本&lt;/span&gt;
docker-compose version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;还有个点，网上很多文章都没有讲：由于 docker 镜像里面依赖了es，所以对内存映射也有要求。像 NAS 这种破机器，mmap最大值默认都是只有1G，肯定不够用。&lt;/p&gt;

&lt;p&gt;首先命令行查看一下自己的大小&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;sysctl vm.max_map_count
 
// 如果 vm.max_map_count 的值小于 262144，需要扩大：
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;vm.max_map_count=262144&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sudo tee&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; /etc/sysctl.conf
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;sysctl &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# 重新加载配置&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;拉依赖&quot;&gt;拉依赖&lt;/h3&gt;

&lt;p&gt;直接从&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github&lt;/code&gt;拉&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/infiniflow/ragflow.git 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;拉完以后，照着官网的操作指南一步一步来&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;ragflow/docker
&lt;span class=&quot;c&quot;&gt;# Use CPU for embedding and DeepDoc tasks:&lt;/span&gt;
docker compose &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; docker-compose.yml up &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# To use GPU to accelerate embedding and DeepDoc tasks:&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# docker compose -f docker-compose-gpu.yml up -d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这一步比较麻烦，在NAS上部署的时候得改一下，首先是群晖只有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose&lt;/code&gt;并没有 docker 命令，其次我也不知道是什么原因，在我的机器上，一直没有 -f 命令，即便我更新了版本以后也提示没有。&lt;/p&gt;

&lt;p&gt;后来查了一下，不加 -f 默认执行的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yaml&lt;/code&gt;其次执行的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yml&lt;/code&gt;所以索性不加了，就按照最正常的命令执行也可以：&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;docker-compose up &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;直接启动大概率会报错 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ependency failed to start: container ragflow-mysql is unhealthy&lt;/code&gt;，还是因为 NAS 的机器性能不如专业服务器，mysql启动速度太慢了，超过了脚本等待时长，在命令行里面会报错。 &lt;br /&gt;
但实际上，等一会再重新手动把服务起一下就行了。&lt;/p&gt;

&lt;p&gt;具体参考 GitHub 这个 issue。  &lt;a href=&quot;https://github.com/infiniflow/ragflow/issues/8958&quot;&gt;https://github.com/infiniflow/ragflow/issues/8958&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;启动服务&quot;&gt;启动服务&lt;/h2&gt;

&lt;p&gt;当各个服务都正常启动以后，打开网页应该就能看到如下界面。默认是80端口，当然在公网上得改端口号，直接在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yaml&lt;/code&gt;中修改成非标端口即可。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/ragflow.png&quot; alt=&quot;RagFlow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这个时候，不要着急去点注册，还是得等一会，不然会报502，造成点注册点登录都没有反应。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/ragflow2.png&quot; alt=&quot;RagFlow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在这里我就踩了个坑，到处找原因，包括把 nginx 的配置都整个看了一遍也没发现问题，还是通过 nginx 里加了一条日志，才发现原来是 server 还没启动，造成在 docker 里面调度不到这台容器。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server {
    listen 80;
    server_name _;
    root /ragflow/web/dist;

    location ~ ^/(v1|api) {
        proxy_pass http://ragflow:9380;
        include proxy.conf;
    }
。。。
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;就是这里的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://ragflow:9380&lt;/code&gt;，正常情况 docker 会自动 DNS，把 ragflow DNS 到实际的虚拟机上，但实际上页面打开的时候，虚拟机的服务还没有启动，造成 http 502&lt;/p&gt;

&lt;p&gt;具体可看这个issue ：&lt;a href=&quot;https://github.com/infiniflow/ragflow/issues/4815&quot;&gt;https://github.com/infiniflow/ragflow/issues/4815&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;配置大模型&quot;&gt;配置大模型&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/ragflow3.png&quot; alt=&quot;RagFlow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;RAGFlow至少需要配置两个模型，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chat&lt;/code&gt;模型负责上下文对话，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;embedding&lt;/code&gt;模型负责解析和推测。 &lt;br /&gt;
网上大部分都推荐本地配 Ollama，让 RAGFlow 调用，这里我没有这么做，原因还是，我 NAS 的性能不够，搞不了。所以我使用的是字节的火山方舟，没别的原因，只是因为他的文档比较多，接入起来方便而已。&lt;/p&gt;

&lt;h2 id=&quot;导入知识库文档&quot;&gt;导入知识库文档&lt;/h2&gt;

&lt;p&gt;知识库支持的文件类型是很多的，基本上常见的图片/文字/网页/pdf 都能做为知识库导入，但是如果是图片的话，上面的大模型还得要配置一个图片大模型才行。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/ragflow4.png&quot; alt=&quot;RagFlow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里我是纯 markdown 文本上传作为知识库，上传以后，需要点击一下进行解析，当所有文本解析完成以后，就可以在后续的 Chat/Agent 直接使用了。&lt;/p&gt;

&lt;p&gt;PS：这破解析，真费token，22份文档，到现在免费额度花完，已经花了我1块钱了，还没解析完。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/ragflow5.png&quot; alt=&quot;RagFlow&quot; /&gt;&lt;/p&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 阿富汗-中亚最倒霉国家]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/history/2025/06/13/01/</br>说起阿富汗，很多人第一印象是新闻里的战争、爆炸、难民。但这片土地其实有着极其悠久的历史，甚至可以说，了解阿富汗的历史，就能理解为什么这么多帝国在这里折戟。 - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220502_06.jpg]]></image>
        <pubDate><![CDATA[2025-06-13]]></pubDate>
        <link><![CDATA[https://kymjs.com/history/2025/06/13/01/]]></link>
        <tags>
          
          <tag>阿富汗史</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>history</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/history/2025/06/13/01/</br>&lt;h2 id=&quot;背景&quot;&gt;背景&lt;/h2&gt;

&lt;p&gt;说起来阿富汗，很多人的第一印象可能是新闻里的战争、爆炸、难民，以及那个著名的称号——”帝国的坟场”。&lt;/p&gt;

&lt;p&gt;但说实话，在研究这个国家之前，我对它的了解也仅限于此。&lt;/p&gt;

&lt;p&gt;阿富汗这个地方很有意思，它地处中亚、南亚、西亚的交汇处，北边是苏联（现在是中亚五国），东边是中国，南边是巴基斯坦和英国殖民时期的印度，西边是伊朗。你看这个地理位置，谁控制了阿富汗，谁就相当于卡住了几个文明板块的脖子。&lt;/p&gt;

&lt;p&gt;所以这个地方，从古至今就没消停过。&lt;/p&gt;

&lt;h2 id=&quot;文明的十字路口&quot;&gt;文明的十字路口&lt;/h2&gt;

&lt;p&gt;公元前500年左右，也就是我们的春秋战国时期，阿富汗地区就已经有了文明。那时候这片土地上有两个著名的城邦国家——大夏和巴克特里亚。&lt;/p&gt;

&lt;p&gt;大夏这个名字你可能不熟，但说到它的另一个名字你肯定知道：吐火罗。&lt;/p&gt;

&lt;p&gt;嗯，对，就是西游记里面那个差点把唐僧蒸了吃的妖怪他国。&lt;/p&gt;

&lt;p&gt;那时候的阿富汗，是丝绸之路的重要节点。商人们从中国出发，经过这里，把丝绸运往西方。中国人换回来了葡萄、石榴、核桃这些东西——你吃的每一个核桃，其实都是丝绸之路的遗产。&lt;/p&gt;

&lt;p&gt;后来亚历山大大帝打过来，在阿富汗建立了希腊化王国。再往后，孔雀王朝、塞琉古帝国、贵霜帝国，一个接一个地统治这片土地。&lt;/p&gt;

&lt;p&gt;你看，说阿富汗是”文明的十字路口”，真不是夸张。&lt;/p&gt;

&lt;h2 id=&quot;伊斯兰化与蒙古铁骑&quot;&gt;伊斯兰化与蒙古铁骑&lt;/h2&gt;

&lt;p&gt;公元7世纪，阿拉伯帝国崛起，伊斯兰教开始传入阿富汗。跟其他地方一样，伊斯兰教以一种近乎碾压的态势，迅速取代了当地的佛教、拜火教。&lt;/p&gt;

&lt;p&gt;有意思的是，当年玄奘去印度取经的时候，曾经路过阿富汗，那时候这里还是佛教国家。他在《大唐西域记》里记载了阿富汗境内的十几个佛教国家。&lt;/p&gt;

&lt;p&gt;几十年后，伊斯兰教就彻底改变了这片土地。&lt;/p&gt;

&lt;p&gt;到了13世纪，成吉思汗的蒙古铁骑横扫欧亚大陆。阿富汗也没能幸免，蒙古人把这里杀得十室九空。有多惨？人口锐减了90%以上，很多繁华的城市直接变成废墟。&lt;/p&gt;

&lt;p&gt;也是从这时候起，阿富汗的经济彻底从商业文明退化成了游牧文明。&lt;/p&gt;

&lt;h2 id=&quot;近代英俄大博弈&quot;&gt;近代：英俄大博弈&lt;/h2&gt;

&lt;p&gt;时间来到19世纪，这时候的阿富汗迎来了两个超级邻居——北边的沙俄帝国和南边的英属印度。&lt;/p&gt;

&lt;p&gt;就像现在的中美博弈一样，当年是英俄大博弈（The Great Game）。阿富汗就是这场博弈的棋盘。&lt;/p&gt;

&lt;p&gt;英国人的想法很简单：不能让俄罗斯人南下威胁到英属印度。所以英国三次入侵阿富汗（1839-1842、1878-1880、1919年）。&lt;/p&gt;

&lt;p&gt;前两次战争，中国的历史书几乎没提过，但你猜阿富汗死了多少人？&lt;/p&gt;

&lt;p&gt;第一次英阿战争，英国3万人的军队全军覆没，只有一个英国医生逃回印度。这个故事后来被写成了小说《克什米尔公主号》——不对，是《阿富汗战争的一面》。&lt;/p&gt;

&lt;p&gt;第二次英阿战争，阿富汗丢掉了部分外交权，但保住了独立。&lt;/p&gt;

&lt;p&gt;1919年第三次战争之后，英国终于承认阿富汗完全独立。&lt;/p&gt;

&lt;p&gt;你看，这个”帝国的坟场”名号，真不是白叫的。&lt;/p&gt;

&lt;h2 id=&quot;王朝更替从王国到共和国&quot;&gt;王朝更替：从王国到共和国&lt;/h2&gt;

&lt;p&gt;一战后，阿富汗终于独立，建立了君主制。&lt;/p&gt;

&lt;p&gt;1929年，一个叫阿曼努拉汉的国王登基。这哥们很有意思，他是虔诚的穆斯林，但在位的第三天就宣布要现代化，强制摘掉女性的面罩，规定一夫一妻制，建立世俗学校。&lt;/p&gt;

&lt;p&gt;结果呢？三年后被宗教势力推翻，自己逃到意大利流亡了。&lt;/p&gt;

&lt;p&gt;后来阿富汗进入了一个相对稳定的时期，直到1973年。最后一个国王查希尔沙被他的前首相达乌德推翻，阿富汗废除君主制，建立共和国。&lt;/p&gt;

&lt;p&gt;达乌德也是个悲剧人物，他推行社会主义政策，结果1978年被苏联支持的政变推翻，自己也被杀了。&lt;/p&gt;

&lt;h2 id=&quot;苏联入侵十年战争&quot;&gt;苏联入侵：十年战争&lt;/h2&gt;

&lt;p&gt;1979年圣诞节，苏联入侵阿富汗。这一打就是十年。&lt;/p&gt;

&lt;p&gt;苏联人的目标是扶植一个亲苏政权，控制这个战略要地。但他们没想到的是，阿富汗的抵抗力量如此顽强。&lt;/p&gt;

&lt;p&gt;这些抵抗组织，有个共同的名字：圣战者（Mujahideen）。&lt;/p&gt;

&lt;p&gt;美国、中国、沙特、巴基斯坦都支持这些圣战者。美国的毒刺导弹、英国的狙击枪、中国的56式冲锋枪，通过巴基斯坦的渠道源源不断地进入阿富汗。&lt;/p&gt;

&lt;p&gt;十年时间，苏联人死了15000多人，耗资200多亿美元，最终还是撤了。&lt;/p&gt;

&lt;p&gt;1989年，最后一个苏联士兵离开阿富汗。&lt;/p&gt;

&lt;p&gt;有意思的是，苏联人撤军两年后，自己就解体了。&lt;/p&gt;

&lt;h2 id=&quot;塔利班与基地组织&quot;&gt;塔利班与基地组织&lt;/h2&gt;

&lt;p&gt;苏联走了，但阿富汗的灾难没结束。圣战者们开始内战，打得一塌糊涂。&lt;/p&gt;

&lt;p&gt;这时候，一个叫奥马尔的神学士出现了。&lt;/p&gt;

&lt;p&gt;1994年，奥马尔在阿富汗南部创立了塔利班。”塔利班”在普什图语里是”学生”的意思，主要是一群宗教学校的学生。&lt;/p&gt;

&lt;p&gt;1996年，塔利班攻占喀布尔，建立了伊斯兰酋长国。&lt;/p&gt;

&lt;p&gt;塔利班的统治极其保守：女性不能上学、不能工作；音乐、电影全部禁止；小偷会被砍手。&lt;/p&gt;

&lt;p&gt;但美国人支持过他们——因为塔利班当时反苏。&lt;/p&gt;

&lt;p&gt;1999年，本·拉登在阿富汗建立了基地组织训练营。2001年9月11日，纽约世贸中心轰然倒塌。&lt;/p&gt;

&lt;p&gt;然后就是美国入侵阿富汗。&lt;/p&gt;

&lt;h2 id=&quot;美国的二十年&quot;&gt;美国的二十年&lt;/h2&gt;

&lt;p&gt;2001年10月7日，美国以”反恐”为名入侵阿富汗。不到两个月，塔利班政权被推翻。&lt;/p&gt;

&lt;p&gt;小布什说：”我们要在阿富汗建立民主。”&lt;/p&gt;

&lt;p&gt;奥巴马说：”我们要在阿富汗取得胜利。”&lt;/p&gt;

&lt;p&gt;特朗普说：”我们要体面地撤军。”&lt;/p&gt;

&lt;p&gt;拜登说：”美国的任务将在8月31日结束。”&lt;/p&gt;

&lt;p&gt;2021年8月30日，最后一个美国士兵离开阿富汗。&lt;/p&gt;

&lt;p&gt;二十年，美国死了2500多人，花了2万多亿美元，扶植的政府撑了不到三个月就垮了。&lt;/p&gt;

&lt;p&gt;塔利班重新接管阿富汗。&lt;/p&gt;

&lt;p&gt;有意思的是，美军撤离前夜，喀布尔机场发生自杀式爆炸，170个阿富汗平民死亡，其中13个是美国人。&lt;/p&gt;

&lt;h2 id=&quot;总结&quot;&gt;总结&lt;/h2&gt;

&lt;p&gt;写了这么多，回头看看，其实阿富汗的悲剧，有几个很明显的规律：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第一，地缘政治是大国的游戏，小国是棋子。&lt;/strong&gt; 英俄博弈、美苏博弈、美国独霸——每一次大国竞争，阿富汗都是战场。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第二，外来模式很难在这片土地生根。&lt;/strong&gt; 民主、共和、社会主义、伊斯兰法——阿富汗试过了所有制度，但没有一种能真正统一这片土地。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第三，地理塑造了这个国家的性格。&lt;/strong&gt; 山地阻隔、交通不便，使得中央政府很难控制地方，部落自治才是阿富汗的常态。&lt;/p&gt;

&lt;p&gt;某种程度上，阿富汗像极了中国的春秋战国时期——没有哪个霸主能真正统一，各地都有自己的王。&lt;/p&gt;

&lt;p&gt;只不过，我们的战国最后走向了大一统，而阿富汗至今还在等待它的”秦始皇”。&lt;/p&gt;

&lt;p&gt;或者说，在这片土地上，”秦始皇”可能永远不会来。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;参考资料：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;《阿富汗史》- 路易·杜普雷&lt;/li&gt;
  &lt;li&gt;《塔利班》- 艾哈迈德·拉希德&lt;/li&gt;
  &lt;li&gt;《烧船》- 拉贾·谢林&lt;/li&gt;
&lt;/ul&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 【HarmonyOS】TheRouter 鸿蒙版新手入门]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/code/2025/03/23/01/</br>TheRouter 鸿蒙版是货拉拉基于HMRouter深度定制的开源路由框架，提供了 Android、iOS、Harmony 三端高一致性使用，在支持平台化应用实现组件化、跨模块调用、动态化等功能的集成等功能基础上，支持动态路由下发、编译时安全检查、路由Path一对多等高度动态能力。 - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2025-03-23]]></pubDate>
        <link><![CDATA[https://kymjs.com/code/2025/03/23/01/]]></link>
        <tags>
          
          <tag>TheRouter</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>code</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/code/2025/03/23/01/</br>&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 鸿蒙版是货拉拉基于HMRouter深度定制的开源路由框架，提供了 Android、iOS、Harmony 三端高一致性使用，在支持平台化应用实现组件化、跨模块调用、动态化等功能的集成等功能基础上，支持动态路由下发、编译时安全检查、路由Path一对多等高度动态能力。&lt;/p&gt;

&lt;p&gt;Github: &lt;a href=&quot;https://github.com/HuolalaTech/hll-wp-therouter-harmony/&quot;&gt;https://github.com/HuolalaTech/hll-wp-therouter-harmony/&lt;/a&gt; &lt;br /&gt;
官网：&lt;a href=&quot;http://therouter.cn/&quot;&gt;http://therouter.cn/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;0开始之前&quot;&gt;0、开始之前&lt;/h2&gt;

&lt;p&gt;如果你的是新项目，请先记住一点：&lt;strong&gt;plugin、router 两个依赖的版本号必须保持一致&lt;/strong&gt;，请继续往下看接入步骤。&lt;/p&gt;

&lt;h2 id=&quot;01查看最新版本&quot;&gt;0.1、查看最新版本&lt;/h2&gt;

&lt;p&gt;TheRouter 的版本分为两种，稳定版和 rc版，一般不追求新功能我们就用稳定版就行，可以在官网看到最新的版本号和各种版本的说明：&lt;a href=&quot;https://therouter.cn/docs/2022/09/06/01&quot;&gt;https://therouter.cn/docs/2022/09/06/01&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;一接入&quot;&gt;一、接入&lt;/h2&gt;

&lt;p&gt;在工程根目录命令行引入依赖库和插件库（必须全部依赖，不能只使用其中一个）。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 引入代码库依赖
ohpm i @therouter/library   

// 引入插件依赖
npm i @therouter/plugin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;第一步接入编译插件&quot;&gt;第一步：接入编译插件&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;打开项目根目录的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hvigor/hvigor-config.json5&lt;/code&gt;，检查 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dependencies&lt;/code&gt; 中是否已经加入了依赖，一般为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;@therouter/plugin&quot;: &quot;x.x.x&quot;&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;打开工程 &lt;strong&gt;所有&lt;/strong&gt; 模块（hsp、hap、har）的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hvigorfile.ts&lt;/code&gt; 文件。&lt;/li&gt;
  &lt;li&gt;在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plugins&lt;/code&gt; 中加入如下对应的依赖&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 如果是 hap，则类似如下依赖&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hapPlugin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;@therouter/plugin&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hapPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 如果是 har，则类似如下依赖&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;harPlugin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;@therouter/plugin&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;harPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 如果是 hsp，则类似如下依赖&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hspPlugin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;@therouter/plugin&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hspPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;第二步检查依赖是否引入&quot;&gt;第二步：检查依赖是否引入&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;打开 &lt;strong&gt;工程根目录&lt;/strong&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;oh-package.json5&lt;/code&gt; 文件。&lt;/li&gt;
  &lt;li&gt;检查 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dependencies&lt;/code&gt; 中，是否已经加入了依赖，一般为：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;@therouter/library&quot;: &quot;x.x.x&quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;二使用&quot;&gt;二、使用&lt;/h2&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;21-初始化&quot;&gt;2.1、 初始化&lt;/h3&gt;

&lt;p&gt;在项目入口的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIAbility&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onCreate()&lt;/code&gt; 中加入如下代码：&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;onCreate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;want:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Want&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;launchParam:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AbilityConstant&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;LaunchParam&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;TheRouter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;22-定义页面容器&quot;&gt;2.2、 定义页面容器&lt;/h3&gt;

&lt;p&gt;TheRouter 按照华为推荐方案，基于系统 Navigation 实现，所以必须在页面中定义一个容器项 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouterPage&lt;/code&gt;，建议创建一个完全新的类作为入口并在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/resources/base/profile/main_pages.json&lt;/code&gt; 中配置这个类，复制如下代码：&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;TheRouterPage&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@therouter/library&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;Entry&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;Component&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Index&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;RelativeContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nc&quot;&gt;TheRouterPage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;stackId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;XXXX&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;//【必传】可以自定义当前stack的名字，每个stack必须唯一&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// 【必传】当前应用的首页 path，推荐按照一定格式定义页面path&lt;/span&gt;
		&lt;span class=&quot;c1&quot;&gt;// 还有很多可选参数，详情请见文档&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;100%&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;100%&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;23声明路由&quot;&gt;2.3、声明路由&lt;/h3&gt;

&lt;p&gt;给需要跳转的页面加上路由表声明&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Route&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;http://therouter.com/home&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HomePage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;xxx&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;24发起跳转&quot;&gt;2.4、发起跳转&lt;/h3&gt;

&lt;p&gt;在需要跳转页面的位置调用如下代码：&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;TheRouter&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://therouter.com/home&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;  
	&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;&apos;k&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sc&quot;&gt;&apos;v&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 向落地页传参数（如果没参数，可不调用）&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;hello:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 另一种方式传参&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;navigation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;25获取页面传参与返回给上个页面参数&quot;&gt;2.5、获取页面传参与返回给上个页面参数&lt;/h3&gt;

&lt;p&gt;接收有两种形式：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;通过注解自动接收，默认支持 String 和8种基本数据类型，也支持自定义对象的解析&lt;/li&gt;
  &lt;li&gt;通过代码从路由中获取&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;使用注解接收对象时，必须调用 TheRouter.inject(this) 。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 第一种：使用注解自动填充&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// 允许解析成8种基本数据类型或对应封装类&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;Autowired&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;key1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&apos;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 允许自定义传参key，如果不传默认是变量名作为key&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;Autowired&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;key1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&apos;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 使用注解接收对象时，必须调用，建议放在 aboutToApper() 中调用。&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;TheRouter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  
  
  
&lt;span class=&quot;c1&quot;&gt;// 第二种：通过代码从路由中获取&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// 可以在任何方法中调用，getCurrentParam() 返回值是个ESObject&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;TheRouter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getCurrentParam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;   
  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;向上一个页面返回参数，在发起跳转的位置，需要手动声明接收到参数时的处理方法，例如 如下代码：&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 发起跳转：&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;TheRouter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;BaseConstant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PAGE_A1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;navigation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;onResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;routeItem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;popInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;popInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;获取到上个页面返回的数据：&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 向上一个页面返回参数   &lt;/span&gt;

&lt;span class=&quot;nf&quot;&gt;onBackPress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 实际业务中，pop() 也可以是点击某个按钮以后执行。&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;TheRouter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;withString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;向上个页面传参数&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;// 因为上面调用了 pop()，这里不需要重复调用，所以 return true。&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;26生命周期方法&quot;&gt;2.6、生命周期方法&lt;/h3&gt;

&lt;p&gt;TheRouter 为每个 struct  component 内置了 page 具备的全部生命周期方法。意味着，你不再需要像 HMRouter 那样，自己定义全局回调的方式去实现生命周期拦截。&lt;/p&gt;

&lt;p&gt;例如，你可以用下面方法做一个“再次点击返回关闭页面”的需求。&lt;br /&gt;
另外附带部分生命周期方法展示（未列举全，但应该够用了，其他的生命周期方法可以自行尝试）：&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;注意&lt;/strong&gt;：要实现生命周期方法，必须调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter.inject(this)&lt;/code&gt;，建议放在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aboutToApper()&lt;/code&gt; 中调用。&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;TheRouter&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@therouter/library&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BaseConstant&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;Route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BaseConstant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PAGE_A2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;Component&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BusinessAMainPage&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;100%&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;100%&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;////////////////////////////////// 如果自定义生命周期方法，必须调用 TheRouter.inject(this) ////////////////////////////////&lt;/span&gt;
  
  &lt;span class=&quot;nf&quot;&gt;aboutToAppear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// 自定义生命周期必须调用 inject。&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;TheRouter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;aboutToAppear business_aMainPage&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 另附一个拦截返回的使用示例&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;onBackPress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lastTime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getUIContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getPromptAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;showToast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`1s内按两次才关闭页面，当前标题&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lastTime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;aboutToDisappear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;aboutToDisappear business_aMainPage&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;aboutToRecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;aboutToRecycle business_aMainPage&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;onWillApplyTheme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Theme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;onWillApplyTheme business_aMainPage&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;onPageShow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;onPageShow business_aMainPage&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;onPageHide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;onPageHide business_aMainPage&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;onFormRecycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;onFormRecycle business_aMainPage&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;onFormRecover&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;statusData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;onFormRecover business_aMainPage&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;onNewParam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ESObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;onNewParam business_aMainPage&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;附：部分关键生命周期方法图&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20250323_01.png&quot; alt=&quot;HarmonyOS lifecycle&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;27release-混淆规则&quot;&gt;2.7、Release 混淆规则&lt;/h3&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;rules&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;keep_all_generated_content&quot;&lt;/span&gt;,
      &lt;span class=&quot;s2&quot;&gt;&quot;match&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;classes&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;main/ets/generated/*&quot;&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,
          &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;main/ets/generated/**/*&quot;&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,
      &lt;span class=&quot;s2&quot;&gt;&quot;keep&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;class&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;access&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;*&quot;&lt;/span&gt;,
          &lt;span class=&quot;s2&quot;&gt;&quot;member&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;&quot;methods&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;
              &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;s2&quot;&gt;&quot;access&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;*&quot;&lt;/span&gt;
              &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;,
            &lt;span class=&quot;s2&quot;&gt;&quot;fields&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;
              &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;s2&quot;&gt;&quot;access&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;*&quot;&lt;/span&gt;
              &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;三问题排查万能公式&quot;&gt;三、问题排查万能公式&lt;/h2&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;在使用过程中，如果发现服务无法调用，或返回为空的时候。通常的排查方式都是以下三步：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;首先观察接入环境。插件是否和依赖库版本一致，插件是否生效了。在我们内部使用的过程中，经常发现，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;oh-package.json5&lt;/code&gt; 中引入的明明是新版本，但是实际编译时使用的还是老版本，猜测应该是构建工具的问题。在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;编译时，日志的第一行就会有当前编译实际使用的版本号，如果和你预期不一致时，需要再执行一下命令行：&lt;/p&gt;

    &lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; // 引入代码库依赖
 ohpm i @therouter/library   

 // 引入插件依赖
 npm i @therouter/plugin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;&lt;br /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;上一步排查完依然无法调用时，就要考虑一下是不是代码使用姿势问题了。比如添加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@ServiceProvider&lt;/code&gt;的服务提供者必须实现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IServiceProvider&lt;/code&gt;接口。服务提供者注解参数里面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serviceName&lt;/code&gt;是不是和使用方传入的一致。注：如果多次添加重复serviceName，框架会保证安全，在编译时报错。&lt;/p&gt;

    &lt;p&gt;&lt;br /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如果上两步都不能解决你的问题，那就需要进源码通过断点调试进一步明确问题出在哪里了。首先从&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter.init()&lt;/code&gt;开始找，看看路由表读取是否正确，再看服务Map里面是否有记录成功，再看调用的时候传入的是否正确，一步一步排查。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;四鸿蒙路由源码调试步骤&quot;&gt;四、鸿蒙路由源码调试步骤&lt;/h2&gt;

&lt;h3 id=&quot;41clone-仓库&quot;&gt;4.1、clone 仓库&lt;/h3&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;使用命令行：&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone git@github.com:HuolalaTech/hll-wp-therouter-harmony.git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;42创建-build-profile&quot;&gt;4.2、创建 build-profile&lt;/h3&gt;

&lt;p&gt;在项目根目录中，新建一个名为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build-profile.json5&lt;/code&gt; 的文件，文件内加入如下内容。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;app&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;signingConfigs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; 
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;签名部分需要手动从官网获取自己的开发者信息&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;products&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;signingConfig&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;xxxxx填上面的签名信息&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;compatibleSdkVersion&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;可根据自己SDK情况填写&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;runtimeOS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;HarmonyOS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;buildOption&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;strictMode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;caseSensitiveCheck&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;useNormalizedOHMUrl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;buildModeSet&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;debug&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;release&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;modules&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;entry&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;srcPath&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;./entry&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;targets&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;applyToProducts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;therouter&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;srcPath&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;./therouter&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;business_a&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;srcPath&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;./business_a&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;business_b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;srcPath&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;./business_b&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;base&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;srcPath&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;./base&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;43创建-hvigor-构建环境&quot;&gt;4.3、创建 hvigor 构建环境&lt;/h3&gt;

&lt;p&gt;在工程根目录新建一个名为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hvigor&lt;/code&gt; 的文件夹&lt;br /&gt;
文件夹内新建一个名为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hvigor-config.json5&lt;/code&gt; 的文件，文件内加入如下内容：&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;modelVersion&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;根据实际情况填写版本号&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;@therouter/plugin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;根据实际情况填写版本号&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;execution&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;analyze&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;normal&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Define&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;the&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;analyze&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;mode.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Value:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;normal&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;advanced&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Default:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;normal&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;*/&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;daemon&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                          &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Enable&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;daemon&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;compilation.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Value:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Default:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;*/&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;incremental&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Enable&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;incremental&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;compilation.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Value:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Default:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;*/&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;parallel&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                        &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Enable&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;parallel&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;compilation.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Value:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Default:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;*/&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;typeCheck&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                      &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Enable&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;typeCheck.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Value:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Default:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;*/&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;logging&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;level&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;info&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                          &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Define&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;the&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;level.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Value:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;debug&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;info&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;warn&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Default:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;info&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;*/&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;debugging&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;stacktrace&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                      &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Disable&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;stacktrace&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;compilation.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Value:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Default:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;*/&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;nodeOptions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;maxOldSpaceSize&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                  &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Enable&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;nodeOptions&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;maxOldSpaceSize&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;compilation.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Unit&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;M.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Used&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;the&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;daemon&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;process.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Default:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;*/&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;exposeGC&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                         &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Enable&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;garbage&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;explicitly.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Default:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;*/&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 可能是 AGP8 编译最快的方案]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/code/2024/10/31/01/</br>文章围绕 TheRouter 的 AGP8 编译提速展开，指出 toTransform()方法编译慢且无法增量编译，提出多种解决思路，包括结合 AsmClassVisitorFactory 等，最终通过 toGet()方法、内存缓存及对比等优化，实现增量编译和保障产物结果。  - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2024-10-31]]></pubDate>
        <link><![CDATA[https://kymjs.com/code/2024/10/31/01/]]></link>
        <tags>
          
          <tag>TheRouter</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>code</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/code/2024/10/31/01/</br>&lt;h3 id=&quot;背景&quot;&gt;背景&lt;/h3&gt;

&lt;p&gt;AGP8 的变更应该很多人都知道了，移除了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transform API&lt;/code&gt;，所以很多 class 操作类的插件代码都需要改了。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;在开发的时候就支持了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGP8&lt;/code&gt;，使用的也是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gradle&lt;/code&gt;提供的标准 API。&lt;/p&gt;

&lt;p&gt;详细可见官方示例仓库：&lt;a href=&quot;https://github.com/android/gradle-recipes/blob/agp-8.7/transformAllClasses/build-logic/plugins/src/main/kotlin/CustomPlugin.kt&quot;&gt;https://github.com/android/gradle-recipes/blob/agp-8.7/transformAllClasses/build-logic/plugins/src/main/kotlin/CustomPlugin.kt&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-groovy highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;AppPlugin:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Queries for the extension set by the Android Application plugin.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// This is the second of two entry points into the Android Gradle plugin&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;androidComponents&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;extensions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getByType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;ApplicationAndroidComponentsExtension:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Registers a callback to be called, when a new variant is configured&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;androidComponents&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;onVariants&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;variant&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;taskProvider&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ModifyClassesTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;${variant.name}ModifyClasses&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Register modify classes task&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;variant&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;artifacts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forScope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ScopedArtifacts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Scope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;PROJECT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;taskProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toTransform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;ScopedArtifact&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;CLASSES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;nl&quot;&gt;ModifyClassesTask:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;allJars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;nl&quot;&gt;ModifyClassesTask:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;allDirectories&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;nl&quot;&gt;ModifyClassesTask:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;问题&quot;&gt;问题&lt;/h3&gt;

&lt;p&gt;这里使用的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toTransform()&lt;/code&gt;这个方法替代老版本的 API，这么做以后有个问题，就是编译速度会非常非常慢，尤其是工程里代码量越大，编译速度越慢，原因我们先看看这个方法的定义。&lt;/p&gt;

&lt;div class=&quot;language-groovy highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * Transform the current version of the [type] artifact into a new version. The order in which
 * the transforms are applied is directly set by the order of this method call. First come,
 * first served, last one provides the final version of the artifacts.
 *
 * @param type the [ScopedArtifact] to transform.
 * @param inputJars lambda that returns a [ListProperty] or [RegularFile] that will be used to
 * set all incoming files for this artifact type.
 * @param inputDirectories lambda that returns a [ListProperty] or [Directory] that will be used
 * to set all incoming directories for this artifact type.
 * @param into lambda that returns the [Property] used by the [Task] to save the transformed
 * element. The [Property] value will be automatically set by the Android Gradle Plugin and its
 * location should not be considered part of the API and can change in the future.
 */&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;toTransform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;type:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ScopedArtifact&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;inputJars:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ListProperty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RegularFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;,&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;inputDirectories:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ListProperty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Directory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;,&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;into:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RegularFileProperty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;重点在这一句：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;last one provides the final version of the artifacts&lt;/code&gt;。这个方法是整个构建最后一步执行，会提供一个最终的输出，并且输出类型是一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RegularFileProperty&lt;/code&gt; 他只能输出一个单独的 File。&lt;/p&gt;

&lt;p&gt;这也就意味着你需要把 前期构建的所有产物，包括全部的 jar 依赖、源码编译后的 class 依赖，都在这个Task中聚合到一个产物内返回，这就是耗时的原因。&lt;/p&gt;

&lt;p&gt;并且由于需要将所有的 jar 和 class 聚合到一个 jar 内，所以也没办法使用增量编译，这就又进一步拖慢了编译速度。&lt;/p&gt;

&lt;p&gt;而对应的老版本操作是，拿到所有的 jar 和 class，需要字节码操作的就做操作，不需要的直接复制一遍到输出路径就可以。很显然，新版本使用的方案还不如旧版本，把所有的class聚合成一个新的jar是很耗时的，而且没办法做增量操作。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;解决思路1&quot;&gt;解决思路1&lt;/h3&gt;

&lt;p&gt;既然耗时间的原因是将全部的 class 聚合成一个 jar，这一步太慢。那么我们的想法肯定是怎么避免这样做，同时又能在编译期改变 class 内容。&lt;/p&gt;

&lt;p&gt;在老版本&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transform API&lt;/code&gt;的替代类还提供了一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AsmClassVisitorFactory&lt;/code&gt;，是对一个指定class做字节码操作，而我们的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;路由恰好是只需要改&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouterServiceProvideInjecter&lt;/code&gt;这一个类就足够了，那这样就可以大幅降低编译时间了。&lt;/p&gt;

&lt;p&gt;但是问题是在于， &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AsmClassVisitorFactory&lt;/code&gt; 是对所有类遍历的过程中执行的，而路由的代码需要所有类遍历一遍以后才知道应该要把哪些类放到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouterServiceProvideInjecter&lt;/code&gt;中。如果使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AsmClassVisitorFactory&lt;/code&gt;，就有可能要记录的类还没有遍历完，就已经轮到要ASM插桩处理的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouterServiceProvideInjecter&lt;/code&gt;类了。&lt;/p&gt;

&lt;p&gt;比如这个图中的流程，假设红色箭头是当前构建处理类的顺序，整体的逻辑就是处理A类的时候，记录是否需要插桩，再B，再C。但是到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouterServiceProvideInjecter&lt;/code&gt;的时候，就要开始读取记录，开始插桩了，实际上如果E、F也是需要插桩的话，此时就会被漏掉。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2024103101.png&quot; alt=&quot;图示&quot; /&gt;&lt;/p&gt;

&lt;p&gt;所以现在的问题又变成了如何让 ASM 处理的时候就知道要处理哪些类。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;解决思路2&quot;&gt;解决思路2&lt;/h3&gt;

&lt;p&gt;之前在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;的反馈群里，有人给出了这篇文章，里面提到了一个很好的方案：&lt;a href=&quot;https://juejin.cn/post/7374677514536812571&quot;&gt;https://juejin.cn/post/7374677514536812571&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在编译的时候，由于KSP/KAPT这种编译期工具是优先于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transform&lt;/code&gt;过程的，并且我们所有需要插入的代码类都是由 KSP/KAPT 生成的，可以在这个时候将所有需要记住的类写入的一个构建文件中，直到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transform&lt;/code&gt;的时候直接去读这个文件。 &lt;br /&gt;
但是他有个很大的弊端，公司大了以后项目会变成各个业务线开发一个独立模块，最终打包的时候只依赖各个业务线的aar打包。而 KSP 如果是在独立模块中，那么在 aar 生成的时候就已经执行完了，这时候如果是给到主工程aar再去打包，就会丢失这个aar内需要写入到文件中的产物。&lt;/p&gt;

&lt;p&gt;大概的逻辑如下图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2024103102.png&quot; alt=&quot;图示&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AAR A&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AAR B&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AAR C&lt;/code&gt;，里面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ksp_cache.txt&lt;/code&gt;就会丢失了，因为app在构建的时候，也会有自己的产物文件，这部分并没有包含aar中的内容。 &lt;br /&gt;
然后还有一种折中的办法，就是让所有aar提供给app接入方的时候，也同时将这个产物文件一起提供，由接入方手动把产物同步到app的产物文档内，这样在最终打包的时候插件asm就能直接插入了。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;therouter-目前的方案&quot;&gt;TheRouter 目前的方案&lt;/h3&gt;

&lt;p&gt;还是回到最原始的那个思考。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AsmClassVisitorFactory&lt;/code&gt; 可以做到增量编译，并且按需插桩，问题是插桩时还没有遍历完全部类。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toTransform()&lt;/code&gt; 可以做到完整遍历所有类，并且按需插桩，问题是无法增量编译，类多的时候执行很耗时。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;那么能不能把这两个结合一下？&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AsmClassVisitorFactory&lt;/code&gt;去做插桩，用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toTransform()&lt;/code&gt;去生成待插桩的记录文件。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;但这样依然有2个问题。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;前面也讲过了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toTransform()&lt;/code&gt;执行时间是在整个构建的最后。所以这就变成了先插桩，再生成记录文件。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toTransform()&lt;/code&gt;如果要想遍历所有类，依然要经历那个聚合所有class的动作，也就意味着依然没办法增量编译，所以并没有解决耗时问题。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;第一个问题好解决，第一次遍历没法用，那就先记录，下次编译的时候再用这次的类记录缓存就行了。 &lt;br /&gt;
第二个问题再想一下，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toTransform()&lt;/code&gt;耗时的原因是需要把所有类聚合，而我们用到他的目的是为了在构建的最后执行我们自己的一个方法，那有没有可以在最后执行，又不需要聚合所有类的方法呢？还真有！ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toTransform()&lt;/code&gt;方法所在类里面，还有一个方法，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toGet()&lt;/code&gt;它只有两个输入，但是没有输出，并且也是在构建结尾执行。&lt;/p&gt;

&lt;div class=&quot;language-groovy highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * Set the final version of the [type] artifact to the input fields of the [Task] [T].
 * Those input fields should be annotated with [org.gradle.api.tasks.InputFiles] for Gradle to
 * property set the task dependency.
 *
 * @param type the [ScopedArtifact] to obtain the final value of.
 * @param inputJars lambda that returns a [ListProperty] or [RegularFile] that will be used to
 * set all incoming files for this artifact type.
 * @param inputDirectories lambda that returns a [ListProperty] or [Directory] that will be used
 * to set all incoming directories for this artifact type.
 */&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;toGet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;type:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ScopedArtifact&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;inputJars:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ListProperty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RegularFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;,&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;inputDirectories:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ListProperty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Directory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;从注释来看，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toGet()&lt;/code&gt; 完全就是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toTransform()&lt;/code&gt; 的平替，而且非常适合我们现在的场景。再看一下官方的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;demo&lt;/code&gt;使用方法都一模一样，只是参数少了个输出路径。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/android/gradle-recipes/blob/agp-8.7/getScopedArtifacts/build-logic/plugins/src/main/kotlin/CustomPlugin.kt&quot;&gt;https://github.com/android/gradle-recipes/blob/agp-8.7/getScopedArtifacts/build-logic/plugins/src/main/kotlin/CustomPlugin.kt&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;variant.artifacts
    .forScope(ScopedArtifacts.Scope.PROJECT)
    .use(taskProvider)
    .toGet(
        ScopedArtifact.CLASSES,
        CheckClassesTask::projectJars,
        CheckClassesTask::projectDirectories,
    )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;再优化一下&quot;&gt;再优化一下&lt;/h3&gt;

&lt;p&gt;现在还剩的一个问题就是：编译的记录文件是下次才生效的，但作为 SDK 这样的表现肯定是不合适的，因为会产生不确定性，你不知道实际使用 SDK 的用户会怎么用，万一他用了旧产物打的包直接发布了，那就会造成问题。&lt;/p&gt;

&lt;p&gt;所以在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;中，做了一个额外的内存缓存，会把本次编译使用的类记录，与本次构建新生成的类记录缓存，在编译结束时做一次对比，如果两次结果不一致，直接抛异常，表示本次构建的产物是有问题的，不可用，再次编译以后就正常了。&lt;br /&gt;
这样就能既享受了增量编译，快速构建，同时对产物结果有了保障。&lt;/p&gt;

&lt;p&gt;比如微信群里面一个使用者的截图：
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2024103103.jpg&quot; alt=&quot;图示&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;再优化首次的中断&quot;&gt;再优化首次的中断&lt;/h3&gt;

&lt;p&gt;目前还有一个问题，Gradle 构建此时的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AsmClassVisitorFactory&lt;/code&gt; 并不会每次都执行（如果输入没有变会直接跳过），单纯从构建来看这是合理的，但是上面的方案就出问题了。因此需要解决增量编译时缓存的正确性，否则会有两个结果，要么每次编译经常性的提醒有模块增删请重新编译，要么就是编译后的插桩类丢失。&lt;/p&gt;

&lt;p&gt;因此我们再次将前面的模式整合一下，每次全新构建时，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toTransform&lt;/code&gt;，同时将此时需要插桩的类记录下来。再次编译时，只走&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AsmClassVisitorFactory&lt;/code&gt;，对增量变化的类做记录，这样就彻底解决了类记录缺失、增量编译、缓存一致性的全部问题。&lt;/p&gt;

&lt;p&gt;详细实现可以直接去看&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;的源码，能查看更多细节实现以及&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;对构建内存占用的优化，以及文件读写的优化。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;说实话，其实这样的方案也是一个折中后的取舍。按理说如果 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toTransform&lt;/code&gt; 加一个重载，第三个参数同时支持既能定义成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;File&lt;/code&gt;又能定义成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dir&lt;/code&gt;，如何在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;File&lt;/code&gt;的重载里面调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dir&lt;/code&gt;的产物做一次聚合，达到的效果是一样的，并且也能更简单的解决问题。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;2024.11.26 更新：&lt;br /&gt;
在 &lt;a href=&quot;https://github.com/5peak2me&quot;&gt;@5peak2me&lt;/a&gt; 的提醒下发现，原来从 AGP8 开始，就提供了上面的疑惑的方法，只不过 API 还是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;internal&lt;/code&gt; 的，应该还是没确定具体的实现方案，可能要到 9 才能对外公开出来了，留个记录，后续观察吧。  &lt;br /&gt;
详情见 TheRouter issue #216： &lt;a href=&quot;https://github.com/HuolalaTech/hll-wp-therouter-android/issues/216&quot;&gt;https://github.com/HuolalaTech/hll-wp-therouter-android/issues/216&lt;/a&gt;.&lt;/p&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - NAS - 玩点有意思的]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/stickies/2024/08/05/01/</br>最近新买了一个NAS，装个黑群晖，感觉可玩的还挺多。 - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2024-08-05]]></pubDate>
        <link><![CDATA[https://kymjs.com/stickies/2024/08/05/01/]]></link>
        <tags>
          
          <tag>生活</tag>
          
        </tags>
        <columnNum><![CDATA[3]]></columnNum>
        <column><![CDATA[思绪万千]]></column>
        <categories>
          
          <category>stickies</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/stickies/2024/08/05/01/</br>&lt;h2 id=&quot;nas是什么&quot;&gt;NAS是什么&lt;/h2&gt;

&lt;p&gt;其实就是一个私有的云存储，你也可以理解为你自己独享的功耗很低的小型&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;linux&lt;/code&gt;服务器，这个服务器通常用来做存储相关的事情，里面可以放很多很大的硬盘。  &lt;br /&gt;
比如我就放了两块10T的硬盘，组了个冗余存储阵列，感觉10T已经够用了，等快装满的时候可以再买两块。&lt;/p&gt;

&lt;h3 id=&quot;冗余存储&quot;&gt;冗余存储&lt;/h3&gt;

&lt;p&gt;群晖系统对存储有多个支持类型（其实所有的NAS，都有类似的能力）。 &lt;br /&gt;
最常用的存储就是RAID了，RAID（独立磁盘冗余阵列）是将多个独立的硬盘整合成一个存储单元的数据存储技术。&lt;br /&gt;
因为我们的物理机上是有多块硬盘的，但实际系统使用的时候，不可能每次都我们自己指定要存在哪块硬盘上(我一直不能理解Windows为什么要反过来，把完整的磁盘分不同的盘符)。RAID就是用来把多个硬盘的感受对使用者屏蔽，让使用者觉得用到的就是一块硬盘。&lt;/p&gt;

&lt;p&gt;RAID 又分很多类型，Basic、RAID 0、RAID 1、RAID 5、RAID 6 …&lt;/p&gt;

&lt;p&gt;Basic其实就是最普通的方式，众所周知，玩NAS，最贵的就是买硬盘了。假如你买不起，只有一块硬盘，那你只能组 Basic，它就是单个硬盘构成独立的单位。也不具备数据安全，如果硬盘坏了，那数据就丢了。&lt;/p&gt;

&lt;p&gt;再进一步，还是那个原因，由于硬盘贵，并且相比其他电子元器件更容易坏，你只买了两块，这时候你有两种选择。牺牲存储来保障数据的安全性，同一份数据存两遍；或者不在乎数据的安全性，直接把两块拼起来，有多大的硬盘就存多少东西。这就对应了RAID 0和RAID 1 。&lt;/p&gt;

&lt;p&gt;RAID 0 组合了两个或更多硬盘以提高性能和容量，但没有容错保护功能。单个硬盘出现故障将导致阵列中的的所有数据丢失。RAID 0 对于需要高性能比的非关键系统非常有用。 &lt;br /&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/RAID-0.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;RAID 1 通常为两个硬盘来执行。硬盘中的数据被映射，在硬盘出现故障时提供容错保护功能。读取性能得到提高，而写入性能将与单个硬盘类似。单个硬盘出现故障时可得以维持而不会丢失数据。在容错保护非常关键而空间和性能不那么重要时，往往使用 RAID 1。&lt;br /&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/RAID-1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如果又过了一段时间，你觉得硬盘不够又买了几块硬盘，这时候你就又多一些选择。依然是牺牲存储来保障数据的安全性，或者更在乎能存的数据量，我要存更多东西但数据还得相对安全一点。这就对应了RAID 5和RAID 6。&lt;/p&gt;

&lt;p&gt;RAID 5 提供容错保护功能并提高了读取性能。至少需要三个硬盘。RAID 5 可在单个硬盘丢失时得以维持运行。在硬盘出现故障的情况下，故障硬盘上的数据将从其余硬盘上延展的奇偶校验进行重建。因此，在 RAID 5 阵列处于降级状态时，读写性能受到严重影响。当存储空间和成本的重要性高于性能时，RAID 5 最为理想。&lt;br /&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/RAID-5.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;RAID 6 与 RAID 5 相似，但其提供了另一层区块延展功能，并在 2 个硬盘出现故障时得以维持。至少需要四个硬盘。RAID 6 的性能因其额外的容错保护功能而低于 RAID 5。在存储空间和成本较为重要且需要在多个硬盘出现故障得以维持的情况下，RAID 6 最为理想。&lt;br /&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/RAID-6.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;联通的公网ip&quot;&gt;联通的公网IP&lt;/h2&gt;

&lt;p&gt;既然都是个服务器了，没有公网岂不是很没意思，只能在自己局域网玩有什么用。&lt;br /&gt;
先看一下我家的网络结构。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2024080601.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;拨号靠联通光猫，然后路由器再分一个子网。这样子普通用户使用肯定没有问题，但是作为服务器肯定是不行的。 &lt;br /&gt;
第一联通光猫性能极差，靠他去调度大流量指不定能把你卡成狗，所以一定得干掉那个性能极差的联通光猫，让自己的高性能路由器直接拨号上网，如果你的路由器更烂那你还是花几十块钱买个好点的吧。  &lt;br /&gt;
第二个问题就是这种网络得做两次内网穿透，两次端口转发，光猫一次、路由器一次，太麻烦了。&lt;/p&gt;

&lt;p&gt;接下来就是详细配置步骤：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;首先本地通过wifi连上联通光猫的网段，然后访问管理页：&lt;a href=&quot;http://192.168.1.1/cu.html&quot;&gt;http://192.168.1.1/cu.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;账号密码都是  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CUAdmin&lt;/code&gt;，CU的意思是 China Unicom。&lt;/li&gt;
  &lt;li&gt;进去以后把IPV4和V6都打开，然后把模式选成桥接模式。&lt;/li&gt;
  &lt;li&gt;再进自己路由器设置，把路由器改成拨号上网。&lt;/li&gt;
  &lt;li&gt;拨号的账号可以在联通APP-&amp;gt;首页右上角切宽带账号-&amp;gt;我的-&amp;gt;宽带专区-&amp;gt;查宽带账号&lt;/li&gt;
  &lt;li&gt;密码不知道给10010打电话重置就行了。&lt;/li&gt;
  &lt;li&gt;能正常拨号上网以后，再给10010打电话，让他开公网IP，这一步不一定能成功，根据所在地联通的公网IP数量决定的，上海还挺多，二话没说就给我打开了。&lt;/li&gt;
  &lt;li&gt;至此已经有一个可变公网IP了，再配合腾讯云的DDNS，就得到一个公网可以访问的域名了。&lt;/li&gt;
  &lt;li&gt;记住，公网还是比较危险的，全世界各种端口扫描暴力破解的都在搞，路由器最好设置一下，把默认端口改了，把ssh密码校验关掉。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;腾讯云免费 DDNS 服务看这个链接：&lt;a href=&quot;https://www.tencentcloud.com/zh/document/product/1007/50202&quot;&gt;腾讯云DDNS链接&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;以下是改进后的拓扑结构，NAS直连路由器，路由器直连公网：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2024080602.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;配个nexus&quot;&gt;配个nexus&lt;/h2&gt;

&lt;p&gt;作为一个移动端开发，最先想到的在服务端占用硬盘的服务，也就是nexus了。 &lt;br /&gt;
有一个属于自己的Nexus代理，以后拉依赖可就快多了。&lt;/p&gt;

&lt;p&gt;群晖是自带docker的，写一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compose.yml&lt;/code&gt;，直接拉镜像了，这种事当然不能我来写：   &lt;br /&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2024080603.png&quot; alt=&quot;&quot; /&gt;    &lt;br /&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2024080604.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;反正一路让GPT教我，分分钟就搞好了。&lt;/p&gt;

&lt;p&gt;以下是我的 nexus，想用你也可以直接用，反正也就只是占我一点公网IP，没啥大影响。&lt;/p&gt;

&lt;div class=&quot;language-groovy highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;repositories&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;maven&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;https://maven.therouter.cn:8443/repository/maven-public/&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;google&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mavenCentral&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;nginx-端口转发&quot;&gt;nginx 端口转发&lt;/h2&gt;

&lt;p&gt;你可能已经看到了，上面配&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nexus&lt;/code&gt;的时候，GPT 是帮我配置的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8081&lt;/code&gt;端口，而且只支持 http 协议。但是新版本的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gradle&lt;/code&gt;默认是不允许 http 访问的，所以要么改&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gradle&lt;/code&gt; 添加仓库时的代码，要么就从服务端直接支持 https。  &lt;br /&gt;
你可以这样子添加一个 maven 仓库地址，手动声明允许 http 协议拉依赖，但最标准的做法当然还是让服务端支持。&lt;/p&gt;

&lt;div class=&quot;language-groovy highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;maven&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;allowInsecureProtocol&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;http://maven.therouter.cn/xxxx&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nexus 要启用 https 还挺麻烦，搜了网上的各种教程照着配，也让GPT帮我改了很多次，依然没办法解决。最后是直接在 nginx 上做了一次端口转发，直接把外部的 https 请求，都打到 nginx 上，然后 nginx 再全转到内部的 8081 端口，也能达到目的。&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    listen 8443 ssl&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    server_name nas.therouter.cn&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    ssl_certificate /xxxxx/ssl/nas.therouter.cn.pem&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    ssl_certificate_key /xxxxx/nas.therouter.cn.key&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    location / &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        proxy_pass http://localhost:8081&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        proxy_set_header Host &lt;span class=&quot;nv&quot;&gt;$host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        proxy_set_header X-Real-IP &lt;span class=&quot;nv&quot;&gt;$remote_addr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        proxy_set_header X-Forwarded-For &lt;span class=&quot;nv&quot;&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        proxy_set_header X-Forwarded-Proto &lt;span class=&quot;nv&quot;&gt;$scheme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;网盘当然是用来存数据的&quot;&gt;网盘当然是用来存数据的&lt;/h2&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;把-mac-备份到-nas-上&quot;&gt;把 Mac 备份到 NAS 上&lt;/h3&gt;

&lt;p&gt;把 Mac 的时间机器备份到 NAS 上。   &lt;br /&gt;
照着官网教程一步一步来，很简单  &lt;br /&gt;
&lt;a href=&quot;https://kb.synology.cn/zh-cn/DSM/tutorial/How_to_back_up_files_from_Mac_to_Synology_NAS_with_Time_Machine&quot;&gt;群晖官网链接&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;注：教程里面的这一句很重要，【&lt;strong&gt;进入 高级 选项卡，然后选择 启用通过SMB进行 Bonjour Time Machine 播送&lt;/strong&gt;】如果没有勾选，不会报任何错，但是在 mac 的时间机器里面，NAS 始终挂载不上，最后不停在连接 smb。&lt;/p&gt;

&lt;h3 id=&quot;把-iphone-备份到-nas-上&quot;&gt;把 iPhone 备份到 NAS 上&lt;/h3&gt;

&lt;p&gt;垃圾 iOS 系统真的是封闭，我试了网上能搜到的几乎全部的方案，始终没有一个能够无感把iPhone数据直接备份到 NAS 上的办法。 &lt;br /&gt;
后来我换了个思路，既然 iOS 封闭，那就在 Apple 的体系理解解决这个事。 &lt;br /&gt;
通过我的 Mac，把 iPhone 的数据备份到 Nas 上。可能唯一麻烦的就是手机得连着电脑。&lt;/p&gt;

&lt;p&gt;备份还是用 Apple 默认的备份方式，连上手机就能看到了。   &lt;br /&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2024080605.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Mac 默认是把手机的数据备份到电脑上，并且这个备份的路径是完全不告诉你的，费了点时间查到了，备份目录是在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Library/Application\ Support/MobileSync/Backup&lt;/code&gt;  &lt;br /&gt;
既然找到了备份目录就简单了。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;最简单也最傻的办法，备份好了以后，直接手动把数据再转到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NAS&lt;/code&gt;上。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;很显然程序员是不会这么干的，直接一步到位，把手机数据直接丢到 NAS 上。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;先通过smb在本地挂一个网络存储卷。&lt;/li&gt;
  &lt;li&gt;命令行进到存储目录，直接把原本的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Backup&lt;/code&gt;目录删了。这一步会报&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Operation not permitted&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;进 System setting -&amp;gt; Privacy -&amp;gt; Full Disk Access -&amp;gt; 把终端勾上&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2024080606.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;重新再进命令行，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ln -s&lt;/code&gt;建一个软链，把 NAS 的存储目录链接到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Backup&lt;/code&gt;目录就行了。&lt;/li&gt;
  &lt;li&gt;这样再备份就自动备份到 NAS 上了。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;把-android-备份到-nas-上&quot;&gt;把 Android 备份到 NAS 上&lt;/h3&gt;

&lt;p&gt;是的，一个移动端开发，手上只有一个手机是不是有点丢人。 &lt;br /&gt;
Android 也没有什么好的同步软件，但是 Android 有个好处，遇事不求人，自己动手上。&lt;br /&gt;
无非就是读文件、读通话记录、读联系人、读短信，读完通过 smb 往NAS上写数据。自己写的自己也放心一点，省的第三方指不定读完顺手同步到哪去了。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;对象存储&quot;&gt;对象存储&lt;/h2&gt;

&lt;p&gt;既然都公网IP了，NAS又本来就是做存储的，那拿来做对象存储太合适了。 &lt;br /&gt;
还是老方法，docker 部一个 apache，把独立的文件夹映射出来做存储。nginx 再把跨域header限制一下，只能我自己的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kymjs.com&lt;/code&gt;调用，顺带就又解决了盗链问题，剩下的被恶意刷流量更是无所谓了，反正联通公网IP，我交的是宽带费又不是服务器一样按流量计费。&lt;/p&gt;

&lt;p&gt;再下一步就是把我的博客都给部署到 NAS 上了，这样每年的服务器费用也可以省下来买硬盘和交电费了，只不过好像运营商都是把80和443端口封了，所以可能访问起来会有些影响。&lt;/p&gt;

]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 动态路由 TheRouter 的设计与实践]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/session/2022/11/23/01/</br>这篇文章是我在 2022【GIAC 全球互联网架构大会】分享时所讲内容的文字版本，修改删减了演讲时的冗余言语，现开放给大家阅读，希望能给买不到票参加分享的 开源实验室 读者带来帮助。   - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2022-11-23]]></pubDate>
        <link><![CDATA[https://kymjs.com/session/2022/11/23/01/]]></link>
        <tags>
          
          <tag>TheRouter</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>session</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/session/2022/11/23/01/</br>&lt;p&gt;这篇文章是我在 2022【&lt;a href=&quot;https://giac.msup.com.cn/2022sh/course?id=16425&quot;&gt;GIAC 全球互联网架构大会&lt;/a&gt;】分享时所讲内容的文字版本，修改删减了演讲时的冗余言语，现开放给大家阅读，希望能给买不到票参加分享的 开源实验室 读者带来帮助。&lt;/p&gt;

&lt;p&gt;大家好，今天跟大家分享的是一个开源路由&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;的设计。  &lt;br /&gt;
代码地址： &lt;a href=&quot;https://github.com/HuolalaTech/hll-wp-therouter-android&quot;&gt;https://github.com/HuolalaTech/hll-wp-therouter-android&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;先来看一下目录 我们从三点，来讲述今天的主题：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;分别是模块化的开始，如何通过路由去实现一个模块化。&lt;/li&gt;
  &lt;li&gt;然后再根据目标，去设计一个动态化的路由解决我们的问题，以及在我们的项目中，是如何实践的。&lt;/li&gt;
  &lt;li&gt;最后，今年的大环境大家应该都知道，考虑一下如何在资源有些的情况下，推动工程的重构。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.003.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里有三张我手机上APP的截图，分别是：货拉拉、今日头条、美团&lt;/p&gt;

&lt;p&gt;他们基本上可以代表了如今市面上大部分APP的一个形态，在这四五年里，互联网公司大幅增加，而APP的业务功能也不断增多。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.005.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;从技术角度再看一下：&lt;/p&gt;

&lt;p&gt;这是我列出来的一个APP的通用架构，这张图基本可以覆盖现如今百分之八九十的 APP 架构。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;首先最上层是各个业务层 比方说是像货拉拉的搬家、拉货、运大件这种。&lt;/li&gt;
  &lt;li&gt;接下来是各个业务模块 比如常见的像用户账户体系、然后可能有一些直播、音视频、支付这样的场景模块。&lt;/li&gt;
  &lt;li&gt;再往下就是一些功能性的组件：他们可能跟具体的业务功能相关，比如推送、IM、广告控件、这样的一系列功能组件。&lt;/li&gt;
  &lt;li&gt;最底层就是基础设施了： 就像数据上报 异常统计等等一系列的必要基础能力。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;然后在侧面还有一些贯穿整个APP的能力 像CICD 国际化 端智能 热修复等等。&lt;/p&gt;

&lt;p&gt;从这张图我们也能看出现如今的APP是越来越复杂 功能也越来越多&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.007.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;对于功能越来越多，越来越复杂的APP架构，我们最直接能想到的就是通过模块化，将不同的功能、不同的业务做独立拆分，分而治之，降低整个系统的复杂度。毕竟越简单，逻辑越少的代码块，BUG就越少。&lt;/p&gt;

&lt;p&gt;所以大型 APP 的开发，基本都会选用模块化开发，同时对于模块间解耦要求更高。  &lt;br /&gt;
而说到模块化，我们一定需要一个路由去承载不同模块之间的通信。路由是现如今 Android 开发中必不可少的功能，尤其是企业级APP，可以用于将原生页面跳转的强依赖解耦，同时减少跨团队开发的互相依赖问题。&lt;/p&gt;

&lt;p&gt;比如UI层级的跳转、功能模块的联动调用，这是做模块化绕不开的两点。&lt;/p&gt;

&lt;p&gt;实现这两点最常用的办法也就是：分别将我们当前的一个UI页面与一个uri关联，用Uri替代我们的页面，&lt;/p&gt;

&lt;p&gt;这个样子在跳转的时候就不需要强依赖UI页面去做匹配，而只需要通过一段字符串去匹配就行&lt;/p&gt;

&lt;p&gt;那另一种就是通过接口下沉，将模块依赖改为协议依赖，这样 我们在不同的模块之间调度的时候 只需要依赖一个最基础的协议或者说是接口 去实现就可以了&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.009.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;做完模块化以后，一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APP&lt;/code&gt;的复杂度已经被降低很多了。&lt;br /&gt;
但是有一个最大的问题，我们通过模块化是没办法解决的。&lt;/p&gt;

&lt;p&gt;也就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APP&lt;/code&gt;依赖用户去主动的更新升级，用户不更新，那就是永远在用旧版本，
当年，也是为了解决这个问题，催生出了很多黑科技，比如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Android&lt;/code&gt;的插件化、热修复这种黑科技，最终这些科技最终也被验证是点歪了的技能树。&lt;/p&gt;

&lt;p&gt;今天我跟大家讲讲另一种解决办法：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.010.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;回到我们今天的主题：动态化路由&lt;/p&gt;

&lt;p&gt;前些天我们开源了一套，在安卓上面的动态化路由叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 他是一整套我们实现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APP&lt;/code&gt;动态化的设计方案。包括模块化、包括远端路由下发、包括前面刚才我列出来的几个依赖用户升级而造成的一些问题，我们都是通过他来解决的。&lt;/p&gt;

&lt;p&gt;之所以叫&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 因为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;The&lt;/code&gt; 代表了一种唯一性，我们在设计的时候就参考了全部现有的开源方案，吸取了大量优秀实现，同时补齐了各个方案的缺点。我们认为做移动端的模块化，只需要看这一个就够了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.011.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;首先我们来看一下行业内路由的设计方案，不管是页面跳转，还是跨模块调用，基本上都是&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;开发阶段，对要使用路由的落地页或被调用方法添加注解标识。&lt;/li&gt;
  &lt;li&gt;在编译期解析注解，生成一系列中间代码，等待调用。&lt;/li&gt;
  &lt;li&gt;应用启动后调用中间代码完成路由的准备动作。大部分路由会额外通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gradle&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transform&lt;/code&gt;，在编译期做一次聚合，以提升运行时准备路由表的效率。&lt;/li&gt;
  &lt;li&gt;发起路由跳转时，本质上就是一次路由表遍历，通过uri获取到对应的落地页或方法对象，进行调用。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;跨模块调用也是类似，在开发时做标记，编译时生成中间代码，运行时通过中间代码调用跨模块方法。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 的整体实现逻辑也是按照这个思路去做的，不过我们对于各个细节的处理，有更好的解决办法。&lt;/p&gt;

&lt;p&gt;这是另一个角度，跟行业路由的一些对比数据。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.012.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;大家可以主要关注这几个点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;第一个点： &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;是完全无运行时扫描，没有任何反射代码的框架。&lt;br /&gt;
当然因为引用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gson&lt;/code&gt;做&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json&lt;/code&gt;解析，他里面应该是用了反射的，但这不在我们讨论的范围内，如果你愿意我们允许自定义&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json&lt;/code&gt;解析框架，你可以换成其他的解析。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;第二点是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;对增量编译支持非常好，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APT&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plugin&lt;/code&gt;都能做到增量编译。&lt;br /&gt;
同时我们内部也有一套基于最新&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KSP&lt;/code&gt;的注解处理代码，KSP是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kotlin&lt;/code&gt;专门用于处理注解做的一套实现，我们之前用的都是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kapt&lt;/code&gt;，但是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kapt&lt;/code&gt;只能处理&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kotlin&lt;/code&gt;类的注解，如果是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kotlin&lt;/code&gt;跟&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Java&lt;/code&gt;混合的工程，他还没办法处理，所以在他内部还包了一层&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Java&lt;/code&gt;的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt&lt;/code&gt;，碰到他解析不了的文件，就调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt&lt;/code&gt;去解析，所以他的处理速度是非常慢的。&lt;br /&gt;
而KSP是基于语法树分析去做的，我们知道，所有的代码在编译之前，都会先经过语法树分析，他就是在这一步顺带把分析出来的词法返回，让我们做一些自己的定制逻辑。所以KSP其实不仅仅可以做注解处理，还可以做一些定制的语法分析规则，类似&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lint&lt;/code&gt;那种。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;第三点：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;应该是现如今所有路由里，唯一一个支持AGP8的。Gradle从7.X开始，内置了编译过程处理的相关方法，所以AGP直接在8.0删除了相同功能的方法，这就造成大量基于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TransformAPI&lt;/code&gt;的库，在AGP8都没办法使用了。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;最后一点也是我们之前碰到的坑，在用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tinker&lt;/code&gt;这类热修复框架的时候，由于路由编译的产物代码是无序的，所以每次编译都有可能发生改变，就造成我们的补丁包非常大。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;对这一点也做了特殊支持，只要你没有新增或改动路由相关的代码，编译产物代码就不会有任何变动。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.013.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;接下来需要大家一起思考一下，一个路由 他真正需要具备的核心能力是哪些。我前面PPT列了一下，参考现在业内的一些通用的路由解决方案 它真正核心需要解决的问题就两个点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;一个是解耦UI跳转&lt;/li&gt;
  &lt;li&gt;一个是降低系统依赖&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们把这两个目标分别拆开。&lt;br /&gt;
在跳转方面，除了业界常用的通过路由字符串映射页面UI之外，我们还加入了动态参数注入。 &lt;br /&gt;
也就是一个UI页面需要的默认参数可以通过路由表提前声明好，而路由表可以是远端下发的，那这些默认参数也可以是远端下发的，这就做到了线上默认字段的及时更新。&lt;/p&gt;

&lt;p&gt;另一部分，降低依赖，除了常用的SPI接口下沉，将模块功能依赖改为接口协议依赖之外，我们还提供了业务节点的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook&lt;/code&gt;，所有模块可以反向订阅所需的业务节点，并在业务发生时做自己的逻辑处理。&lt;/p&gt;

&lt;p&gt;这一个能力最常用的地方，比如我们在做隐私合规的时候，要求用户同意隐私协议以后，才能做一些敏感API的调用。在以前的开发，这些调用都得要放到隐私弹窗所在的模块内，当用户点同意按钮以后，再调用其他模块初始化方法。这种逻辑对模块化是非常难受的，因为增大了跨模块的沟通，如果团队特别大，不同团队负责不同模块的时候，这种沟通就很累了，假设初始化方法需要增加一个参数，还得额外处理。哪些能力是要一启动就调用的，哪些API是必须用户同意以后才能调用的，都得沟通清楚。&lt;/p&gt;

&lt;p&gt;而我们做了业务节点订阅以后，就把这种依赖某个业务节点的功能，做成了订阅发布模式，你只需要声明初始化方法依赖用户同意隐私协议就行了，在用户同意以后就会自动调用初始化方法。&lt;/p&gt;

&lt;p&gt;另外，我们还允许客户端创建一套基于规则引擎的触发与响应，可以全局动态智能处理用户操作。假设客户端此刻碰到什么意外情况，比如一个女性用户，在夜里十一二点打车，路上又在某些偏僻点发生异常停留，客户端可以主动做一些我们预置的事件，比如自动报警、语音或者视频自动联系我们的客服。比如像今年&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iPhone14&lt;/code&gt;的新功能，有个车祸检测，如果车翻了或者撞车了，自动帮你打救援电话。而我们这一系列规则，都可以是动态响应的。&lt;/p&gt;

&lt;p&gt;接下来看一下路由的设计细节&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.014.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 会在编译期根据注解生成 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RouteMap__&lt;/code&gt;开头的类，这些类中记录了当前模块的所有路由信息，也就是当前模块的路由表。&lt;/p&gt;

&lt;p&gt;在最顶层的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app&lt;/code&gt;模块中，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gradle&lt;/code&gt;插件，将所有aar、源码中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RouteMap__&lt;/code&gt;开头的类统一集中到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouterServiceProvideInjecter&lt;/code&gt;类中。&lt;/p&gt;

&lt;p&gt;后续应用启动后，初始化路由时只需要执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouterServiceProvideInjecter&lt;/code&gt;类的方法，就能&lt;strong&gt;没有任何反射&lt;/strong&gt;的加载到全部的路由表了。&lt;/p&gt;

&lt;p&gt;加载以后的路由表会被保存到一个支持正则匹配的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Map&lt;/code&gt; 中，这也是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;允许多个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;path&lt;/code&gt;对应同一个落地页的原因。每当发生页面跳转时，通过跳转时的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;path&lt;/code&gt;，去&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Map&lt;/code&gt;中获取到对应的落地页信息，再正常调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;startActivity()&lt;/code&gt;即可。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.016.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;对于模块化开发中跨模块的调用，我们推荐采用 &lt;a href=&quot;https://zh.m.wikipedia.org/zh-cn/%E9%9D%A2%E5%90%91%E6%9C%8D%E5%8A%A1%E7%9A%84%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84&quot;&gt;SOA(面向服务架构)&lt;/a&gt; 的设计方式，服务调用方与使用方完全隔离，调用模块外的能力不需要关注能力的提供者是谁。 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ServiceProvider&lt;/code&gt; 的核心设计思想也是这样的，目前服务间的调用协议采用接口的方式。当然，也可以兼容不通过接口下沉而是直接调用的情况。&lt;/p&gt;

&lt;p&gt;具体到 Android 侧就是 AIDL 类似的设计，只是要比AIDL开发简单很多：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;服务提供方负责提供服务，不需要关心调用方是谁会在何时调用自己。&lt;/li&gt;
  &lt;li&gt;服务的使用方只关注服务本身，不需要关心这个服务是谁提供的，只需要只能服务能提供哪些能力即可。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;例如上面的图片：服务使用方需要使用录音的服务，服务提供方则向外提供一个录音的服务，由&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ServiceProvider&lt;/code&gt;负责撮合。&lt;/p&gt;

&lt;h4 id=&quot;服务使用方&quot;&gt;服务使用方：&lt;/h4&gt;

&lt;p&gt;无需关心，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IRecordService&lt;/code&gt;这个接口服务是谁提供的，他只需要知道自己需要使用这样的一个服务就行了。 注：如果没有提供服务的提供方，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter.get()&lt;/code&gt;可能返回&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;TheRouter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;IRecordService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;doRecord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;服务提供方&quot;&gt;服务提供方：&lt;/h4&gt;

&lt;p&gt;服务提供方需要声明一个提供服务的方法，用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@ServiceProvider&lt;/code&gt;注解标记。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;如果是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;java&lt;/code&gt;，必须是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public static&lt;/code&gt; 修饰&lt;/li&gt;
&lt;/ul&gt;

&lt;!----&gt;

&lt;ul&gt;
  &lt;li&gt;如果是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kotlin&lt;/code&gt;，建议写成 top level 的函数&lt;/li&gt;
&lt;/ul&gt;

&lt;!----&gt;

&lt;ul&gt;
  &lt;li&gt;方法名不限&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * 方法名不限定，任意名字都行
 * 返回值必须是服务接口名，如果是实现了服务的子类，需要加上returnType限定（例如下面代码）
 * 方法必须加上 public static 修饰，否则编译期就会报错
 */&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@ServiceProvider&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IRecordService&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;IRecordService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;doRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;执行录制逻辑&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 也可以直接返回对象，然后标注这个方法的服名是什么&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@ServiceProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;returnType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IRecordService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RecordServiceImpl&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// xxx &lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.020.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;前面讲过，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;是完全面向模块化开发提供的一套解决方案。&lt;/p&gt;

&lt;p&gt;在模块化开发时，可能每个模块都有自己需要初始化的一些代码。以前的做法是把这些代码都在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application&lt;/code&gt;里声明，但是这样可能随着业务变动每次都需要修改&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application&lt;/code&gt;所在模块。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 的单模块自动初始化能力就是为了解决这样的情况，可以只在当前模块声明初始化方法后，将会在业务场景时自动被调用。&lt;/p&gt;

&lt;p&gt;每个希望被自动初始化的方法，必须使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public static&lt;/code&gt;修饰，主要原因是这样子就能通过类名直接调用了。另外很多初始化代码都需要获取&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Context&lt;/code&gt;对象，所以我们将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Context&lt;/code&gt;作为初始化方法的默认参数，会自动传入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application&lt;/code&gt;。其他的所在类名、方法名都没有限制，反正只要加上了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@FlowTask&lt;/code&gt; 注解，在编译期都能通过 APT 获取到。&lt;/p&gt;

&lt;p&gt;或者隐私合规的时候，有一些功能需要同意隐私协议才能调用。&lt;/p&gt;

&lt;p&gt;跨模块依赖的时候，需要另一个模块初始化以后，才能调用当前模块的初始化，等等业务都可以用业务节点自主订阅的方式去解耦。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.019.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;每个加了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@FlowTask&lt;/code&gt; 注解的方法，都会在编译期被解析，生成一个对应的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Task&lt;/code&gt; 对象，这个对象包含了初始化方法的相关信息，比如：是否异步执行、任务名、是否依赖其他任务先执行。&lt;/p&gt;

&lt;p&gt;当所有aar都编译完成，生成好全部的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Task&lt;/code&gt; 以后，会在主 app 中通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gradle&lt;/code&gt;插件进行聚合，在这时会将所有的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Task&lt;/code&gt; 做一次检查，通过构建&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;有向无环图&lt;/code&gt;来防止 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Task&lt;/code&gt; 发生循环引用的情况。&lt;/p&gt;

&lt;p&gt;每次应用启动后，会在路由初始化时，将有向图中的全部&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Task&lt;/code&gt;，按照依赖关系按顺序加载。&lt;/p&gt;

&lt;p&gt;可以在当前模块中，任意类中声明一个任意方法名的方法，给方法添加上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@FlowTask&lt;/code&gt; 的注解即可。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@FlowTask&lt;/code&gt; 注解参数说明：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;taskName&lt;/strong&gt;：当前初始化任务的任务名，必须全局唯一，建议格式为：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moduleName_taskName&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;dependsOn&lt;/strong&gt;：参考&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gradle&lt;/code&gt; Task，任务与任务之间可能会有依赖关系。如果当前任务需要依赖其他任务先初始化，则在这里声明依赖的任务名。可以同时依赖多个任务，用英文逗号分隔，空格可选，会被过滤：dependsOn = “mmkv, config, login”，默认为空，应用启动就被调用&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;async&lt;/strong&gt;：是否要在异步执行此任务，默认false。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最后一个，APP动态响应的实现。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.021.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;还是回到之前的例子：假设一个女性、夜里12点、KTV上车、偏僻地点停车，那么我们就可以根据这样的一系列先决条件，交由后端的智慧大脑分析，然后下发给客户端一个动作：比如打开视频或语音，让客服介入。&lt;/p&gt;

&lt;p&gt;而把这个例子抽象一下，所有用户的操作，比如点击、曝光、页面跳转等等埋点数据，都可以作为分析数据交给服务端分析，然后让客户端执行：跳转页面、弹窗、优惠券、或者其他本地方法。&lt;/p&gt;

&lt;p&gt;这样的一个流程做完了以后，只要我们有一个可靠的行为分析模型，我们是大概率可以预测用户接下来的行为是要做什么的。&lt;/p&gt;

&lt;p&gt;当然，即便我们没有这样一个用户行为分析的大脑，纯客户端的方案，也是能够支持的，这就是离线端智能方案了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.022.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.024.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;最后我们再来看一下前面提到的几个 APP 的弊端，在 TheRouter 中是怎么解决的呢？&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;第一个：页面Crash，我们可以通过去修改路由表，然后我们把某一些页面的 Crash 给它降级，降级成 H5 或者说是小程序。当假设我们这个页面没办法访问的时候，我们可以让用户先暂时地去访问 H5 页面或者说小程序页面。同样的，如果某个页面白屏很久，我们也可以通过降级，直接通过H5或小程序的方式兼容打开。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;第二个：对于一些接口字段，老版本的兼容问题，我们也是能够去下发默认参数的方式。如果老版本它强制要求有某一个参数，那其实我们可以把这个参数给下发成一个默认参数。如果我们做了千人千面的话，那每一个用户都可以达到不同参数不同展示的效果。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;第三个：新功能透传及时性。假设我们当前有某一个直播的页面，新版本已经有一个可以让用户打赏或者说是让用户发礼物这样的功能了。那老版本它还没有这样的一个功能的话，我们可以通过点击礼物图标后，修改落地页把它给他提示升级弹窗。这样的升级弹窗对用户是影响最小的，它只在使用到这个功能的时候才需要做某一些升级。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;第四个意外事件处理：就是我前面讲到的云端大脑或端智能这样的应用场景了。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最后我们来看今天的第三部分，今年的情况大家都能感受，各种人员优化，大家都很忙，那如何将这种大的技术重构成本降到最低呢，我们为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;开发了很多周边能力：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.025.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;提供了图形化界面的一键迁移工具，可以一键从其他路由迁移到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;，整个迁移过程都是基于字符串匹配完成的，不涉及任何黑科技，所有的替换点也都会展示出来，非常安全。在替换完成后，自动输出改动页面与测试点，大幅减少了开发与测试的工作量。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter_giac/therouter.027.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;还有一个用于自动跳转的高效&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IDE&lt;/code&gt;辅助插件，可以直接从路由的声明处查看到哪些地方跳转到本路由，再也不用怕路由字符串满天飞了。&lt;/p&gt;

&lt;p&gt;只需要点一下左边的图标，就能自动跳转到落地页了。假设我们有多个跳转，跳转到同一个落地页的，点击落地页左侧的图标，也会展示出对应的代码，选择以后也可以自动跳转过去。&lt;/p&gt;

&lt;p&gt;另外还有一个很好的特性，就是如果你写了没有落地页的跳转，会在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IDE&lt;/code&gt;左侧有个黄色的警告，提示你是不是因为手抖或其他原因，写错了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;path&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/410430dbf99249f9930f904a606f2a39~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;另外&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;还提供了官网和微信群，官网有大量的技术文档和指导教程，有不懂的问题还可以加入微信群寻求帮助。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;官网：&lt;a href=&quot;https://therouter.cn&quot;&gt;https://therouter.cn&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;微信群：&lt;a href=&quot;https://therouter.cn/wx/&quot;&gt;https://therouter.cn/wx/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;总的来说，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 并不仅仅是一个小巧灵活的路由库，而是一整套完整的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Android&lt;/code&gt; 模块化解决方案，能够解决几乎全部的模块化过程中会遇到的问题。 对于现有的路由框架，我们也在最大限度支持平滑迁移。你也可以在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Github&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;issue&lt;/code&gt;中提出需求，我们评估后会尽快支持，也欢迎任何人提供 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pull Requests&lt;/code&gt;。&lt;/p&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 没错，TheRouter 是我写的]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/code/2022/09/05/01/</br>路由是现如今移动端开发中必不可少的功能，尤其是企业级APP，可以用于将Intent页面跳转的强依赖关系解耦，同时减少跨团队开发的互相依赖问题。&lt;br&gt; TheRouter 是一整套完全面向模块化开发的解决方案，不仅能支持常规的模块依赖解耦、页面跳转，同时提供了模块化过程中常见问题的解决办法。  - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2022-09-05]]></pubDate>
        <link><![CDATA[https://kymjs.com/code/2022/09/05/01/]]></link>
        <tags>
          
          <tag>TheRouter</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>code</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/code/2022/09/05/01/</br>&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/image/therouter/a.png&quot; alt=&quot;TheRouter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;没错，货拉拉开源的路由库 —— TheRouter 是我写的&lt;/p&gt;

&lt;p&gt;大约在17年底到18年初的时候，我经常会讲一些当时做模块化开发的心得和踩坑历程。比如这几篇都是那时候写的：《&lt;a href=&quot;https://kymjs.com/session/2018/04/22/01/&quot;&gt;Android 模块化平台设计&lt;/a&gt;》、《&lt;a href=&quot;https://kymjs.com/code/2017/11/18/01/&quot;&gt;优雅移除模块间耦合&lt;/a&gt;》、《&lt;a href=&quot;https://xiaozhuanlan.com/topic/2735849061&quot;&gt;企业级 Android 模块化平台设计建议&lt;/a&gt;》。&lt;/p&gt;

&lt;p&gt;但后来我慢慢不讲这些了，因为我发现做模块化，虽然我们能总结出来一套较为通用的解决方案，但很难通过几次短短的技术分享就跟别人讲清楚。并且很容易让人产生误解：我们是小公司，不需要做模块化。再加上因为当时是基于公司已有的基础建设，和制度的一些限制，并不能对外开源一套较为完善的模块化方案，&lt;strong&gt;开源一套完整的模块化方案&lt;/strong&gt;这个种子就一直埋下了。&lt;/p&gt;

&lt;h2 id=&quot;说回-therouter&quot;&gt;说回 TheRouter&lt;/h2&gt;

&lt;p&gt;这个名字，其实熟悉我的都知道，之前写过一个开源类 MVP 框架，叫&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheMVP&lt;/code&gt;，基本上成为了一种将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity&lt;/code&gt;看做 P 层架构的行业规范。后来被支付宝使用了，也在 设置-关于-版权信息 里面能查到，直到前几天我去反编译的时候，都还看到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BaseActivity&lt;/code&gt;用的是我的代码。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 代表了一种唯一性，表示有这个就够了。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;也是一样，我相信用过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;以后你才会真正意识到，现在的企业级&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Android&lt;/code&gt;模块化应该怎么玩。&lt;/p&gt;

&lt;h2 id=&quot;为什么要使用-therouter&quot;&gt;为什么要使用 TheRouter&lt;/h2&gt;

&lt;p&gt;路由是现如今 Android 开发中必不可少的功能，尤其是企业级APP，可以用于将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Intent&lt;/code&gt;页面跳转的强依赖关系解耦，同时减少跨团队开发的互相依赖问题。&lt;/p&gt;

&lt;p&gt;对于大型 APP 开发，基本都会选用模块化(或组件化)方式开发，对于模块间解耦要求更高。 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 是一整套完全面向模块化开发的解决方案，不仅能支持常规的模块依赖解耦、页面跳转，同时提供了模块化过程中常见问题的解决办法。例如：完美解决了模块化开发后组件内无法获取 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application&lt;/code&gt; 生命周期与业务流程，造成每次初始化与关联依赖调用都需要跨模块修改代码的问题。&lt;/p&gt;

&lt;p&gt;不过为什么要用，说到底，还是用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ARouter&lt;/code&gt;用的太头疼了。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;一个是死板，所有路由都是写死的，但凡想灵活一点，把线上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Crash&lt;/code&gt;的页面降级成H5临时解决，都得改一大堆代码还很多限制性。&lt;/li&gt;
  &lt;li&gt;另一个就是效率，不管是编译时长还是启动耗时，这俩问题都一直不解决。某个厂的开源项目都这样，作者们该晋升的晋升，该转岗的转岗，剩下的躺平不管，毕竟修修补补这事不占KPI，没法述职啊。没办法，自己来吧，谁让我们还有启动耗时指标的。&lt;/li&gt;
  &lt;li&gt;再就是遇到的一个坑，在用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tinker&lt;/code&gt;下发补丁的时候，发现同一个分支打出来的包，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ARouter&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Butterknife&lt;/code&gt;的产物包代码都不一样，直接增大了补丁体积。&lt;/li&gt;
  &lt;li&gt;当然，还有很多差异，看这个表格吧。&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;功能&lt;/th&gt;
      &lt;th&gt;TheRouter&lt;/th&gt;
      &lt;th&gt;ARouter&lt;/th&gt;
      &lt;th&gt;WMRouter&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Fragment路由&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;支持依赖注入&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;加载路由表&lt;/td&gt;
      &lt;td&gt;无运行时扫描&lt;br /&gt;无反射&lt;/td&gt;
      &lt;td&gt;运行时扫描dex&lt;br /&gt;反射实例类&lt;br /&gt;性能损耗大&lt;/td&gt;
      &lt;td&gt;运行时读文件&lt;br /&gt;反射实例类&lt;br /&gt;性能损耗中&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;注解正则表达式&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Activity指定拦截器&lt;/td&gt;
      &lt;td&gt;✔️（四大拦截器可根据业务定制）&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;导出路由文档&lt;/td&gt;
      &lt;td&gt;✔️（路由文档支持添加注释描述）&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;动态注册路由信息&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;APT支持增量编译&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✔️（开启文档生成则无法增量编译）&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;plugin支持增量编译&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;多 Path 对应同一页面（低成本实现双端path统一）&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;远端路由表下发&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;支持单模块独立初始化&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;支持使用路由打开第三方库页面&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;对热修复支持（例如tinker）&lt;/td&gt;
      &lt;td&gt;✔️(未改变的代码多次构建无变动)&lt;/td&gt;
      &lt;td&gt;✖️(多次构建apt产物会发生变化，生成无意义补丁)&lt;/td&gt;
      &lt;td&gt;✖️(多次构建apt产物会发生变化，生成无意义补丁)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;动态页面路由能力&quot;&gt;动态页面路由能力&lt;/h2&gt;

&lt;p&gt;其实单纯的页面路由，没什么好说的，基本上所有人都是这么做的。APT编译期生成一个描述类，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gradle&lt;/code&gt;插件聚合所有的描述类，应用启动的时候再加载描述类，就这么一个流程。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 文档里面写的非常详细了，这里主要讲讲路由在现代APP中要怎么用。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 从设计阶段，考虑的就是APP动态化能力。所以既能支持第三方&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SDK&lt;/code&gt;的路由跳转，也能支持插件化的开发形态，又能处理&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;H5Hybrid&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Flutter&lt;/code&gt;混合的这种项目，反正路由表都是可以随便添加。&lt;/p&gt;

&lt;p&gt;那，真正用处最多的是通过动态下发，提升客户端容灾能力。&lt;br /&gt;
比如在线上，某些页面或者核心下单交易流程因为客户端开发疏忽，造成无法使用的情况，可以通过路由将对应页面降级为H5或者小程序，保证线上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APP&lt;/code&gt;依然是可用的状态。&lt;/p&gt;

&lt;p&gt;有两种推荐的远程下发方式可供使用方选择：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;将打包系统与配置系统打通，每次新版本APP打包后自动将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assets/&lt;/code&gt;目录中的配置文件上传到配置系统，下发给对应版本APP 。优点在于全自动不会出错。&lt;/li&gt;
  &lt;li&gt;配置系统无法打通，线上手动下发需要修改的路由项，因为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 会自动用最新下发的路由项覆盖包内的路由项。优点在于精确，且流量资源占用小。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;注：一旦你设置了自定义的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InitTask&lt;/code&gt;，原框架内路由表初始化任务将不再执行，你需要自己处理找不到路由表时的兜底逻辑，一种建议的处理方式见如下代码。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 此代码 必须 在 Application.super.onCreate() 之前调用&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;RouteMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setInitTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RouterMapInitTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/** 
     * 此方法执行在异步
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;asyncInitRouteMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 此处为纯业务逻辑，每家公司远端配置方案可能都不一样&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 不建议每次都请求网络，否则请求网络的过程中，路由表是空的，可能造成APP无法跳转页面&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 最好是优先加载本地，然后开异步线程加载远端配置&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Connfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;doHttp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;routeMap&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 建议加一个判断，如果远端配置拉取失败，使用包内配置做兜底方案，否则可能造成路由表异常&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TextUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;RouteItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Gson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;fromJson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TypeToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;RouteItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 建议远端下发路由表差异部分，用远端包覆盖本地更合理&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;RouteMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addRouteMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 在异步执行TheRouter内部兜底路由表&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;initRouteMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;另一种情况，如果某些页面传参过程中，漏传了一些固定参数，也可以通过动态下发路由表的方式，对不同的页面，做动态的默认参数注入，这样就能达到不发版也能直接修复某些参数引起的小问题。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;中下发路由表的格式：&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://kymjs.com/therouter/test&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;className&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;com.therouter.app.autoinit.TestActivity&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;params&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    	&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;......&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;单模块自动初始化能力&quot;&gt;单模块自动初始化能力&lt;/h2&gt;

&lt;p&gt;其实，做模块化最麻烦的两个点，第一个是依赖解耦，第二个应该就是独立模块的初始化问题了。再加上现在对于隐私合规问题越查越严，各种权限都必须在隐私弹窗授权以后才能使用，使得模块独立更难，动不动就得改到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application&lt;/code&gt;壳工程。&lt;/p&gt;

&lt;p&gt;TheRouter 的单模块自动初始化能力就是为了解决这样的情况，可以只在当前模块声明初始化方法后，将会在业务场景时自动被调用。&lt;/p&gt;

&lt;p&gt;类似于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gradle&lt;/code&gt; 的 Task，你也可以声明自己的初始化 Task，然后声明的时候提供好需要依赖的其他 Task，这样只要依赖的那个 Task 没有初始化，你的任务就不会被初始化。直到依赖的那个 Task 初始化完成，你的任务才会被自动调用。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * 将会在异步执行
 */&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@FlowTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;taskName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;mmkv_init&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dependsOn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TheRouterFlowTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;APP_ONCREATE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;异步=========Application onCreate后执行&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;@FlowTask 注解参数说明：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;taskName：当前初始化任务的任务名，必须全局唯一，建议格式为：moduleName_taskName&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;dependsOn：参考Gradle Task，任务与任务之间可能会有依赖关系。如果当前任务需要依赖其他任务先初始化，则在这里声明依赖的任务名。可以同时依赖多个任务，用英文逗号分隔，空格可选，会被过滤：dependsOn = “mmkv, config, login”，默认为空，应用启动就被调用&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;async：是否要在异步执行此任务，默认false。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;内置初始化节点&quot;&gt;内置初始化节点&lt;/h3&gt;

&lt;p&gt;使用这个能力，在路由内部默认支持了两个生命周期类任务，可在使用时直接引用&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;TheRouterFlowTask.APP_ONCREATE&lt;/strong&gt;：当Application的onCreate()执行后初始化&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;TheRouterFlowTask.APP_ONSPLASH&lt;/strong&gt;：当应用的首个Activity.onCreate()执行后初始化&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;同时，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;的自动初始化依赖，也无需担心循环依赖造成的问题，框架会在编译期构建有向无环图，监测循环依赖情况，如果发现会在编译期直接报错，并且还会将发生循环引用的任务显示出来，用于排错。&lt;/p&gt;

&lt;h2 id=&quot;动态化能力&quot;&gt;动态化能力&lt;/h2&gt;

&lt;p&gt;还有一个，动态化能力。这个能力其实是需要整个项目公司的配合，比如有一套类似智慧大脑的方案，可以基于客户端过去的一些埋点数据，智能推断出用户下一步要做的事情，然后通过长连接直接向客户端下发指令做某些事情。&lt;/p&gt;

&lt;p&gt;不过抛开后端的能力，单独靠客户端也是可以使用的。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Action&lt;/code&gt; 本质是一个全局的系统回调，主要用于预埋的一系列操作，例如：弹窗、上传日志、清理缓存。 &lt;br /&gt;
与 Android 系统自带的广播通知类似，你可以在任何地方声明动作与处理方式。并且所有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Action&lt;/code&gt;都是可以被跟踪的，只要你愿意，可以在日志中将所有的动作调用栈输出，以方便调试使用。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2022/06/08/XrUy8g.png&quot; alt=&quot;TheRouter-ActionManager&quot; /&gt;&lt;/p&gt;

&lt;p&gt;当用户执行某些操作（打开某个页面、H5点击某个按钮、动态页面配置的点击事件）时，将会自动触发，执行预埋的 Action 逻辑。&lt;/p&gt;

&lt;p&gt;但还是强烈推荐，将端上数据与服务端链路打通，根据客户端不同的用户行为，交由后端分析，进而推测出用户下一步动作，提前执行下发逻辑交给客户端执行，则是一套完整的动态化方案。&lt;/p&gt;

&lt;h2 id=&quot;模块化支持gradle脚本一键切换源码引用&quot;&gt;模块化支持，Gradle脚本一键切换源码引用&lt;/h2&gt;

&lt;p&gt;在模块化开发过程中，如果没有采用分仓，或采用了分仓但依然使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git-submodule&lt;/code&gt; 的方式开发，应该都会遇到一个问题。如果集成包采用源码编译，构建时间实在太久，大大降低开发调试效率；如果采用aar依赖编译，对于底层模块修改了代码，每次都要重新构建aar，在上层模块修改版本号以后，才能继续整包构建编译，也极大影响开发效率。&lt;br /&gt;
TheRouter 中提供了一个 Gradle 脚本，只需要在开发本地的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;local.properties&lt;/code&gt;文件中声明要参与编译的module，其他未声明的默认使用aar编译，这样就能灵活切换源码与aar，并且不会影响其他人，如下节选代码可供参考使用：&lt;/p&gt;

&lt;div class=&quot;language-groovy highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * 如果工程中有源码，则依赖源码，否则依赖aar
 */&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;moduleApi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compileStr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Closure&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configureClosure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compileStr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;:&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;artifactid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;includeModule&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rootProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAllprojects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rootProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;includeModule&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;includeModule&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;artifactid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;源码依赖：===project(\&quot;:$artifactid\&quot;)&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;projects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;dependencies&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;api&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;:&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;artifactid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configureClosure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//        projects.project.configurations { compile.exclude group: group, module: artifactid }&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;依赖：=======$group:$artifactid:$version&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;projects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;dependencies&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;api&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;$group:$artifactid:$version&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configureClosure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在实际使用时，可以完全使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moduleApi&lt;/code&gt; 替换掉原有的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api&lt;/code&gt;。当然， &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;implementation&lt;/code&gt;也可以有一个对应的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moduleImplementation&lt;/code&gt;，这样只需要注释或解注释&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setting.gradle&lt;/code&gt;文件内的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include&lt;/code&gt;语句就可以达到切换源码、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aar&lt;/code&gt;的目的了。 &lt;br /&gt;
具体的使用方法，可以看我这篇文章里面讲的【源码与aar互斥】的实现：&lt;a href=&quot;https://xiaozhuanlan.com/topic/2735849061&quot;&gt;https://xiaozhuanlan.com/topic/2735849061&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;什么年代了还在用-arouter&quot;&gt;什么年代了，还在用 ARouter?&lt;/h2&gt;

&lt;p&gt;支持从 ARouter 一键迁移！&lt;br /&gt;
没错，什么年代了，还在用ARouter？ &lt;br /&gt;
对于这种已有的存量路由框架，当然也是提供了一键迁移的图形化工具。&lt;br /&gt;
为了写这个工具我也是废了好大的劲，特意学了一遍JavaFX怎么用，然后打了一个Mac产物、一个Windows产物。&lt;br /&gt;
不禁感叹：Java的跨平台才是真正的跨平台啊。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;注：传到了GitHub，可能有点慢，耐心等待&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Mac OS 迁移工具下载：&lt;a href=&quot;https://github.com/HuolalaTech/hll-wp-therouter-android/wiki/uploads/file/TheRouterTransfer-Mac.zip&quot;&gt;https://github.com/HuolalaTech/hll-wp-therouter-android/wiki/uploads/file/TheRouterTransfer-Mac.zip&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Windows 迁移工具下载：&lt;a href=&quot;https://github.com/HuolalaTech/hll-wp-therouter-android/wiki/uploads/file/TheRouterTransfer-Windows.zip&quot;&gt;https://github.com/HuolalaTech/hll-wp-therouter-android/wiki/uploads/file/TheRouterTransfer-Windows.zip&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/therouter/5.png&quot; alt=&quot;TheRouter迁移工具&quot; /&gt;&lt;/p&gt;

]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 货拉拉 Android 模块化路由框架：TheRouter]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/code/2022/09/04/01/</br>路由是现如今移动端开发中必不可少的功能，尤其是企业级APP，可以用于将Intent页面跳转的强依赖关系解耦，同时减少跨团队开发的互相依赖问题。&lt;br&gt; TheRouter 是一整套完全面向模块化开发的解决方案，不仅能支持常规的模块依赖解耦、页面跳转，同时提供了模块化过程中常见问题的解决办法。  - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2022-09-04]]></pubDate>
        <link><![CDATA[https://kymjs.com/code/2022/09/04/01/]]></link>
        <tags>
          
          <tag>TheRouter</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>code</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/code/2022/09/04/01/</br>&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 是一个 Kotlin 编写，用于 Android 模块化开发的一整套解决方案框架。&lt;br /&gt;
Github 项目地址与使用文档详见 &lt;a href=&quot;https://github.com/HuolalaTech/hll-wp-therouter-android&quot;&gt;https://github.com/HuolalaTech/hll-wp-therouter-android&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;TheRouter 核心功能具备如下能力：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;页面导航跳转能力(Navigator)&lt;/li&gt;
  &lt;li&gt;跨模块依赖注入能力(ServiceProvider)&lt;/li&gt;
  &lt;li&gt;单模块自动初始化能力(FlowTaskExecutor)&lt;/li&gt;
  &lt;li&gt;动态化能力(ActionManager)&lt;/li&gt;
  &lt;li&gt;模块AAR/源码依赖一键切换脚本&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;一为什么要使用-therouter&quot;&gt;一、为什么要使用 TheRouter&lt;/h3&gt;

&lt;p&gt;路由是现如今移动端开发中必不可少的功能，尤其是企业级APP，可以用于将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Intent&lt;/code&gt;页面跳转的强依赖关系解耦，同时减少跨团队开发的互相依赖问题。&lt;/p&gt;

&lt;p&gt;对于大型 APP 开发，基本都会选用模块化(或组件化)方式开发，对于模块间解耦要求更高。 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 是一整套完全面向模块化开发的解决方案，不仅能支持常规的模块依赖解耦、页面跳转，同时提供了模块化过程中常见问题的解决办法。例如：完美解决了模块化开发后由于组件内无法获取 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application&lt;/code&gt; 生命周期与业务流程，造成每次初始化与关联依赖调用都需要跨模块修改代码的问题。&lt;/p&gt;

&lt;h4 id=&quot;11-therouter-四大能力&quot;&gt;1.1 TheRouter 四大能力&lt;/h4&gt;

&lt;p&gt;Navigator：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;支持 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fragment&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;支持&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Path&lt;/code&gt;与页面多对一关系或一对一关系，可用于解决多端path统一问题&lt;/li&gt;
  &lt;li&gt;页面&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Path&lt;/code&gt;支持正则表达式声明&lt;/li&gt;
  &lt;li&gt;支持 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json&lt;/code&gt; 格式路由表导出&lt;/li&gt;
  &lt;li&gt;支持动态下发 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json&lt;/code&gt; 路由表，降级任意页面为H5&lt;/li&gt;
  &lt;li&gt;支持任意&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt;跨模块传递（无需序列化，且能保证对象类型）&lt;/li&gt;
  &lt;li&gt;支持页面跳转拦截处理&lt;/li&gt;
  &lt;li&gt;支持自定义页面参数解析方式（例如将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json&lt;/code&gt;解析为对象）&lt;/li&gt;
  &lt;li&gt;支持使用路由跳转到第三方 SDK 中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity&lt;/code&gt;(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fragment&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ServiceProvider：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;支持跨模块依赖注入&lt;/li&gt;
  &lt;li&gt;支持自定义注入项的创建规则，依赖注入可自定义参数&lt;/li&gt;
  &lt;li&gt;支持自定义服务拦截，单模块&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mock&lt;/code&gt;调试&lt;/li&gt;
  &lt;li&gt;支持注入对象缓存，多次注入 只会new一次对象&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FlowTaskExecutor：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;支持单模块独立初始化&lt;/li&gt;
  &lt;li&gt;支持懒加载初始化&lt;/li&gt;
  &lt;li&gt;独立初始化允许多任务依赖(参考&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gradle Task&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;支持编译期循环引用检测&lt;/li&gt;
  &lt;li&gt;支持自定义业务初始化时机，可以用于解决隐私合规问题&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ActionManager：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;支持全局回调配置&lt;/li&gt;
  &lt;li&gt;支持优先级响应与中断响应&lt;/li&gt;
  &lt;li&gt;支持记录调用路径，解决调试期观察者模式无法追踪&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Observable&lt;/code&gt;的问题&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;注：&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FlowTaskExecutor&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActionManager&lt;/code&gt; 后续会作为可选能力，提供&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;可插拔&lt;/code&gt;或&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;单独使用&lt;/code&gt;的选项（预计10月份提供）。&lt;/p&gt;

&lt;h3 id=&quot;二路由方案&quot;&gt;二、路由方案&lt;/h3&gt;

&lt;p&gt;目前现有的路由基本上集中于两种能力的实现：页面跳转、跨模块调用，核心技术方案大体上如图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/therouter/1.png&quot; alt=&quot;TheRouter&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;开发阶段，对要使用路由的落地页或被调用方法添加注解标识。&lt;/li&gt;
  &lt;li&gt;编译期解析注解，生成一系列中间代码，待调用。&lt;/li&gt;
  &lt;li&gt;应用启动后调用中间代码完成路由的准备动作。大部分路由会额外通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gradle Transform&lt;/code&gt;，在编译期做一次聚合，以提升运行时准备路由表的效率。&lt;/li&gt;
  &lt;li&gt;发起路由跳转时，本质上就是一次路由表遍历，通过uri获取到对应的落地页或方法对象，进行调用。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 的页面跳转、跨模块调用也是如此，但是在设计上会有一些细节处理。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/therouter/2.png&quot; alt=&quot;TheRouter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 会在编译期根据注解生成 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RouteMap__&lt;/code&gt;开头的类，这些类中记录了当前模块的所有路由信息，也就是当前模块的路由表。&lt;/p&gt;

&lt;p&gt;在最顶层的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app&lt;/code&gt;模块中，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gradle&lt;/code&gt;插件，将所有aar、源码中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RouteMap__&lt;/code&gt;开头的类统一集中到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouterServiceProvideInjecter&lt;/code&gt;类中。&lt;/p&gt;

&lt;p&gt;后续应用启动后，初始化路由时只需要执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouterServiceProvideInjecter&lt;/code&gt;类的方法，就能&lt;strong&gt;没有任何反射&lt;/strong&gt;的加载到全部的路由表了。&lt;/p&gt;

&lt;p&gt;加载以后的路由表会被保存到一个支持正则匹配的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Map&lt;/code&gt; 中，这也是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;允许多个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;path&lt;/code&gt;对应同一个落地页的原因。每当发生页面跳转时，通过跳转时的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;path&lt;/code&gt;，去&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Map&lt;/code&gt;中获取到对应的落地页信息，再正常调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;startActivity()&lt;/code&gt;即可。&lt;/p&gt;

&lt;h3 id=&quot;三使用-therouter-页面跳转&quot;&gt;三、使用 TheRouter 页面跳转&lt;/h3&gt;

&lt;h4 id=&quot;31-声明路由项&quot;&gt;3.1 声明路由项&lt;/h4&gt;

&lt;p&gt;如果一个页面（支持 Activity、Fragment）允许被路由打开，则需要使用注解 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Route&lt;/code&gt; 声明路由项，每个页面允许声明多个路由项，也就是一对多的能力，极大降低多端路由统一时的业务影响面。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;参数释义&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;path&lt;/strong&gt;: 路由path 【必传】。 &lt;br /&gt;
 建议是一个url。path内支持使用正则表达式（为了匹配效率，正则必须包含反双斜杠\），允许多个path对应同一个Activity(Fragment)。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;action&lt;/strong&gt;: 自定义事件【可选】。&lt;br /&gt;
 一般用来打开目标页面后做一个执行动作，例如自定义页面弹出广告弹窗。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;description&lt;/strong&gt;: 页面描述【可选】。&lt;br /&gt;
 会被记录到路由表中，方便后期排查的时候知道每个path或Activity是什么业务。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;params&lt;/strong&gt;: 页面参数【可选】。 &lt;br /&gt;
 自动写入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;intent&lt;/code&gt;中，允许写在路由表中动态下发修改默认值，或通过路由跳转时代码传入。&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;nd&quot;&gt;@Route&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;http://therouter.com/home&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;action://scheme.com&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;第二个页面&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HomeActivity&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AppCompatActivity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;32-发起页面跳转&quot;&gt;3.2 发起页面跳转&lt;/h4&gt;

&lt;p&gt;传入的参数可以是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; 和8种基本数据类型、也可以是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bundle&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Serializable&lt;/code&gt;、
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Parcelable&lt;/code&gt;对象，跟 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Intent&lt;/code&gt; 传值规则一致。 &lt;br /&gt;
同时也支持为本次跳转的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Intent&lt;/code&gt; 添加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Flag/Uri/ClipData/identifier&lt;/code&gt;等业务特殊参数。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 传入参数可以通过注解 @Autowired 解析成任意类型，如果是对象建议传json&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// context 参数如果不传或传 null，会自动使用 application 替换&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;TheRouter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://therouter.com/home&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;12345678&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key2&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;参数&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withBoolean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key3&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withSerializable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key4&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 这个方法可以传递任意对象，但是接收的地方对象类型需自行保证一致，否则会强转异常&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;navigation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 如果传入 requestCode，默认使用startActivityForResult启动Activity&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;navigation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 如果要打开的是fragment，需要使用&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createFragment&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;33-路由表生成规则&quot;&gt;3.3 路由表生成规则&lt;/h4&gt;

&lt;p&gt;如果两条路由的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;path&lt;/code&gt;、目标&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;className&lt;/code&gt;完全相同，则认为是同一条路由，&lt;strong&gt;不会考虑参数是否相同&lt;/strong&gt;。 &lt;br /&gt;
路由表生成规则：编译期按照如下顺序取&lt;strong&gt;并集&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;覆盖规则&lt;/strong&gt;： &lt;br /&gt;
根据如下顺序，如果相同，后者可以覆盖前者的路由表规则。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;编译期解析注解生成路由表&lt;/li&gt;
  &lt;li&gt;首先取 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;业务模块 aar&lt;/code&gt; 中的路由表&lt;/li&gt;
  &lt;li&gt;再取 主&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app module&lt;/code&gt; 代码中的路由表&lt;/li&gt;
  &lt;li&gt;最后取 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assets/RouteMap.json&lt;/code&gt; 文件中声明的路由表。
    &lt;ul&gt;
      &lt;li&gt;如果编译期没有这个文件，会生成一份默认路由表放在这个目录内；如果有，会将路由表合并。&lt;/li&gt;
      &lt;li&gt;路由表生成时可配置是否启用检查路由合法性，判断目标页面是否存在，(warning/error)级别。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;运行时线上动态下发的路由表
    &lt;ul&gt;
      &lt;li&gt;路由表允许线上动态下发，将覆盖本地路由表，详见 【3.4 动态路由表的设计与使用】&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果编译期没有这个文件，会生成一份默认路由表放在这个目录内；如果有，会将路由表合并，因此，对于没办法修改代码的第三方SDK内部，如果希望通过路由打开，只需要手动在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RouteMap.json&lt;/code&gt;文件中声明，就能通过路由打开了。&lt;/p&gt;

&lt;h4 id=&quot;34-动态路由表的设计与使用&quot;&gt;3.4 动态路由表的设计与使用&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 的路由表是动态添加的，项目每次编译后，会在 apk 内生成一份当前 APP 的全量路由表，默认路径为：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/assets/therouter/routeMap.json&lt;/code&gt;。这个路由表也可以后续通过远程下发的方式使用，例如远端可以针对不同的APP版本，下发不同的路由表达到配置目的。这样如果将来线上某些页面发生Crash，可以通过将这个页面的落地页替换为H5的方式，临时解决这类问题。&lt;/p&gt;

&lt;p&gt;有两种推荐的远程下发方式可供使用方选择：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;将打包系统与配置系统打通，每次新版本APP打包后自动将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assets/&lt;/code&gt;目录中的配置文件上传到配置系统，下发给对应版本APP 。优点在于全自动不会出错。&lt;/li&gt;
  &lt;li&gt;配置系统无法打通，线上手动下发需要修改的路由项，因为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 会自动用最新下发的路由项覆盖包内的路由项。优点在于精确，且流量资源占用小。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;注：一旦你设置了自定义的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InitTask&lt;/code&gt;，原框架内路由表初始化任务将不再执行，你需要自己处理找不到路由表时的兜底逻辑，一种建议的处理方式见如下代码。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 此代码 必须 在 Application.super.onCreate() 之前调用&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;RouteMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setInitTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RouterMapInitTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/** 
     * 此方法执行在异步
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;asyncInitRouteMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 此处为纯业务逻辑，每家公司远端配置方案可能都不一样&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 不建议每次都请求网络，否则请求网络的过程中，路由表是空的，可能造成APP无法跳转页面&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 最好是优先加载本地，然后开异步线程加载远端配置&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Connfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;doHttp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;routeMap&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 建议加一个判断，如果远端配置拉取失败，使用包内配置做兜底方案，否则可能造成路由表异常&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TextUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;RouteItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Gson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;fromJson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TypeToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;RouteItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 建议远端下发路由表差异部分，用远端包覆盖本地更合理&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;RouteMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addRouteMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 在异步执行TheRouter内部兜底路由表&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;initRouteMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;35-高级用法&quot;&gt;3.5 高级用法&lt;/h4&gt;

&lt;p&gt;TheRouter同时支持更多页面跳转能力，详情可参考项目文档【&lt;a href=&quot;https://github.com/HuolalaTech/hll-wp-therouter-android/wiki/Navigator&quot;&gt;https://github.com/HuolalaTech/hll-wp-therouter-android/wiki/Navigator&lt;/a&gt;】：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;为第三方库里面的页面添加路由表，达到对某些页面降级替换的目的；&lt;/li&gt;
  &lt;li&gt;延迟路由跳转（从Android 8开始，不能在后台启动页面）；&lt;/li&gt;
  &lt;li&gt;跳转过程拦截器（总共四层，可根据实际需求使用）；&lt;/li&gt;
  &lt;li&gt;跳转结果回调；&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;四跨模块依赖注入-serviceprovider-的设计&quot;&gt;四、跨模块依赖注入 ServiceProvider 的设计&lt;/h3&gt;

&lt;p&gt;对于模块化开发中跨模块的调用，我们推荐采用 &lt;a href=&quot;https://zh.m.wikipedia.org/zh-cn/%E9%9D%A2%E5%90%91%E6%9C%8D%E5%8A%A1%E7%9A%84%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84&quot;&gt;SOA(面向服务架构)&lt;/a&gt; 的设计方式，服务调用方与使用方完全隔离，调用模块外的能力不需要关注能力的提供者是谁。&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ServiceProvider&lt;/code&gt; 的核心设计思想也是这样的，目前服务间的调用协议采用接口的方式。当然，也可以兼容不通过接口下沉而是直接调用的情况。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/therouter/3.jpeg&quot; alt=&quot;TheRouter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;具体到 Android 侧就是 AIDL 类似的设计，只是要比AIDL开发简单很多：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;服务提供方负责提供服务，不需要关心调用方是谁会在何时调用自己。&lt;/li&gt;
  &lt;li&gt;服务的使用方只关注服务本身，不需要关心这个服务是谁提供的，只需要只能服务能提供哪些能力即可。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;例如上面的图片：拉拉需要使用录音的服务，小货则向外提供一个录音的服务，由&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ServiceProvider&lt;/code&gt;负责撮合。&lt;/p&gt;

&lt;h4 id=&quot;41-服务使用方拉拉&quot;&gt;4.1 服务使用方：拉拉&lt;/h4&gt;

&lt;p&gt;她无需关心，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IRecordService&lt;/code&gt;这个接口服务是谁提供的，他只需要知道自己需要使用这样的一个服务就行了。&lt;br /&gt;
注：如果没有提供服务的提供方，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter.get()&lt;/code&gt;可能返回&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;TheRouter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;IRecordService:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)?.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;doRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;42-服务提供方小货&quot;&gt;4.2 服务提供方：小货&lt;/h4&gt;

&lt;p&gt;服务提供方需要声明一个提供服务的方法，用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@ServiceProvider&lt;/code&gt;注解标记。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;如果是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;java&lt;/code&gt;，必须是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public static&lt;/code&gt; 修饰&lt;/li&gt;
  &lt;li&gt;如果是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kotlin&lt;/code&gt;，建议写成 top level 的函数&lt;/li&gt;
  &lt;li&gt;方法名不限&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * 方法名不限定，任意名字都行
 * 返回值必须是服务接口名，如果是实现了服务的子类，需要加上returnType限定（例如下面代码）
 * 方法必须加上 public static 修饰，否则编译期就会报错
 */&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@ServiceProvider&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IRecordService&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;IRecordService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;doRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;执行录制逻辑&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 也可以直接返回对象，然后标注这个方法的服名是什么&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@ServiceProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;returnType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IRecordService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RecordServiceImpl&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// xxx &lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;五单模块自动初始化能力-flowtaskexecutor-的设计&quot;&gt;五、单模块自动初始化能力 FlowTaskExecutor 的设计&lt;/h3&gt;

&lt;p&gt;前面讲过，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;是完全面向模块化开发提供的一套解决方案。在模块化开发时，可能每个模块都有自己需要初始化的一些代码。以前的做法是把这些代码都在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application&lt;/code&gt;里声明，但是这样可能随着业务变动每次都需要修改&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application&lt;/code&gt;所在模块。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 的单模块自动初始化能力就是为了解决这样的情况，可以只在当前模块声明初始化方法后，将会在业务场景时自动被调用。&lt;/p&gt;

&lt;p&gt;每个希望被自动初始化的方法，必须使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public static&lt;/code&gt;修饰，主要原因是这样子就能通过类名直接调用了。另外很多初始化代码都需要获取&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Context&lt;/code&gt;对象，所以我们将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Context&lt;/code&gt;作为初始化方法的默认参数，会自动传入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application&lt;/code&gt;。其他的所在类名、方法名都没有限制，反正只要加上了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@FlowTask&lt;/code&gt; 注解，在编译期都能通过 APT 获取到。&lt;/p&gt;

&lt;h4 id=&quot;51-flowtaskexecutor-使用介绍&quot;&gt;5.1 FlowTaskExecutor 使用介绍&lt;/h4&gt;

&lt;p&gt;可以在当前模块中，任意类中声明一个任意方法名的方法，给方法添加上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@FlowTask&lt;/code&gt; 的注解即可。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@FlowTask&lt;/code&gt; 注解参数说明：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;taskName&lt;/strong&gt;：当前初始化任务的任务名，必须全局唯一，建议格式为：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moduleName_taskName&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;dependsOn&lt;/strong&gt;：参考&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gradle&lt;/code&gt; Task，任务与任务之间可能会有依赖关系。如果当前任务需要依赖其他任务先初始化，则在这里声明依赖的任务名。可以同时依赖多个任务，用英文逗号分隔，空格可选，会被过滤：dependsOn = “mmkv, config, login”，默认为空，应用启动就被调用&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;async&lt;/strong&gt;：是否要在异步执行此任务，默认false。&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * 将会在异步执行
 */&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@FlowTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;taskName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;mmkv_init&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dependsOn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TheRouterFlowTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;APP_ONCREATE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;异步=========Application onCreate后执行&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@FlowTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;taskName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;app1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;main线程=========应用启动就会执行&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/**
 * 将会在主线程初始化
 */&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@FlowTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;taskName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dependsOn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;mmkv,app1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;main线程=========在app1和mmkv两个任务都执行以后才会被执行&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;52内置初始化节点&quot;&gt;5.2内置初始化节点&lt;/h4&gt;

&lt;p&gt;使用这个能力，在路由内部默认支持了两个生命周期类任务，可在使用时直接引用&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;TheRouterFlowTask.APP_ONCREATE&lt;/strong&gt;：当Application的onCreate()执行后初始化&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;TheRouterFlowTask.APP_ONSPLASH&lt;/strong&gt;：当应用的首个Activity.onCreate()执行后初始化&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;同时，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;的自动初始化依赖，也无需担心循环依赖造成的问题，框架会在编译期构建有向无环图，监测循环依赖情况，如果发现会在编译期直接报错，并且还会将发生循环引用的任务显示出来，用于排错。&lt;/p&gt;

&lt;h4 id=&quot;53--实现原理&quot;&gt;5.3  实现原理&lt;/h4&gt;

&lt;p&gt;每个加了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@FlowTask&lt;/code&gt; 注解的方法，都会在编译期被解析，生成一个对应的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Task&lt;/code&gt; 对象，这个对象包含了初始化方法的相关信息，比如：是否异步执行、任务名、是否依赖其他任务先执行。&lt;/p&gt;

&lt;p&gt;当所有aar都编译完成，生成好全部的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Task&lt;/code&gt; 以后，会在主 app 中通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gradle&lt;/code&gt;插件进行聚合，在这时会将所有的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Task&lt;/code&gt; 做一次检查，通过构建&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;有向无环图&lt;/code&gt;来防止 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Task&lt;/code&gt; 发生循环引用的情况。&lt;/p&gt;

&lt;p&gt;每次应用启动后，会在路由初始化时，将有向图中的全部&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Task&lt;/code&gt;，按照依赖关系按顺序加载。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/therouter/4.png&quot; alt=&quot;TheRouter&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;六动态化能力-actionmanager-的设计&quot;&gt;六、动态化能力 ActionManager 的设计&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Action&lt;/code&gt; 本质是一个全局的系统回调，主要用于预埋的一系列操作，例如：弹窗、上传日志、清理缓存。&lt;br /&gt;
与 Android 系统自带的广播通知类似，你可以在任何地方声明动作与处理方式。并且所有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Action&lt;/code&gt;都是可以被跟踪的，只要你愿意，可以在日志中将所有的动作调用栈输出，以方便调试使用，这样在一定程度上可以解决观察者模式带来的通病：&lt;strong&gt;无法追踪&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Observable&lt;/code&gt;的问题&lt;/strong&gt;。&lt;/p&gt;

&lt;h4 id=&quot;61-action-使用&quot;&gt;6.1 Action 使用&lt;/h4&gt;

&lt;p&gt;声明一个 Action：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// action建议遵循一定的格式&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ACTION&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;therouter://action/xxx&quot;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@FlowTask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;taskName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;action_demo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;context:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;TheRouter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addActionInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ACTION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;object:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActionInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;context:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;args:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Bundle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Boolean&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// do something&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;执行一个 Action：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// action建议遵循一定的格式&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ACTION&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;therouter://action/xxx&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 如果执行了一个没有被声明的Action，则不会有任何动作&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;TheRouter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ACTION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;62-高级用法&quot;&gt;6.2 高级用法&lt;/h4&gt;

&lt;p&gt;每个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Action&lt;/code&gt; 允许关联多个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActionInterceptor&lt;/code&gt;进行处理，多个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActionInterceptor&lt;/code&gt;之间可以自定义拦截器优先级，同时允许终止接下来的低优先级拦截器的执行。&lt;/p&gt;

&lt;p&gt;最典型应用场景：首页可能会有多个弹窗，不同业务之间的弹窗是有优先级之分的，为了体验优化我们肯定不会在首页一次把所有弹窗全部弹出，可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActionInterceptor&lt;/code&gt;为每个弹窗声明好优先级关系，假设需求是首页只能弹出3个弹窗，那么第三个弹窗处理完毕即可关闭当前事件，接下来的拦截器将不会被响应。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActionInterceptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;context:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;args:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Bundle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Boolean&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onFinish&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 数字越大，优先级越高
     */&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;open&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;priority:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Int&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;63-客户端动态响应使用场景&quot;&gt;6.3 客户端动态响应使用场景&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;如果仅客户端使用&lt;/strong&gt;，常用的场景可能是：当用户执行某些操作（打开某个页面、H5点击某个按钮、动态页面配置的点击事件）时，将会自动触发，执行预埋的 Action 逻辑。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;如果与服务端链路打通&lt;/strong&gt;，这个能力其实是需要整个公司的配合，比如有一套类似智慧大脑的方案，可以基于客户端过去的一些埋点数据，智能推断出用户下一步要做的事情，然后通过长连接直接向客户端下发指令做某些事情。那么通过客户端预埋的页面跳转、弹窗、清缓存、退出登录等等操作，就可以通过服务端指令进行操作，则就是一套完整的动态化方案。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/therouter/actionmanager.png&quot; alt=&quot;TheRouter-ActionManager&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;七一键切换源码与-aar&quot;&gt;七、一键切换源码与 AAR&lt;/h3&gt;

&lt;h4 id=&quot;71-模块化支持的-gradle-脚本&quot;&gt;7.1 模块化支持的 Gradle 脚本&lt;/h4&gt;

&lt;p&gt;在模块化开发过程中，如果没有采用分仓，或采用了分仓但依然使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git-submodule&lt;/code&gt; 的方式开发，应该都会遇到一个问题。如果集成包采用源码编译，构建时间实在太久，大大降低开发调试效率；如果采用aar依赖编译，对于底层模块修改了代码，每次都要重新构建aar，在上层模块修改版本号以后，才能继续整包构建编译，也极大影响开发效率。&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 中提供了一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gradle&lt;/code&gt; 脚本，只需要在开发本地的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;local.properties&lt;/code&gt;文件中声明要参与编译的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module&lt;/code&gt;，其他未声明的默认使用aar编译，这样就能灵活切换源码与aar，并且不会影响其他人，如下节选代码可供参考使用：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * 如果工程中有源码，则依赖源码，否则依赖aar
 */&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;moduleApi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compileStr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Closure&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configureClosure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compileStr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;:&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;artifactid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;includeModule&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rootProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAllprojects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rootProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;includeModule&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;includeModule&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;artifactid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;源码依赖：===project(\&quot;:$artifactid\&quot;)&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;projects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;dependencies&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;api&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;&apos;:&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;artifactid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configureClosure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//        projects.project.configurations { compile.exclude group: group, module: artifactid }&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;依赖：=======$group:$artifactid:$version&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;projects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;dependencies&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;api&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;$group:$artifactid:$version&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configureClosure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在实际使用时，可以完全使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moduleApi&lt;/code&gt; 替换掉原有的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api&lt;/code&gt;。当然， &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;implementation&lt;/code&gt;也可以有一个对应的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moduleImplementation&lt;/code&gt;，这样只需要注释或解注释&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setting.gradle&lt;/code&gt;文件内的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include&lt;/code&gt;语句就可以达到切换源码、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aar&lt;/code&gt;的目的了。&lt;/p&gt;

&lt;h3 id=&quot;八从其他路由迁移至-therouter&quot;&gt;八、从其他路由迁移至 TheRouter&lt;/h3&gt;

&lt;h4 id=&quot;81-迁移工具一键迁移&quot;&gt;8.1 迁移工具一键迁移&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;提供了图形化界面的迁移工具，可以一键从其他路由迁移到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt;，目前仅支持&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ARouter&lt;/code&gt;，其他路由框架迁移也在开发中：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Mac OS 迁移工具下载：&lt;a href=&quot;https://github.com/HuolalaTech/hll-wp-therouter-android/wiki/uploads/file/TheRouterTransfer-Mac.zip&quot;&gt;https://github.com/HuolalaTech/hll-wp-therouter-android/wiki/uploads/file/TheRouterTransfer-Mac.zip&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Windows 迁移工具下载：&lt;a href=&quot;https://github.com/HuolalaTech/hll-wp-therouter-android/wiki/uploads/file/TheRouterTransfer-Windows.zip&quot;&gt;https://github.com/HuolalaTech/hll-wp-therouter-android/wiki/uploads/file/TheRouterTransfer-Windows.zip&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果项目中使用了ARouter的IProvider.init()方法，可能需要手动处理初始化逻辑。&lt;br /&gt;
如下图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/therouter/5.png&quot; alt=&quot;TheRouter&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;82-与其他路由对比&quot;&gt;8.2 与其他路由对比&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;功能&lt;/th&gt;
      &lt;th&gt;TheRouter&lt;/th&gt;
      &lt;th&gt;ARouter&lt;/th&gt;
      &lt;th&gt;WMRouter&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Fragment路由&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;支持依赖注入&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;加载路由表&lt;/td&gt;
      &lt;td&gt;无运行时扫描&lt;br /&gt;无反射&lt;/td&gt;
      &lt;td&gt;运行时扫描dex&lt;br /&gt;反射实例类&lt;br /&gt;性能损耗大&lt;/td&gt;
      &lt;td&gt;运行时读文件&lt;br /&gt;反射实例类&lt;br /&gt;性能损耗中&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;注解正则表达式&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Activity指定拦截器&lt;/td&gt;
      &lt;td&gt;✔️（四大拦截器可根据业务定制）&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;导出路由文档&lt;/td&gt;
      &lt;td&gt;✔️（路由文档支持添加注释描述）&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;动态注册路由信息&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;APT支持增量编译&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✔️（开启文档生成则无法增量编译）&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;plugin支持增量编译&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;多 Path 对应同一页面（低成本实现双端path统一）&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;远端路由表下发&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;支持单模块独立初始化&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;支持使用路由打开第三方库页面&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;支持使用路由打开第三方库页面&lt;/td&gt;
      &lt;td&gt;✔️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
      &lt;td&gt;✖️&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;对热修复支持（例如tinker）&lt;/td&gt;
      &lt;td&gt;✔️(未改变的代码多次构建无变动)&lt;/td&gt;
      &lt;td&gt;✖️(多次构建apt产物会发生变化，生成无意义补丁)&lt;/td&gt;
      &lt;td&gt;✖️(多次构建apt产物会发生变化，生成无意义补丁)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;九总结&quot;&gt;九、总结&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TheRouter&lt;/code&gt; 并不仅仅是一个小巧灵活的路由库，而是一整套完整的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Android&lt;/code&gt; 模块化解决方案，能够解决几乎全部的模块化过程中会遇到的问题。&lt;br /&gt;
对于现有的路由框架，我们也在最大限度支持平滑迁移，目前已完成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ARouter&lt;/code&gt;的一键迁移工具，其他框架的迁移仍在开发中。你也可以在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Github&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;issue&lt;/code&gt;中提出需求，我们评估后会尽快支持，也欢迎任何人提供 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pull Requests&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;加入微信群沟通交流：&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://kymjs.com/therouter/wx/therouter_wx.jpg&quot; alt=&quot;TheRouter官方微信群&quot; width=&quot;40%&quot; /&gt;
&lt;/div&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 菲律宾这国家到底怎么回事]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/history/2022/05/11/01/</br>5月开头，这周其实有两件大事：一件是俄罗斯胜利日大阅兵，大家关心的是普京会说些什么。西方觉得普京会对乌克兰宣战。因为这关系到三件事：这仗会不会打出乌克兰国界外；这仗会打到什么程度；这仗会打多久。&lt;br&gt;但事实证明，它们的格局太低了。普京的演讲，表明了俄罗斯的态度：要终结西方自从大航海时代以来500年的海洋霸权，尤其是近200年来的盎格鲁撒克逊人的霸权。 之前俄罗斯方面就这么宣称过，但红场阅兵不一样：在俄罗斯，它的收视率达到50%以上，这等于普京向全国人民正式宣布了。    - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220511_02.png]]></image>
        <pubDate><![CDATA[2022-05-11]]></pubDate>
        <link><![CDATA[https://kymjs.com/history/2022/05/11/01/]]></link>
        <tags>
          
          <tag>菲律宾史</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>history</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/history/2022/05/11/01/</br>&lt;p&gt;另一件事意义也挺大的：菲律宾总统大选。&lt;br /&gt;
根据选举委员会提供给媒体的服务器的初步统计数据，截至5月10日凌晨2时47分，总统候选人小费迪南德·马科斯以 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;29446813&lt;/code&gt;票大幅领先，在投票中胜出。&lt;br /&gt;
不过菲律宾人不靠谱，公告没出咱都别信。它曾经有过1天就出选举初步结果的，也曾有过3周之后才出结果的，3周出的那次，就是现任总统杜特尔特当年参选时。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220511_01.jpeg&quot; alt=&quot;菲律宾大选&quot; /&gt;&lt;br /&gt;
投票前一天，马科斯阵营显示团结&lt;/p&gt;

&lt;h2 id=&quot;菲律宾到底是个什么国家&quot;&gt;菲律宾到底是个什么国家&lt;/h2&gt;

&lt;p&gt;这件事的意义也是挺大的。&lt;br /&gt;
甚至可以这么说，一点都不亚于普京宣称要挑战美国为主导的世界秩序。&lt;br /&gt;
为什么这么说呢？要从菲律宾这个国家说起。&lt;/p&gt;

&lt;p&gt;菲律宾本身不是个特别大的国家：国土面积不到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;30万&lt;/code&gt;平方公里，但它老百姓挺能生的，人口倒是有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.2亿&lt;/code&gt;了。经济也很一般般：2021年GDP为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3900多亿&lt;/code&gt;美元，人均&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3570美元&lt;/code&gt;。比前段时间很多自媒体猛吹的越南，人均其实还要稍强一些。但和中国，就不好比了。&lt;/p&gt;

&lt;p&gt;这个国家的军力，也不强，简单这么说：它的全部海军，战力可能不如一艘052D导弹驱逐舰；它的全部陆军，可能打不过我们一个师；空军也没啥好飞机。&lt;br /&gt;
那么为什么说它这次总统大选意义重大呢？&lt;br /&gt;
这和它的历史有关。&lt;/p&gt;

&lt;p&gt;菲律宾这个国家的过往和现在，简单来说，就是这三句话：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;两次殖民；&lt;/li&gt;
  &lt;li&gt;两个大岛；&lt;/li&gt;
  &lt;li&gt;一场动荡。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;两次殖民&quot;&gt;两次殖民&lt;/h2&gt;

&lt;p&gt;菲律宾这个国家，离我们虽然近，我们也一直知道它，却没对它做什么。这主要是与亚洲盛行季风有关。  &lt;br /&gt;
菲律宾的历史，主要和西班牙有关。&lt;/p&gt;

&lt;p&gt;1521年，麦哲伦搞人类首次环球航海时，发现了它，当时麦哲伦比较粗暴，想直接在当地搞征服，结果被打死了，是的麦哲伦环球旅行的终点就是菲律宾。麦哲伦死了以后，他的同伙逃跑了，一路航行，跑回欧洲老家，完成整个航行。&lt;br /&gt;
所以我们看不少地图，会发现标有麦哲伦环球航行线路的，分成了两段：一段是麦哲伦自己航行的；从菲律宾到欧洲那一段，是他的同伙航行的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220511_02.png&quot; alt=&quot;麦哲伦环球旅行&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这样，有菲律宾这么个地方，就被西班牙人知道了。&lt;br /&gt;
1565年，西班牙人吸取教训后，传播宗教加武力征服，软硬两手一起来，在几十年间，就基本搞定了吕宋岛和上千个大大小小的岛屿。&lt;br /&gt;
当时西班牙国王名字叫菲利普，“菲律宾”这国名就这么来的。&lt;br /&gt;
这是菲律宾第一次被殖民。&lt;/p&gt;

&lt;p&gt;然后到了1898年。美国那时候强大起来了，当时中国虽然屡遭侵略，但要论GDP，还是世界第一大国。 &lt;br /&gt;
美国人很想在附近找个地盘，和中国做贸易。&lt;br /&gt;
当时西班牙已经很弱了，所以美国人就黑吃黑，对西班牙人下手。&lt;br /&gt;
当时有艘美国军舰，名字叫“缅因号”，停在现在古巴首都哈瓦那的港口，当时古巴还是西班牙的殖民地，这艘军舰突然爆炸，然后沉了。&lt;/p&gt;

&lt;p&gt;它为啥爆炸，现在还说不清楚。&lt;br /&gt;
很多人觉得是美国人自己干的。&lt;br /&gt;
因为它后来有太多这样自导自演，然后栽赃给他国的例子。&lt;br /&gt;
但美国说：这是西班牙人干的。&lt;br /&gt;
然后两家开战。&lt;/p&gt;

&lt;p&gt;战争一开，基本就是一边倒，西班牙人根本没还手之力。&lt;br /&gt;
当时主要有两个战场，一个是古巴，当时全世界最大的蔗糖产地；一个是菲律宾。&lt;br /&gt;
两家打仗的时候，美国对菲律宾人许诺，让他们赶紧起义，赶走西班牙人，还说战后让菲律宾独立。等美国人打败古巴人，它就说话不算数了，说菲律宾还得乖乖做殖民地。
菲律宾人觉得受骗了，为了独立，就和美国人打仗。&lt;/p&gt;

&lt;p&gt;这场仗打了3年。&lt;br /&gt;
战争非常残酷：美国人差不多把所有城市夷为平地，还到处袭击村庄，见到村民就杀掉，大家结下了血仇。&lt;br /&gt;
美国人自己统计说，有25万到27万菲律宾老百姓被杀，2万菲律宾士兵被杀。当时菲律宾总人口也就400多万，差不多13个人中，就有1个被杀。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220511_03.png&quot; alt=&quot;麦哲伦环球旅行&quot; /&gt;&lt;/p&gt;

&lt;p&gt;所以，菲律宾就这么两次被殖民了。&lt;strong&gt;一次被西班牙，一次被美国&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;这两次殖民产生了什么后果呢？ &lt;br /&gt;
先说第一次。&lt;/p&gt;

&lt;p&gt;西班牙离菲律宾非常远，所以能到这地方的白人就少。但西班牙人有办法。
他们疯狂和当地部落首领通婚，建立亲属关系，在菲律宾社会中搞出一大群有西班牙血统的人；西班牙还大力推广天主教，信教和帮着他们传教的当地人，也获得了不低的社会地位。&lt;/p&gt;

&lt;p&gt;西班牙人再给这些人授土地和老百姓。顿时就在菲律宾制造出一个大地主阶层出来。&lt;br /&gt;
西班牙统治菲律宾300多年，这些当初跟着它混的人，就这么繁衍了十几代，这么一代代地繁衍下来，加上之间通婚，就形成一个个大家族。&lt;br /&gt;
这些家族的利益，是和西班牙捆绑在一起的。&lt;/p&gt;

&lt;p&gt;这也是现代职场空降以后最有效的管理手段，打压一批人，再拉拢一批人，让这批人跟自己有共同利益，同时分给他们一些甜头，接下来他们为了维护自己的甜头，自然就会拼死跟你站在同一阵营了。&lt;/p&gt;

&lt;p&gt;后来美国人来了。 &lt;br /&gt;
这些家族当然就拼命反抗，美国人非常不客气，到处杀人，把那些最激烈抵抗它的家族要么杀光，要么搞得跌落到社会最底层。&lt;br /&gt;
当然，不少西班牙人时期的老牌家族一看：美国人太猛了，和它对干没好果子吃！就和美国人合作了，这些家族就这么保留下来了。&lt;br /&gt;
但光靠这些倒戈的家族，美国人还不能统治菲律宾。&lt;br /&gt;
所以它也得扶植一批。&lt;/p&gt;

&lt;p&gt;正好，菲律宾还确实有这么一批人。&lt;br /&gt;
这事要从明朝说起，哥伦布不是发现了美洲大陆了吗？&lt;br /&gt;
西班牙就去殖民，搞到了很多白银，尤其是秘鲁，那白银产量非常不得了。西班牙人就从南美洲，穿过太平洋，用大帆船把白银拉到菲律宾的马尼拉。&lt;/p&gt;

&lt;p&gt;中国沿海的商人，看到银子，积极性非常高，纷纷驾船，装着各种货物，像丝绸、瓷器、茶叶什么的，赶到马尼拉，去和西班牙人交易。&lt;br /&gt;
那些货品，是当时的古驰、LV，非常抢手。这些船的目的地是马尼拉，所以有个专用名词，叫“马尼拉大帆船”。 &lt;br /&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220511_04.png&quot; alt=&quot;“马尼拉大帆船”航行路线&quot; /&gt;&lt;br /&gt;
“马尼拉大帆船”航行路线&lt;/p&gt;

&lt;p&gt;后来有个统计，说西班牙人从南美洲搞到的银子，大约二分之一到三分之二，就这么流入了中国。&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;我们在古装电视剧里，老是看到某某人进了酒店，扔出一块银子，对店小二喊道：打五斤酒，整一桌好菜，剩下的就不要找了。&lt;/p&gt;

  &lt;p&gt;其实这是在西班牙发现新大陆，搞来大批银子之后才有的事。之前中国没那么多银子。&lt;/p&gt;

  &lt;p&gt;大家哪怕上馆子吃饭或者做一些交易，一般用铜钱，或者用纸钞。阔气得到处扔银子，其实只是近四、五百年的事。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;但这么一贸易，事情就来了：慢慢地，菲律宾有了很多华人后裔。 &lt;br /&gt;
这是怎么一回事呢？怪老天爷！！！&lt;/p&gt;

&lt;p&gt;古代船要在大海航行，主要靠风，亚洲季风。&lt;br /&gt;
从我们这里坐船去菲律宾，方向是从西北往东南，得靠西北风。 &lt;br /&gt;
但老天爷和古人作对。广东、福建、台湾以北，二月吹的是西北风，但过了台湾后，季风突然转成往东南方向吹的，对中国古代的船来说，沿越南海岸线走，顺风顺水，往菲律宾方向走，既不顺风也不顺水。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220511_05.jpeg&quot; alt=&quot;东南亚季风方向&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这就是为啥菲律宾明明离我们不远，古代中国人不大去的原因。&lt;/p&gt;

&lt;h2 id=&quot;两个大岛&quot;&gt;两个大岛&lt;/h2&gt;

&lt;p&gt;但后来有银子赚，情况就不一样了嘛！&lt;br /&gt;
幸好每年冬季，总是有那么一段时间，哪怕过了台湾，风也是朝东南方向吹的。
于是人们就纷纷驾船出海。&lt;br /&gt;
赶到马尼拉后，东西虽然卖掉了，但风向不对。大家就只能在那里呆着，找个地方住下，每天喝酒吃饭，等到六七月，夏季季风来了，再乘着顺风船回国。&lt;/p&gt;

&lt;p&gt;古代航海的，都是男人。远离家乡那么长时间，既空虚又寂寞还无聊，加上身边没家眷，还赚了这么多钱，时间慢慢长了，菲律宾就冒出了一大堆华人后裔。&lt;br /&gt;
再后来呢，就有人不回去了，慢慢定居下来。&lt;/p&gt;

&lt;p&gt;1901年，美国人做了个统计，发现至少三分之一的菲律宾人有中国血统，比有西班牙人血统的要多很多。所以东南亚有些国家排华，但菲律宾从来就没排华这种事。&lt;br /&gt;
因为整个国家的老百姓，几乎都沾点华人血统，怎么排？&lt;/p&gt;

&lt;p&gt;中国人勤劳能干，善于经商，很多人就成了当地的大富翁，在西班牙人统治时，就有一些和那些老牌家族联姻，加入了大家族的行列。但毕竟不多。&lt;br /&gt;
美国人从西班牙手里抢到菲律宾后，就大力扶植当地的华人富豪，于是又形成了一波大家族。&lt;br /&gt;
那么，这些家族都在哪里呢？看到菲律宾的地图。  &lt;br /&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220511_06.jpeg&quot; alt=&quot;菲律宾地图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;菲律宾是个群岛国家，这个国家简单来说，就是南边有个大岛，北边也有个大岛，中间好像被谁狠狠地踹了一脚，全碎了，所以是一大堆零零星星的小岛。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;南边那个大岛，叫棉兰老岛，是菲律宾第一大岛。&lt;/li&gt;
  &lt;li&gt;北边那个大岛，叫吕宋岛，是第二大岛。&lt;/li&gt;
  &lt;li&gt;中间那一大堆被踹碎了的岛屿群，叫作米沙鄢群岛。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;虽然吕宋岛是第二大岛，但在很长一段时间，它是菲律宾整个国家的核心。&lt;br /&gt;
为什么呢？原因很简单：它离中国近。&lt;br /&gt;
我在上面说过，西班牙殖民菲律宾后，主要在做什么事？和我们做生意。 &lt;br /&gt;
美国人为什么要从西班牙人手里抢菲律宾？也是为和我们做生意，有个立足点。&lt;/p&gt;

&lt;p&gt;既然菲律宾这个国家之所以被殖民，之所以存在的目的，绝大部分时候，都是为了和我们做生意。俗话说：近水楼台先得月。有钱有势的大家族，都聚集在离我们最近的吕宋岛，也就顺理成章了。&lt;/p&gt;

&lt;p&gt;说到这里，有小伙伴应该能看出来了：美国这个国家，虽然天天民主、自由喊得震天响，其实和其他西方国家一样，也是很善于控制被它殖民的国家的。&lt;br /&gt;
既然菲律宾当过它的殖民地，当然会和它产生千丝万缕的联系，一般不可能轻易逃脱。
按照一般逻辑，本来应该是这样。 &lt;br /&gt;
但在美国统治菲律宾时，偏偏发生了一件事。&lt;/p&gt;

&lt;p&gt;1941年12月，日本偷袭珍珠港，太平洋战争爆发。&lt;br /&gt;
在日本偷袭珍珠港时，日军也大举南下，分兵多路，占领东南亚。&lt;br /&gt;
菲律宾是美国殖民地，日本人当然不会放过，其中一路就直指菲律宾。当时麦克阿瑟正在菲律宾担任美国远东军司令部司令，他抵抗了一段时间后，就丢下7.5万名部下，逃之夭夭。&lt;/p&gt;

&lt;p&gt;菲律宾这些家族，早就成墙头草了。&lt;br /&gt;
谁有奶谁就是娘，它们立即和日本人合作。&lt;br /&gt;
但有个小家族，坚决地不和日本人合作，还打起了游击战，在那种“疾风知劲草”的战争年代，着实让美国人刮目相看了一把。&lt;br /&gt;
这个家族，就是5月这次大选的主角：&lt;strong&gt;马科斯家族&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;马科斯家族的来源，非常神秘。&lt;br /&gt;
很早时，泰国曾经拍过一个片子，叫《晚娘》（这片子国内不能正常看，别想了，给你们一个&lt;a href=&quot;https://baike.baidu.com/item/%E6%99%9A%E5%A8%98/8271089&quot;&gt;链接&lt;/a&gt;）。  &lt;br /&gt;
大致意思是：有个权贵家族的女儿，因为被人掳走，被坏人强暴之后，怀孕了，为了遮羞，招了个普通人做丈夫。而她丈夫，因为贪图权贵家族的钱财，“喜当爹”，也就认了。&lt;/p&gt;

&lt;h2 id=&quot;马科斯家族&quot;&gt;马科斯家族&lt;/h2&gt;
&lt;p&gt;Marcos：马科斯家族(不是马尔科，也不是马克思Marx，我刚看的时候一直想着海贼的马尔科)，据说就源于这样的一次“喜当爹”。&lt;/p&gt;

&lt;p&gt;1917年，有个已经怀孕的女孩子，和一个男孩子结婚了。&lt;br /&gt;
菲律宾比较开放，这种情况比较正常。男方叫马里亚诺·马科斯，爷爷是个西班牙法官的私生子，但家庭没啥社会地位；女方叫何塞法，祖先是中国人和西班牙人混血，父母亲开杂货铺的。  &lt;br /&gt;
没多久，孩子生下了，名字叫&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;费迪南德·马科斯&lt;/code&gt;。但奇怪的事发生了：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;这孩子刚生下没多久，就被当地一个纯华人血统的富豪家族蔡家，认作教子。&lt;/li&gt;
  &lt;li&gt;自从这次结婚之后，马里亚诺·马科斯就开始从政，此后基本上是青云直上，当过北依罗戈省省长，还去参选过参议员。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在菲律宾，这两件事能干成，都是不简单。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;当地社会等级森严，普通人家的孩子根本没机会和豪门套上什么交情。&lt;/li&gt;
  &lt;li&gt;同样的原因，普通人想从政，根本难如上青天。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;后来大家才知道真相：原来孩子真正父亲，名字叫名叫&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;费迪南德·蔡&lt;/code&gt;，是这个蔡家的大少爷，蔡少爷和何塞法混了一场后，怀孕了。&lt;/p&gt;

&lt;p&gt;但当时纯华人血统的家族，都会要自家孩子去福建故乡娶老婆，否则就没法当地华人大家族圈里混，所以他父母坚决反对。 &lt;br /&gt;
所以只能找&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;马里亚诺·马科斯&lt;/code&gt;。
正好，马里亚诺·马科斯是个要求上进的好青年，就提出了自己的条件，蔡家也同意帮忙。各取所需。这张图就是这个私生子&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;费迪南德·马科斯&lt;/code&gt; ，是不是长得一点都不像菲律宾人，反倒像中国人？ &lt;br /&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220511_07.png&quot; alt=&quot;费迪南德·马科斯&quot; /&gt;&lt;/p&gt;

&lt;p&gt;对这孩子，喜当爹的老马科斯显然一点都不喜欢，也不怎么照看他，就任他自生自灭。&lt;br /&gt;
所以费迪南德·马科斯从小就在他外公的杂货铺里，和当地小孩厮混。&lt;br /&gt;
但这个假爸爸，费迪南德·马科斯却很有感情。&lt;br /&gt;
马里亚诺有次竞选众议员败了，他很生气。&lt;br /&gt;
他的对手叫纳沦达桑，马里亚诺就说他贿选；纳沦达桑也不客气，直接叫人抬了口棺材上街，上面写一行大字：这是马里亚诺的棺材。&lt;/p&gt;

&lt;p&gt;3天之后，纳沦达桑吃完晚饭，在洗脸池漱口刷牙时，突然“砰”的一声枪响，纳沦达桑中枪身亡。3年以后，警察逮捕了小马科斯，承认就是他杀了纳沦达桑。&lt;br /&gt;
之后神奇的事发生了：对&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;费迪南德·马科斯&lt;/code&gt;为父报仇这件事，菲律宾各大媒体纷纷报道，对他大加赞扬，说他是个了不起的孩子。&lt;/p&gt;

&lt;p&gt;还有更神奇的事：&lt;br /&gt;
案子还没判，最高法院就准许他交保出狱；&lt;br /&gt;
他自己都没申请，菲律宾总统就宣布赦免他；&lt;br /&gt;
更牛逼的是，他在监狱里通过律师资格考试，都成了大新闻。&lt;br /&gt;
背后的原因，大家都能猜到的：蔡家在后面使了很大的劲。&lt;/p&gt;

&lt;p&gt;这件事还带来另一个意想不到的结果：马里亚诺对这儿子也产生了感情。&lt;br /&gt;
虽然不是亲生的，却干了亲生儿子不见得能干到的事。&lt;br /&gt;
其实马里亚诺自己也挺努力的，在政坛也闯荡出了一点名头出来，所以等这件事平息后，他的政治衣钵就传给了马科斯。&lt;br /&gt;
但才到第二代，离成为一个家族，还远远不够。 &lt;br /&gt;
关键时刻，日本人来了。不得不说，亚洲近现代史里面，每次大变动都跟小日子过的不错的日本人有关。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220511_08.png&quot; alt=&quot;日本轰炸珍珠港&quot; /&gt; &lt;br /&gt;
日本轰炸珍珠港&lt;/p&gt;

&lt;p&gt;马科斯对美国人挺忠诚的，当时他参加了美军，结果美军战败，他被俘虏。但他虽说名义上的爸爸是参议员什么的，却不像那些大家族的公子哥们，因为马里亚诺根本就不管他，所以从来没娇生惯养过，因此眼快脚快，乘着日本人一个不注意，就逃走了。&lt;br /&gt;
逃走之后，他也不闲着，找人拉了一支队伍，和日本人打起了游击战。&lt;br /&gt;
这就不得了。&lt;br /&gt;
对美国人来说，能拉队伍，能和日本人打游击战，这就是块试金石啊。&lt;/p&gt;

&lt;p&gt;后来美军打回菲律宾。&lt;br /&gt;
对在关键时刻，菲律宾能出这么一号人物，麦克阿瑟极其地欣赏，1945年任命马科斯为吕宋岛北部八省的行政官。&lt;br /&gt;
马科斯一下子变得有钱有粮有地盘。&lt;br /&gt;
枪杆子里出政权，这是颠扑不破的真理。&lt;br /&gt;
后来菲律宾独立，但经历过这么一次成功地站队后，马科斯虽说是第二代，但作为一个家族，他是完全够格了，所以之后一帆风顺：1959 年任参议员，1962年任参议院议长，1965年当选为总统。&lt;br /&gt;
马科斯家族就此崛起。&lt;/p&gt;

&lt;p&gt;但美国人不乐意了。&lt;br /&gt;
我之前说过：美国人向来讨厌这种自己手头有枪杆子，又有地盘的人。&lt;br /&gt;
美国人喜欢搞民主，把一个国家分成好几派，让大家斗得你死我活，谁想占优势，就得跑去美国人那里磕头，是多么爽的一件事？&lt;br /&gt;
自己手头有枪杆子，又有地盘的人，基本上会自家一个人说了算。&lt;br /&gt;
美国当时在搞冷战啊，这种能自己说了算的国家，美国得花钱去拉拢啊，要不然就会被苏联或者什么别的国家拉去了。&lt;br /&gt;
自家养的鹰，翅膀硬了，也会飞的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220511_09.jpeg&quot; alt=&quot;菲律宾访华&quot; /&gt; &lt;br /&gt;
马科斯夫人伊梅尔达带着儿子小马科斯（就是身穿白衣那位少年，这次菲律宾总统大选胜选者）访华&lt;/p&gt;

&lt;p&gt;从表面上看，马科斯每次去美国，总能搞到大笔大笔的钱。&lt;br /&gt;
当众演讲时，马科斯的夫人伊梅尔达也公开说：菲律宾是美国的小儿子。&lt;br /&gt;
但美国人心里还是极其不爽。和美国人一样不爽的，还有吕宋岛的大家族们。&lt;br /&gt;
家族政治嘛，就要讲平衡，但马科斯家族实在太强大了。&lt;br /&gt;
所以，1955年，菲律宾举行一场世纪婚礼：阿基诺家族的公子和许寰哥家族的大小姐结婚。&lt;br /&gt;
菲律宾最顶级的三个家族就此结盟：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;许寰哥&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;阿基诺&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;苏木隆&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&quot;三大家族&quot;&gt;三大家族&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;阿基诺家族&lt;/strong&gt;，本来是当地部落首领，后来投靠西班牙人，得到了打拉省的大片土地，这个省几乎全是平原，是菲律宾最重要的农业省。&lt;br /&gt;
美国人到时，阿基诺家族又跟了美国人，再上一层楼。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;许寰哥家族&lt;/strong&gt;，最早的祖先名叫许尚志，福建人，年轻时因生活所迫到菲律宾闯天下，到菲律宾后，他改名为许玉寰。 &lt;br /&gt;
因为他人勤劳，由精明能干，善于经营，在打拉省买下大批田产，成了这个省的首富。大家都叫他许寰哥，他的后人登记时，就把“许寰哥”当姓。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;苏木隆家族&lt;/strong&gt;和阿基诺家族经历类似：也是西班牙人时期就有的，美国人来时又投靠了美国人，从而跃升为顶级家族。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这三个家族，出过好几位菲律宾参议院议长，至于议员，那更是无数。&lt;br /&gt;
许寰哥家族的这位大小姐，她的舅家是苏木隆家族。&lt;br /&gt;
很多人都说：阿基诺家这位大公子，将来迟早是菲律宾总统。&lt;br /&gt;
但历史和他开了个很大的玩笑，他没当成。因为有马科斯在。&lt;/p&gt;

&lt;p&gt;1972年，马科斯宣布戒严，阿基诺二世和众多老政治家族的人、新闻记者被关进牢里。5年之后，检察官说阿基诺家的大公子从事颠覆活动，法院判他死刑。&lt;br /&gt;
世界各国的家族政治，向来有条不成文的规矩：搞政治斗争，可以把政敌关进牢里，可以逼人流亡国外，但做事要留一线，不能动手杀人。一动手杀人，那就麻烦了，因为这意味着各大家族从此之后只能以命相搏了。&lt;/p&gt;

&lt;p&gt;马科斯竟然想杀人，此例一开，今后他岂不是想杀谁就杀谁了？&lt;br /&gt;
各个家族纷纷组织力量，上街游行。&lt;br /&gt;
连支持马科斯的那些家族，都劝他说千万不能杀人。&lt;br /&gt;
美国人呢？他们早就看马科斯不顺眼了，早就说他是“独裁者”了，也疯狂施压力。马科斯就没敢杀人。&lt;/p&gt;

&lt;p&gt;1980年5月，在美国压力下，阿基诺二世被假释出狱，去美国就医。之后，马科斯在撑了3年后，实在受不了美国和国内的压力，被迫同意阿基诺二世回国。&lt;br /&gt;
1983年8月，阿基诺二世坐飞机回国，但刚走出机舱时，突然头部中了一枪，当场身亡。&lt;br /&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220511_10.jpeg&quot; alt=&quot;阿基诺二世坐飞机回国&quot; /&gt;&lt;/p&gt;

&lt;p&gt;菲律宾顿时掀起轩然大波。马科斯说：他绝对没想杀阿基诺二世。 &lt;br /&gt;
后来有消息说：可能是美国干的，为的是嫁祸马科斯。真相至今没人知道。&lt;br /&gt;
接下来的路数，有点像颜色革命。&lt;br /&gt;
先是全国游行，搞了两年。菲律宾人嘛，干事情从来都是这么慢吞吞的。 &lt;br /&gt;
之后，马科斯同意举行大选，阿基诺夫人被推作他的对手。1986年2月，议会宣布马科斯胜选，但阿基诺夫人说马科斯作弊，实际上是她胜选了，并宣布接管国家权力。&lt;/p&gt;

&lt;p&gt;菲律宾国内差不多所有的大家族都站了出来，宣布不承认马科斯胜选；美国和西方国家，也说不承认。然后马科斯带着妻子、儿子，被迫流亡海外。他死在了海外。&lt;/p&gt;

&lt;p&gt;1955年那场婚礼，确实产生一位菲律宾未来的总统。但命运竟然如此弄人，大家都以为是丈夫，结果竟然是妻子！&lt;br /&gt;
阿基诺夫人成为总统，背后有很深的美国背景。&lt;br /&gt;
顺便说一下，她的儿子阿基诺三世后来也当过菲律宾总统，在任内搞出了非常严重的“南海仲裁案”。&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;菲律宾以中国在南海（菲律宾称西菲律宾海）中菲争议“九段线”的海洋权益主张及近年的海洋执法和岛礁开发违反《联合国海洋法公约》为由，向裁法院提出对中国的仲裁案。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在当上总统之前，阿基诺三世还曾好几次到中国寻根问祖过。可见，有华人血统的外国元首，不见得一定对华友好。&lt;/p&gt;

&lt;h2 id=&quot;一场动荡&quot;&gt;一场动荡&lt;/h2&gt;

&lt;p&gt;1986年那次事件，在菲律宾历史上，是非常严重的一场动荡。&lt;br /&gt;
作为政治核心的吕宋岛，居然发生了大家族之间的内讧！&lt;br /&gt;
于是，按照惯例，他们就得找外援。菲律宾的第一大岛棉兰老岛的政治势力，机会就来了。&lt;br /&gt;
一个新家族出头了。杜特尔特家族。&lt;br /&gt;
杜特尔特来自一个小家族，他的父亲曾任过南达沃省省长，不过时间不长，他妈妈是是个教师。但杜特尔特有个优势，他外公是华人，姓吕，所以他也有华人血统，长这个样子：  &lt;br /&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220511_11.png&quot; alt=&quot;杜特尔特&quot; /&gt;&lt;/p&gt;

&lt;p&gt;棉兰老岛毕竟在最南边，离北面太远了，有华人血统的人要少一点。&lt;br /&gt;
在吕宋岛大家族们内讧时，杜特尔特正好任达沃市的检察官。&lt;br /&gt;
杜特尔特是个好勇斗狠的人，他说自己16岁就用刀捅死一个人，“仅仅是因为我们互相看了对方一眼”，还频繁入狱。“这里打一架，那里打一架”。&lt;/p&gt;

&lt;p&gt;当时的达沃市算边陲地带，市长是马科斯的人。&lt;br /&gt;
为了稳住当地，阿基诺夫人任命杜特尔特当达沃市的行政长官。马科斯任命的这个市长平时就知道杜特尔特心狠手辣，听说阿基诺夫人已经任命了，吓得连夜逃走，把达沃市拱手相让。&lt;br /&gt;
从此之后，达沃市就成了杜特尔特家的地盘。至今已经36年。一个新家族从此崛起。&lt;/p&gt;

&lt;p&gt;发生在1986年，吕宋岛这次家族之间的大内讧，导致了一个很严重的后果：&lt;strong&gt;从此之后，菲律宾的政坛就陷入了混乱&lt;/strong&gt;。&lt;br /&gt;
可能是政治平衡被打破的原因，从此之后，吕宋岛的大家族再也推不出什么像样的人选来。&lt;/p&gt;

&lt;p&gt;既然吕宋岛无英雄，那么就只能别的地方人来执政。&lt;br /&gt;
杜特尔特有点像董卓，他从边陲之地崛起，成功当选为菲律宾总统。&lt;br /&gt;
和之前所有的菲律宾总统不同，他没有美国背景。&lt;br /&gt;
所以这些年，我们看到：很多场合，他和美国对着干，对中国的态度呢，也相对友好。&lt;/p&gt;

&lt;p&gt;所以美国人恨透了他。&lt;br /&gt;
就想着在今年这次选举，把局面再扳回来。&lt;br /&gt;
所以这次选举，其实是一次决定菲律宾命运的大博弈：主要是菲律宾本土力量和美国之间的博弈。&lt;br /&gt;
所谓的菲律宾本土力量，主要指的是马科斯家族和杜特尔特家族之间的联盟。&lt;br /&gt;
小马科斯是总统候选人。杜特尔特的女儿莎拉是副总统候选人。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220511_12.jpeg&quot; alt=&quot;杜特尔特和女儿莎拉&quot; /&gt; &lt;br /&gt;
杜特尔特和他女儿莎拉&lt;/p&gt;

&lt;p&gt;这两个家族，都和美国关系不好。小马科斯的爸爸马科斯，和美国有宿怨，整个家族还因此遭到了清算。&lt;br /&gt;
马科斯家族逃亡出去几年后，风声慢慢松了，小马科斯又回到家乡，然后蛰伏了将近30年，最近这几年，因为很多事情已经过去，他才再次出现在政坛。&lt;br /&gt;
至于杜特尔特，因为他强力扫毒，又不像美国靠拢，早就惹怒了美国。&lt;br /&gt;
美国人早就扬言，等他总统任期结束后，要把他送进国际刑事法院。&lt;/p&gt;

&lt;p&gt;这两个家族在菲律宾，有着不同寻常的影响力：在吕宋岛北部，马科斯家族有着无可匹敌的影响力，至于杜特尔特家族，在明达瑙岛尤其是达沃大区的号召力，则是独占鳌头的。&lt;br /&gt;
可以说是南北呼应。而且它们和美国的关系，至少目前看来，都很不好。&lt;br /&gt;
这对组合的对手，则是明白无误的美国代理人：现任菲律宾副总统罗布雷多。&lt;/p&gt;

&lt;p&gt;对罗布雷多，美国给了最全力的支持。简直是不遗余力。首先，她选举的资金从哪里来？我们可以看到一个大家都很熟悉的名字：美国民主基金会。&lt;/p&gt;

&lt;p&gt;菲律宾媒体说：这是她最大的金主。还有大批的菲律宾媒体，在美国民主基金会的操纵下，成了她的吹鼓手：拉普勒、媒体自由与责任中心、菲律宾调查性新闻中心。美国民主基金会还收买了菲律宾年轻政治精英。花了很多钱。各种渠道，光现在透露出来的，就有450万美元。&lt;/p&gt;

&lt;p&gt;其中最大一个渠道就是“青年领袖善治奖学金”。名义上打着是为纪念罗布雷多老公林炳智而设立的。你没看错，这是个华人名字，罗布雷多的老公是华人。&lt;br /&gt;
在菲律宾，有华人血统的人太多了，这不意味着什么，因为有华人名字的，不一定亲华；而没有华人名字的，很可能也是华人，说不定对华更友好。&lt;/p&gt;

&lt;p&gt;我们都知道，美国民主基金会背后，就是索罗斯。索罗斯背后，则是更黑暗深沉的力量。 &lt;br /&gt;
美国势力介入的，还不止索罗斯一个。还有一个美国富豪，名字叫皮埃尔·奥米迪亚。这人非常神秘，谁也说不清楚他到底是个什么人，甚至世界上存在不存在这么一个人，都很难说，或者干脆他就是某个团体的神秘符号。但在这次菲律宾总统大选中，就至少出现了这么一个名字。&lt;/p&gt;

&lt;p&gt;菲律宾的媒体，像CNN菲律宾、菲律宾星报、菲律宾每日询问者报，都在皮埃尔·奥米迪亚指挥下，每天都在攻击小马科斯。&lt;br /&gt;
在美国的指挥下，当年死硬反对马科斯家族的力量也都集结了。美国人不喜欢小马科斯上台，吕宋岛的大家族们也不喜欢。照道理说，罗布雷多应该要赢的。但结果是：她没赢。&lt;/p&gt;

&lt;p&gt;这是为什么呢？因为三点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;ol&gt;
      &lt;li&gt;对美国人支持的候选人，菲律宾人已经产生戒备心理了。&lt;br /&gt;
马科斯在位时，菲律宾人骂他独裁专权，贪污腐败。结果后来换上来的政权，竟然一个都不如他在位时，腐败依旧，社会停滞，治安还更差。&lt;br /&gt;
菲律宾人已经受够了，尤其是前几年和美国没关系的杜特尔特在台上，菲律宾不管是经济发展，还是社会治安，比之前好多了。&lt;br /&gt;
所以这次小马科斯宣布参选，菲律宾人因为知道他父亲当年倒台是美国人逼的，虽说他父亲也不那么干净，但至少菲律宾在他治下，社会治安还是好的，生活还是安定的，怀念当年的菲律宾人很多，因此他的支持率一直一马当先，保持在50%以上。&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;ol&gt;
      &lt;li&gt;杜特尔特家族这次很有策略。&lt;br /&gt;
之前讲过，其实在总统大选刚开始时，因为杜特尔特不能连任总统，所以他的女儿莎拉的支持率一直是比小马科斯高的。&lt;br /&gt;
但杜特尔特显然明白，如果他当过总统之后，再让女儿当总统，必然会引起吕宋岛大家族和美国人内外夹击，所以虽然民望很高，但他女儿作为副总统候选人，和遭受过大难的马科斯家族，可以有效抗击这双重压力。&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;ol&gt;
      &lt;li&gt;美国看走眼，选错了人。&lt;br /&gt;
罗布雷多太过自信，以为有了美国支持，一定能当选。今年4月，美国曾经插手进去，让很多候选人退选，并要求他们退选时宣布支持罗布雷多。&lt;br /&gt;
本来罗布雷多应该很客气地跟他们说，并且和他们谈一下价钱。但她什么也没干，而是用命令式的方式，要他们如何如何，结果这帮人虽然确实退选了，却都破口大骂罗布雷多，白白替小马科斯增加了选票。&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220511_13.jpeg&quot; alt=&quot;五位候选人宣布退选&quot; /&gt; &lt;br /&gt;
&lt;strong&gt;今年4月，菲律宾选举史上发生了惊人的一幕：五位候选人宣布退选，但没有按照一般国家的选举规则，宣布支持第二名，而是痛骂排名第二的罗布雷多&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;这就是美国人看人不准，用人不当了。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;第一，如果说上次杜特尔特胜选，还有美国没怎么用心的原因。这次的菲律宾大选，美国人可以说是其实使尽浑身解数，但还是没能成功。在美国前殖民地中，这种情况很可能是第一次发生。&lt;br /&gt;
这说明，美国对前殖民地的控制，也开始松动了。有了第一个，就有第二个、第三个。
这个世界真的在改变。&lt;/li&gt;
  &lt;li&gt;第二，对中国来说，小马科斯胜选是件好事。他的对华政策，一般来说，可能甚至会比杜特尔特更友好。&lt;br /&gt;
比如说，他在竞选时，就说过：南海仲裁案根本就是张废纸，因为仲裁要双方参与啊，一方搞的，怎么能说是仲裁呢？&lt;br /&gt;
南海今后会更平静了。&lt;/li&gt;
  &lt;li&gt;第三，小马科斯和美国的关系，不会那么好。&lt;br /&gt;
因为按照美国法院的判决，小马科斯和他妈妈伊梅尔达等，至今仍然是被执行人。就算他当了总统，如果没能和美国政府达成谅解，不光是他，甚至他的整个家族成员踏上美国土地时，都可能被关进监狱。 &lt;br /&gt;
美国期待菲律宾政权发生变动，然后它可以在南海搞事，已经等了很长时间了。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当年被美国打翻的家族，再次复兴了。  &lt;br /&gt;
当年那个青涩的少年，蛰伏30多年后，东山再起了。  &lt;br /&gt;
这么一个人胜选，对美国来说，真不是个好消息，今后在南海更会没筹码。&lt;/p&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 印度的穆斯林王朝——德里苏丹]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/history/2022/05/08/01/</br>蒙古成吉思汗这个大BUG，说大半个世界被他一锅端都不过分，当时突厥的古尔王朝还有一点残余势力，他们以印度河恒河流域的枢纽，从德里这个城邦为中心建立了一个伊斯兰化的邦国叫德里苏丹国。&lt;br&gt;&lt;br&gt;但问题是这个国家它其实不算是个国家，因为他本身的就是四十几个阿富汗突厥贵族为了利益组成的一种诸侯联邦状态。这个状态就有点像我们的战国时期，各个诸侯表面上都说自己是周天子的子民，但实际上都自立为王。当时的德里苏丹国也是这样，内部矛盾很多，要不是印度土著擅长躺平，估计早就像陈胜吴广一样揭竿而起，干翻这个王国了。 - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220508_01.jpg]]></image>
        <pubDate><![CDATA[2022-05-08]]></pubDate>
        <link><![CDATA[https://kymjs.com/history/2022/05/08/01/]]></link>
        <tags>
          
          <tag>印度史</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>history</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/history/2022/05/08/01/</br>&lt;p&gt;前段时间在研究阿富汗的时候，特意了解了一下中东地区几个国家的历史，看着看着反倒觉得印度是个神奇的存在，因为整个印度的历史基本上就是印度的屈辱史，被各种民族各种国家入侵，然后关键他到现在还能活着。所以特意写一系列文章讲一讲。&lt;/p&gt;

&lt;h3 id=&quot;库特布沙希王朝翻身农奴做国王&quot;&gt;库特布沙希王朝——翻身农奴做国王&lt;/h3&gt;

&lt;p&gt;1206年，古尔王朝的苏丹吉亚斯·乌德丁·穆罕默德遇刺身亡，统治印度地区的总督库特布丁·艾伊拜克(也有翻译为艾伊伯克)趁机独立1206年，古尔王朝的印度总督艾伊拜克自立为苏丹，建立德里苏丹国。&lt;/p&gt;

&lt;p&gt;这是印度历史上第一个信奉伊斯兰教的王朝。就在艾伊拜克登上帝位的庆典前不久，穆罕默德的侄子给他送来了释放证书和一副华盖。原来艾伊拜克竟然是个古尔王族的奴隶。他是如何从这般卑贱的地位一步步成为一个王国的缔造者的呢？让我们把目光投向这之前的中亚。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220508_01.jpg&quot; alt=&quot;古尔王朝地理位置&quot; /&gt;&lt;br /&gt;
当时古尔王朝地理位置：位于阿富汗一带，南接印度。&lt;/p&gt;

&lt;p&gt;在突厥人的一个奴隶市场，小小年纪的艾伊拜克被卖身成为奴隶。奴隶贩子将他卖给了尼夏普尔的首席法官卡济，卡济让艾伊拜克跟自己的儿子一起接受宗教教育，进行军事训练。艾伊拜克就是从这里得到了一些行军打仗的知识。在中亚地区，奴隶多从事生产、打仗，主人也乐得他们带来财富和军功，最后奴隶靠这些自己赎身。所以突厥人吹牛聊天的时候就喜欢说：想出人头地，就去做奴隶。艾伊拜克还真是靠做奴隶出人头地的。卡济去世后，他又被卖给奴隶贩子，这一次艾伊拜克的买主来头更大了，他就是古尔王朝的苏丹吉亚斯·乌德丁·穆罕默德。凭借出色的表现和忠心耿耿，艾伊拜克荣升“弼马温”。齐天大圣觉得这个职位小，大闹天宫，而艾伊拜克给苏丹穆罕默德管理马厩，那前景是非常光明的，突厥人作战主要依赖骑兵，此时为了获得南部的土地，苏丹远征印度。&lt;/p&gt;

&lt;p&gt;随着艾伊拜克的新征程，我们也可以把目光转向印度了。艾伊拜克追随左右，军事才能发挥作用，立下了赫赫战功。于是，苏丹派艾伊拜克前去掌管印度地区的事务，并继续开疆拓土，扩大征服区。艾伊拜克果然不负所望，先后为苏丹征服了德里、曲女城、孟加拉等重要地区，北印度大部分地区都在他的控制之下。艾伊拜克艾伊拜克还十分懂得笼络当地王公，通过与当地首领的联姻，巩固自己在印度的统治。他本人就娶了当地首领塔杰·伊勒迪兹的女儿，又将自己的妹妹和女儿也都嫁给势力较小的首领，结成势力集团。当艾伊拜克在印度的势力不断膨胀的同时，古尔王朝却在外敌入侵的打击下逐渐衰落。艾伊拜克开始为自己的未来做打算，产生了自立之心。1206年，苏丹穆罕默德遇刺身亡，他的哥哥夺取王位。许多被征服的地区趁机开始反抗，国家陷入四分五裂的境地。见此情形，艾伊拜克当机立断，自立为苏丹。由于艾伊拜克信奉伊斯兰教，按照当时中亚国家穆斯林对国王的叫法，被称为“苏丹”。新建立的国家又是以德里为首都，因此，这个国家就被称为“德里苏丹国”。德里苏丹国是印度历史上第一个信仰伊斯兰教的王朝。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220508_01.jpg&quot; alt=&quot;德里苏丹国地理位置&quot; /&gt;&lt;br /&gt;
当年艾伊拜克的奴隶王朝大概区域&lt;/p&gt;

&lt;p&gt;由于艾伊拜克已经提前做好准备，巩固势力，因此在宣布独立后，立刻得到了印度的其他穆斯林官员和地方首领的承认。基尔曼的总督伊勒迪兹，也在加兹尼自立为王。伊勒迪兹见艾伊拜克得到了印度的统治权，于是心生嫉妒，就想要夺取富庶的旁遮普地区。艾伊拜克也不甘示弱，他起兵迎战，击败伊勒迪兹，占领了加兹尼。随后，艾伊拜克将这个所谓的“国王”赶了出去。可惜，艾伊拜克在加兹尼并没有赢得民心，他的暴虐引起了人民的反抗，人们暗中帮助伊勒迪兹重返加兹尼，赶走了艾伊拜克。于是，艾伊拜克失去了对阿富汗地区的影响力，统治范围仅限于印度。艾伊拜克是一个虔诚的伊斯兰教徒，他大力推动伊斯兰教的传播，并修建了许多清真寺和学校。而印度本土宗教损失惨重，佛教更是在印度彻底灭亡，艾伊拜克大量捣毁佛寺，玄奘的母校那烂陀寺就被夷为平地。由于艾伊拜克曾经是奴隶，因此他开创的德里苏丹国的第一个王朝就被称为“奴隶王朝”。&lt;/p&gt;

&lt;p&gt;此后，德里苏丹国还经历了四个王朝的统治，分别是卡尔吉王朝、图格鲁克王朝、赛义德王朝、洛迪王朝，直到1526年灭亡。后来德里苏丹国逐渐扩张后果：莫卧儿王朝建立1526年 ，帖木儿的后裔巴布尔征服印度，建立起莫卧儿王朝，德里苏丹国最终灭亡。&lt;/p&gt;

&lt;h3 id=&quot;卡尔吉王朝&quot;&gt;卡尔吉王朝&lt;/h3&gt;

&lt;p&gt;卡尔吉王朝（1290年－1320年）是德里苏丹国第二个王朝。这个朝代王族是阿富汗突厥的一支。人种上与文化上已经是伊朗化的。&lt;/p&gt;

&lt;p&gt;卡尔吉王朝的创建者为菲鲁兹·卡尔吉。菲鲁兹本为库特布沙希王朝的一名将领，并在1290年取代了当时的库特布沙希苏丹。菲鲁兹死后，他儿子阿拉乌丁·卡尔吉大力继位，加强中央集权，整顿了财政和行政，并向南面的德干苏丹国发动进攻，并派遣马利克·卡富尔四次南征德干高原，大大扩张扩张了王国的领土。但这王朝捣毁了孟加拉国超戒寺与那烂陀寺。   &lt;br /&gt;
1320年，卡尔吉王朝的最后一任苏丹被刺杀，王朝也就灭亡了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220508_03.png&quot; alt=&quot;卡尔吉王朝地理位置&quot; /&gt;  &lt;br /&gt;
德里苏丹国的扩张在卡尔吉王朝达到鼎盛，剩下的王朝基本可以忽略了，反正就是开始走下坡路的那种，有点像南北朝时期南朝这边的状态，宋齐梁陈一个比一个废。   &lt;br /&gt;
在1300年的卡尔吉王朝时人口达到 3500 万，到 1380 年的图格鲁克王朝时人口达到5500万。但是到了1450年的赛义德王朝人口只有3200万。&lt;/p&gt;

&lt;h3 id=&quot;德里之战&quot;&gt;德里之战&lt;/h3&gt;

&lt;p&gt;1398年，帖木儿率军入侵印度的德里苏丹国，双方在在首都德里附近爆发的决战。图格鲁克王朝一败涂地，德里德里苏丹国各地总督纷纷独立，为后来亡国埋下伏笔。帖木儿等待这一战已经很久了。&lt;br /&gt;
自十年前苏丹菲鲁兹·图格鲁克去世，德里苏丹国就陷入四分五裂的境地，国力渐渐衰落。而帖木儿帝国却如日中天，称霸于整个中亚地区。作为帖木儿帝国的缔造者，帖木儿继承了祖先蒙古人的优良传统，能征善战。经过短短十几年的战争，就征服了整个中亚和两河流域地区。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220508_04.jpg&quot; alt=&quot;帖木儿帝国大致版图&quot; /&gt;   &lt;br /&gt;
帖木儿帝国大致版图&lt;/p&gt;

&lt;h4 id=&quot;苏丹国的丧钟&quot;&gt;苏丹国的丧钟&lt;/h4&gt;

&lt;p&gt;1398年，帖木儿决定率军南下，征服印度。初秋，帖木儿的军队在撒马尔罕集结起来，10万大军向南进发。一路上，军队对行军途中的部族和居民进行屠杀，扫荡他们的物资用作军队补给。9月份，军队已经开进开伯尔山口，沿着曾经波斯人、希腊人和阿拉伯人走过的路线进军。此时，统治德里苏丹国的是倒霉的纳西尔丁·马赫穆德·沙。苏丹虽然已经得知帖木儿率军入侵的消息，但他早已被地方上的总督和封建主们架空了权力，根本无力调动各地总督来阻挡入侵。既然苏丹都无所作为，各地的总督更是先求自保，哪里顾得上老百姓的安危。   &lt;br /&gt;
于是，帖木儿的军队一路上几乎没有遭到任何抵抗，顺利的朝着印度的心脏德里开进。面对日渐逼近的侵略者，苏丹勉强拼凑起一支数量相当的军队，准备背水一战。只是，苏丹的军队都是临时招募的，人员混杂，作战经验参差不齐，无法与久经沙场、训练有素的帖木儿大军相提并论。但是没有办法，战争不会等你做好准备了才爆发。帖木儿是成吉思汗后裔。&lt;/p&gt;

&lt;h4 id=&quot;开战&quot;&gt;开战&lt;/h4&gt;

&lt;p&gt;12月17日，双方在德里附近展开了决战。虽然军队战斗力不高，但苏丹自持拥有一个秘密武器，梦想以此实现反击。这个秘密武器就是印度的战象。印度人专门为战象准备了坚固的护甲，将他们安排在战线的最前方。苏丹的计划是，让战象首先进攻，将敌人的阵线冲出一个缺口，然后军队在发起进攻，将敌人一举击败。起初，这些庞然大物确实让帖木儿的士兵大卫惊惧。他们大都来自中亚，大部分人从来都没有见过大象，一时不知如何是好。帖木儿迅速想出了应对策略。他先是命令士兵挖掘了一道壕沟，组建起一道临时工事，让步兵躲在后面见机行事。然后，他又将运输补给用的骆驼全部拉出来，在它们身上布置了秘密武器。战斗开始后，大批战象横冲直撞而来。帖木儿不慌不忙，令躲在壕沟里的步兵先发射带有火药的箭头，并放炮惊吓大象。随后，他又将许多身上驮着干草的骆驼放出来，在它们身上点者熊熊烈火。大象怕火，面对此情此景都被吓得不行，竟调头向印度人冲去。印度军队的阵线就这样被自家的大象活活冲散了。帖木儿看到时机已到，立刻下令让军队发起总攻。中亚铁骑冲向混乱的印度军队，如入无人之境，肆意杀戮。苏丹看到战况不妙，只好撤退到要塞里躲了起来。帖木儿的军队又对要塞围攻不断，纳西尔丁只好抛下自己的国家，暗中出逃。帖木儿攻占德里后，军队照例在城中烧杀抢掠。反抗的市民遭到了惨烈的屠杀，大量建筑被焚毁殆尽，整个城市都失去了生机。帖木儿帝国骑兵次年，整个德里已经被帖木儿洗劫一空，城市的防卫体系也被完全破坏，帖木儿心满意足地回国了，留下一片狼藉的印度。苏丹已经完全丧失了自己的军队，无力在对国家进行统治。德里苏丹国的分裂更加严重，国势日渐衰微。后果：赛义德王朝1414年，黑兹尔汗占领德里，建立赛义德王朝。&lt;/p&gt;

&lt;p&gt;德里之战是帖木儿帝国入侵印度德里苏丹国的一次战役。苏丹纳西尔丁的失败，导致图格鲁克王朝覆灭，也使德里苏丹国深受打击，一蹶不振。   &lt;br /&gt;
1526年，帖木儿的后裔巴布尔彻底推翻德里苏丹国的统治，建立莫卧儿王朝。&lt;/p&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 学历史有什么用]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/history/2022/05/04/01/</br>凡是在朋友圈问我这个问题的，我基本上都是一句话：互联网没有增量了，多学点历史没坏处的。因为手机上解释的多了，我打字累，再加上别人也不一定愿意听，可能纯粹就是随便问一句。 - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[https://cdn.kymjs.com:8843/qiniu/images/37.jpg]]></image>
        <pubDate><![CDATA[2022-05-04]]></pubDate>
        <link><![CDATA[https://kymjs.com/history/2022/05/04/01/]]></link>
        <tags>
          
          <tag>世界史</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>history</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/history/2022/05/04/01/</br>&lt;h3 id=&quot;学历史有什么用&quot;&gt;学历史有什么用&lt;/h3&gt;

&lt;p&gt;之前在朋友圈发我学中亚地区历史的时候，碰到朋友问的最多的问题就是这个了：学历史有什么用。   &lt;br /&gt;
凡是在朋友圈问我这个问题的，我基本上都是一句话：互联网没有增量了，多学点历史没坏处的。因为手机上解释的多了，我打字累，再加上别人也不一定愿意听，可能纯粹就是随便问一句。   &lt;br /&gt;
其实说到底，任何开放性问题要想解答都要引入特定环境作为背景。通常来说，作为一个程序员，学历史可能真的没什么用。但是如果是经济下行，互联网公司大量裁员的今天，了解一下历史上经济危机前期或两国开战前期，做什么事情最容易保命甚至发达，可能就是一件收益颇丰的事情了。&lt;/p&gt;

&lt;p&gt;学历史有什么用基本上也就两点：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;了解历史史实&lt;br /&gt;
史实其实没什么好说的，哪怕是理科生，初中也学过基础的历史课程，甚至很多内容还被强迫必须背下来。一是从全局了解历史面貌，至少看电视剧的时候也不会问出刘邦不是秦始皇时期的人怎么又变成汉朝的人这种问题。第二就是一种成就感吧，比如你可以自信的跟别人说出：清朝时期就已经能去纽交所买股票，喝可口可乐了。&lt;/li&gt;
  &lt;li&gt;锻炼出探究事物背后逻辑的能力。 &lt;br /&gt;
也可能是我作为一个程序员的职业病，遇到什么事都想搞清楚是什么原因造成的这件事发生。我觉得很多课堂上的老师都会忽略这种逻辑的推理，经常是照着书读，当时只知道老师是这么教的，但是不知道为什么，而现在却已经锻炼出把问题带入到特定环境去分析的能力。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;成年人应该怎么学历史&quot;&gt;成年人应该怎么学历史&lt;/h3&gt;

&lt;h4 id=&quot;参考系法&quot;&gt;参考系法&lt;/h4&gt;

&lt;p&gt;参考系法是最容易的，而且后期跟人吹逼的时候，讲出来也是最顺畅的，我自己用的就是这种方式来学历史的。  &lt;br /&gt;
首先你要有一个自己熟悉的参考坐标系，比如我们都学过的中国史是最好的坐标轴，在这个参考系里面有一个关键的事件点的先后顺序要知道。    &lt;br /&gt;
比如说最简单的&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;以电视剧中封神榜作为历史起点，周朝开国天子姬昌姬发灭了商朝纣王，建立周朝。&lt;/li&gt;
  &lt;li&gt;然后是周朝分封诸侯，开始了春秋战国，诸子百家。&lt;/li&gt;
  &lt;li&gt;电视剧《神话》，秦始皇统一中国，顺带刘邦跟项羽打仗，最后有了汉朝，顺带也知道了刘邦他老婆叫吕雉，儿子叫刘盈。&lt;/li&gt;
  &lt;li&gt;然后有首歌”东汉末年分三国”，顺带也就知道最早是西汉，然后才变成东汉，汉朝以后是三国。&lt;/li&gt;
  &lt;li&gt;三国最后有个老六司马懿，熬死了曹魏的所有人，然后有了晋朝。&lt;/li&gt;
  &lt;li&gt;有个词叫『魏晋南北朝』，这是历史老师最烦的，因为这是一大堆朝代。&lt;/li&gt;
  &lt;li&gt;魏晋南北朝以后就是《隋唐英雄传》&lt;/li&gt;
  &lt;li&gt;再然后有了唐，就是那个词『唐宋元明清』&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一下子就把中国古代史记住了。&lt;br /&gt;
记住了中国古代史以后，就以这几个朝代作为坐标点，去横向延伸，看其他国家在这个时期的事情，就慢慢把全部的历史都记住了。&lt;/p&gt;

&lt;h4 id=&quot;公元纪年法&quot;&gt;公元纪年法&lt;/h4&gt;

&lt;p&gt;还有一种记忆方式，就是公元纪年法。这种方式相当于你把时间作为坐标轴，然后在这个坐标轴上去看历史上的几个大事件，他们发生的时间段都是什么时候。&lt;br /&gt;
然后慢慢，事件与事件之间又发生了哪些事，慢慢把事情的前因后果联系起来，就能把全部历史记住了。  &lt;br /&gt;
这一套记忆的方式，可以看看下面这个视频，如果你能把其中大部分王国的发家史和这个王国的事情有一个概述，那么你就很厉害了。&lt;/p&gt;

&lt;p&gt;手机端也可以抖音搜索：【&lt;a href=&quot;https://www.douyin.com/video/7094209692523646238&quot;&gt;开源实验室&lt;/a&gt;】 打开浏览。&lt;/p&gt;

&lt;script type=&quot;text/javascript&quot;&gt;
	kymjs_videos(&quot;https://cdn.kymjs.com:8843/qiniu/videos/2022050401.mp4&quot;);
&lt;/script&gt;

]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 印度的列国时代大一统——孔雀王朝]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/history/2022/05/03/01/</br>于是大约在公元前800-600年，相当于中国的春秋时期。这时期印度内部各种各样的种族斗争，被称为列国时代，跟我们的战国时期很像。加上后面的波斯入侵，希腊入侵，孔雀王朝崛起，精彩程度丝毫不亚于我们的战国时期。 - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220502_06.jpg]]></image>
        <pubDate><![CDATA[2022-05-03]]></pubDate>
        <link><![CDATA[https://kymjs.com/history/2022/05/03/01/]]></link>
        <tags>
          
          <tag>印度史</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>history</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/history/2022/05/03/01/</br>&lt;p&gt;前段时间在研究阿富汗的时候，特意了解了一下中东地区几个国家的历史，看着看着反倒觉得印度是个神奇的存在，因为整个印度的历史基本上就是印度的屈辱史，被各种民族各种国家入侵，然后关键他到现在还能活着。所以特意写一系列文章讲一讲。&lt;/p&gt;

&lt;h2 id=&quot;东边的摩揭陀王国&quot;&gt;东边的摩揭陀王国&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;有一个前提大家要知道，印度历史上，各种各样的王朝。别太当真以为跟中国的李唐王朝，大清王朝一样动不动就是上百年。印度的王朝可能一代人就是一个王朝，老爹还叫牛逼王朝，儿子就马上改名叫喂牛王朝了。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;诃黎王朝建立约公元前544年，由于摩揭陀国国王频毗沙罗是诃黎人，由他建立的王朝就被称为诃黎王朝。最早的摩揭陀国因为地理优势具备易守难攻的条件，再加上两代强势国王的经营，崛起也就成了必然。当时印度正处于列国时代，本土有16个国家并存，摩揭陀是其中一个。除了这些国外，还有数不清的城市和部落，也在彼此征伐战争不断。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220502_04.jpg&quot; alt=&quot;摩揭陀国&quot; /&gt;  &lt;br /&gt;
列国时代末期的摩羯陀国&lt;/p&gt;

&lt;p&gt;摩揭陀第一个国王就是公元前544年继位的频毗(pi)沙罗只有15岁。当时的他就已经懂得采取远交近攻的策略，通过联姻与强国居萨罗结盟，随后，慢慢吞并了旁边的大国鸳伽。没想到此时的儿子阿阇(shé)世为夺取王位，直接囚禁饿死了他。阿阇世继位后，进一步对外征服吞并了许多国家，摩揭陀国的势力一直扩张到恒河流域，成为十六雄国时代最强盛的国家。  &lt;br /&gt;
除了对外扩张，频毗沙罗和阿阇世二人对佛教的发展也作出了巨大的贡献。&lt;/p&gt;

&lt;p&gt;阿阇世早期迫害过佛教，佛陀(也就是现在我们说的佛祖)释迦牟尼有个一直想要谋害他的堂兄弟叫提婆达多，跟阿阇世是好友，就是这个提婆达多劝阿阇世篡位的。提婆达多曾经加入过释迦牟尼的僧团，但是后来因为意见不合与权力斗争，另外成立教派，也是之前讲佛教的18教派之一。&lt;br /&gt;
但是阿阇世篡位当上国王后，随着佛教的影响力，还有维护统治的需要，他也转而开始支持佛教。佛教历史上的第一次结集就是在阿阇世赞助下举办的。 &lt;br /&gt;
另外上面我们也提到，最早支持耆那教的国家，就是摩揭陀国了。  &lt;br /&gt;
整个摩羯陀国可能是跟基因有关，几乎每一任的国王，都是靠杀父篡位的。但偏偏开了这个先河的阿阇世，是自然老死的。 &lt;br /&gt;
后面继任的统治者残暴昏庸，无所作为，摩揭陀国渐渐衰落。约公元前430年，诃黎王朝的最后一任国王被大臣悉输那伽推翻，建立起幼龙王朝，继续执掌摩揭陀国。&lt;br /&gt;
不过这个幼龙王朝跟他名字一样幼小，总共就三代，儿子辈有个响亮的名字”黑阿育王”。黑主要是为了跟接下来有个孔雀王朝的阿育王做个区分，可能是他比较心黑。 &lt;br /&gt;
就是在他统治时期，爆发了佛教第二次结集。黑阿育王可能对结集进行了干预，他本来支持在本国的毗舍离比丘一方的大众部，后来他在妹妹的劝告下转而支持了西边“正统”的上座部一方。 &lt;br /&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220502_02.jpg&quot; alt=&quot;部派佛教&quot; /&gt;&lt;/p&gt;

&lt;p&gt;黑阿育王死了以后，就是历史重现，他的年幼的儿子们被推翻，建立了难陀王朝。难陀王朝是摩揭陀又一次大扩张时代，其扩张所及远远超出了摩揭陀的核心范围，公元前400年左右已经基本统一印度北部。最后是因为末代国王统治非常残暴，导致民众暴乱，一个叫旃(zhān)陀罗笈多的冒险家乘机推翻难陀王朝，建立了孔雀王朝。&lt;/p&gt;

&lt;h2 id=&quot;西北的犍陀罗国&quot;&gt;西北的犍陀罗国&lt;/h2&gt;

&lt;p&gt;公元前600-400年，印度次大陆正处于列国时期，共有16个国和不计其数的部落、城邦并存，政治混乱，国力衰弱。前面我们讲了东边摩揭陀国一统的事情，接下来讲讲西边印度河流域的事，一讲西边，那自然是开伯尔山口又进来人了。 &lt;br /&gt;
之前的文章我讲过，印度这地形，两面环山，两面环海，虽然南边是高原，但是北部还有块大平原，其实是个很好的地形。但是要命的地方是西北部他山脉没包上，留了个面朝中亚的开伯尔山口，中亚这地方，从古至今就没安稳过，所以，时不时就进来一波游牧民族。而离这个山口最近的国家，就是犍陀罗国了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220502_05.jpg&quot; alt=&quot;犍陀罗国&quot; /&gt;&lt;/p&gt;

&lt;p&gt;公元前550年左右，波斯帝国蒸蒸日上，国王居鲁士远征小亚细亚，东出伊朗高原，一路来到印度。面对久经沙场的波斯大军，犍陀罗本地的统治者很快就宣告投降了。&lt;br /&gt;
波斯帝国的边境扩展到印度河西岸，还留下了雕刻功绩的石碑，只是当时波斯帝国内有叛乱，所以在印度只留下一部分士兵驻扎，让印度上贡金粉。  &lt;br /&gt;
后来，波斯国王去世后，整个波斯帝国分崩离析，犍陀罗国这边也早就心怀不满，但是他们的反抗还没有成气候，波斯这边又诞生了一个厉害的君主，波斯贵族大流士。大流士希望凭借印度的海上航线，为打希腊运送士兵和物资。公元516年左右，整个帝国的波斯军人沿着印度河长驱直入印度境内，几乎没遭遇到任何抵抗，仅用了一年时间，大流士就整合了印度河流域兴起的城邦。&lt;/p&gt;

&lt;p&gt;波斯帝国入侵应该算印度历史上第一次被外国入侵，之前的雅利安人更多是算融入了印度，而波斯帝国入侵，则是真正意义上将印度划入了波斯帝国的领土。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220502_06.jpg&quot; alt=&quot;波斯帝国&quot; /&gt;  &lt;br /&gt;
当年波斯帝国的领土&lt;/p&gt;

&lt;h2 id=&quot;孔雀王朝印度的首次统一&quot;&gt;孔雀王朝，印度的首次统一&lt;/h2&gt;

&lt;p&gt;当波斯实力逐渐衰败后，印度开始了各个小国之间的混战。亚历山大征服波斯后，公元前327年，他一路向东朝印度进军，此时的印度各邦国非但不齐心御敌还想着从中获利。&lt;br /&gt;
从翻越兴都库什山脉进入印度后，亚历山大帝就一路势如破竹，将印度北边的多个国家收入囊中。他们少有对手，比较像样的印度土邦就是保拉瓦王国，当时王公波鲁斯同马其顿军艰难的战争持续了几个月，最终亚历山大俘获了投降的波鲁斯和他的儿子，宣告胜利。紧接着因为军中开始传播疫病，再加上长年的征战让士兵们看不到尽头，马其顿军开始了集体的罢工。也因为下一目标摩揭陀国当时正好是全盛时期的难陀王朝，难陀王朝是当时控制着恒河流域的广大地区，并有继续向外扩张，统一北印度之势。没有了士兵支持的亚历山大也只好放弃向恒河前进的计划，在公元前326年他停止了东征，在被征服的旁遮普地区设立总督进行统治，就撤军返回罗马了。&lt;/p&gt;

&lt;p&gt;这时，有个叫旃(zhān)陀罗笈多的人，率领当地人民揭竿而起，组织了一支军队，他是摩揭陀国人，出身是吠舍种姓，加上年轻，所以其实部队并不大，但是却赶走了马其顿军留守的人，趁机占领了一座城。因为他有一个自己的贵人——出身婆罗门种姓的考底利耶。考底利耶擅长权谋、军事和政治，是个了不起的智者。   &lt;br /&gt;
当时他们都正值青年，一边抗击马其顿侵略者，一边逐步蚕食难陀王朝的领地，扩大自己的势力。旃陀罗笈多不断的袭扰难陀王朝的外围领土，然后慢慢的向王朝的核心腹地渗透。最终难陀王朝无力支撑，被旃陀罗笈多在公元前324年彻底击败。&lt;/p&gt;

&lt;p&gt;王朝的领土不断扩大。但是公元前305年，亚历山大留下的另一个大将给孔雀王朝泼了盆冷水。旃陀罗笈多遇到了人生中的一大劲敌：亚历山大的部将塞琉古。亚历山大去世后，塞琉古继承了对中亚地区的统治。为了夺回被孔雀王朝占领的旁遮普，塞琉古挑起战争。期间断断续续打了一年多，最终双方议和结束。此后7年间，旃陀罗笈多都没有再发动大规模的对外征服战争。后来皈依了耆那教，将王位传给了自己的儿子宾头娑罗。他本人则前往南印度，到耆那教圣地进行修行，他按照正统耆那教的苦行，绝食而死。  &lt;br /&gt;
随着宾头娑罗治理，孔雀王朝已控制了印度河平原，恒河平原，孟加拉湾，德干高原以及远达阿拉伯海的广大领域，形成了史学界传统意义上的大帝国。直到公元前273年宾头娑罗逝世，他儿子阿育王在大臣成护的帮助下，与兄弟苏深摩争夺王位取胜，并把王族政敌全部杀死。上位后的阿育王最初也是四处征战，最后统一了印度全境，成为古代印度历史上空前强盛的时代。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220502_07.jpg&quot; alt=&quot;孔雀王朝&quot; /&gt;&lt;/p&gt;

&lt;p&gt;阿育王在统治初期被认为是一个暴君，后来也可能是统治需要，也可能是受人影响，开始接触佛教，即位后第七年皈依佛教，并且将佛教定为国教。不过虽然佛教为国教，其他宗教也还是能正常存在和发展，他还向周边国家派遣传教师至各地宣传佛教。从那以后开始佛教遂成为世界重要宗教之一。不过阿育王早期信奉是西边的上座部，在各国宣传的也是上座部佛法，但到了晚年又改信大众部，这也就是为什么印度周边，斯里兰卡、缅甸、泰国、柬埔寨、老挝及中国云南傣族都是南传佛教，结果后来玄奘去取经取回来的却是北传佛教的原因。&lt;/p&gt;

]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 印度列国时代的百家争鸣——沙门思潮]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/history/2022/05/02/01/</br>大约公元前800年-公元前100年，当时的印度有十六国，正处于列国时代，学术流派百家争鸣。耆那教并不是当时唯一反对婆罗门教的宗教。除了耆那教外，还有佛教、顺世派、不可知论派等等许多思想流派，它们被统称为沙门思潮。 - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220501_03.jpg]]></image>
        <pubDate><![CDATA[2022-05-02]]></pubDate>
        <link><![CDATA[https://kymjs.com/history/2022/05/02/01/]]></link>
        <tags>
          
          <tag>印度史</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>history</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/history/2022/05/02/01/</br>&lt;p&gt;前段时间在研究阿富汗的时候，特意了解了一下中东地区几个国家的历史，看着看着反倒觉得印度是个神奇的存在，因为整个印度的历史基本上就是印度的屈辱史，被各种民族各种国家入侵，然后关键他到现在还能活着。所以特意写一系列文章讲一讲。&lt;/p&gt;

&lt;h2 id=&quot;沙门思潮&quot;&gt;沙门思潮&lt;/h2&gt;

&lt;p&gt;大概公元前1500年，遍布亚洲中心的雅利安人就侵入了印度河古印度文明，他们把自己的多神教改成了婆罗门教，雅利安的祭司们也就成了最高贵的婆罗门，雅利安得武士们成为了国王官员刹帝利，雅利安的普通平民就成了商人农民吠舍，然后原本的古印度人就成了苦力劳工叫首陀罗。&lt;br /&gt;
这几个词都是来自当时雅利安传说：普鲁沙是众神为了献祭创造出的巨人，他死了以后，身体各部分就幻化成了众生。嘴巴变成婆罗门，双臂变成刹帝利，双腿变成吠舍，双脚变成首陀罗。这也就是印度种姓制的由来，虽然现如今被废弃了但依然影响着印度人的思想。当时的雅利安人随着入侵很快扩散到整个北印度，原住民就只能被挤到了南部的德干高原，然后雅利安人搞出了一套以婆罗门为精神领袖，以刹帝利为世俗领导的这些农业邦国。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220501_03.jpg&quot; alt=&quot;普鲁沙&quot; /&gt;&lt;/p&gt;

&lt;p&gt;但是问题没多久，刹帝利又不乐意了，凭什么我每天打仗，你天天吆喝训斥我。&lt;br /&gt;
于是大约在公元前800-600年，刹帝利内兴起了一种提倡众生平等、绝对精神的运动叫沙门思潮。 &lt;br /&gt;
沙门这个词也被翻译成：娑门、沙门那。大致上可以理解为出家人，修行者，跟我们汉传佛教中的和尚类似。&lt;br /&gt;
沙门思潮是当时一系列宗教起源运动的统称，沙门诸教不承认后期婆罗门所提出祭祀升天的说法，而是相信原来的轮回。这一时期的印度不仅时间上跟中国的春秋时期很接近，形态上也非常相像，当时印度有16个国家和多不胜数的小城邦，它们各自独立，征战不休。  &lt;br /&gt;
在这样动荡的环境下，思想空前活跃，形成了百家争鸣的局面。筏驮摩那(Vardhamāna)创立的耆(qi)那教就是其中重要的一派，另一个重要的派别就是我们熟知的佛教。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220502_01.jpg&quot; alt=&quot;列国时代&quot; /&gt;  &lt;br /&gt;
列国时代的16个大国&lt;/p&gt;

&lt;h2 id=&quot;耆那教&quot;&gt;耆那教&lt;/h2&gt;

&lt;p&gt;公元前599年，筏驮摩那出生，他父亲属刹帝利种姓，统治着贝拿勒斯的一个小王国。筏驮摩那自幼就过着安定富足的生活，后来娶妻生女，但30岁那年，筏驮摩那的父亲去世，兄弟争斗不休，厌倦宫廷生活的他便决定出家修行，寻找人生意义。&lt;br /&gt;
后来，筏驮摩那前往各处游历，开始了颠沛流离的旅途。因为没有行李，又经常绝食来磨练心志，时间长了衣服也磨损地破烂不堪，后来甚至裸体行乞，这样的生活，筏驮摩那一共坚持了整整12年。 &lt;br /&gt;
后来慢慢集结起一批信徒和追随者，因为他反对婆罗门教的种姓制度，受到了当时掌握军政大权的刹帝利种姓、经商致富的吠舍大商人的欢迎。&lt;/p&gt;

&lt;p&gt;当时的印度据说有十六国，正处于列国时代，学术流派百家争鸣。耆那教并不是当时唯一反对婆罗门教的宗教。除了耆那教外，还有佛教、顺世派、不可知论派等等许多思想流派，它们被统称为“沙门思潮”。 &lt;br /&gt;
这一时期也成为奠定古印度文明基本价值的轴心时代。佛教因为与耆那教的主张相似，两派产生了尖锐的矛盾。&lt;br /&gt;
最初，筏驮摩那带领众弟子在恒河流域一带传教，得到了摩揭陀王国、阿般提王国等国王的支持（上面那张图靠近南部的两个国家），之前的文章也提过，此时北方都还受雅利安人的婆罗门教统治，能有两个大国支持已经很不容易了。 &lt;br /&gt;
除了国王，还有王公贵族、朝中许多大臣都成为耆那教的信徒，筏驮摩那也完全不会为供养问题发愁。然而后来佛教兴起，王子等人也皈依佛门，甚至剃度出家，耆那教的势力受到了排挤，因为它有一个常人难以接受的问题。比起佛教，耆那教信徒的修行要更加严格，长期苦行甚至裸休让他们痩骨嶙峋、身上也总是污秽不堪。 &lt;br /&gt;
筏驮摩那是以智者的形象来到王廷的，当时贵族们热衷追捧智者，但是苦行僧的方式非常不利于耆那教的形象，与人们想象中的智慧、威严的智者完全不是一回事。筏驮摩那还算有名声，而他的弟子们没有智者的光环，在人们眼中形象就很糟糕了。此外，严格的耆那教徒都不会做屠夫、当兵，甚至不肯耕田。对他们而言，就连走路踩死一只蚂蚁、打死一只苍蝇，所犯下的罪过都与杀人别无二致。这样的宗教规范实在太过沉重。直到今天的耆那教教徒，依然长期佩戴口罩，以免呼吸杀死微生物。 &lt;br /&gt;
后来，越来越强势的佛教，让耆那教渐渐暗淡。筏驮摩那虽然一生从来没有跟佛陀见面，但他们的弟子却经常争斗，挑起论战，但是用一句话形容就是又菜又爱玩。类似的论战很多，也不光是佛教跟耆那教吵，那时候各种沙门教都在互相吵。佛陀就会经常亲自出马，还教授佛教弟子辩论术，耆那教往往占不到什么便宜。筏驮摩那本人原本无意争斗，但是后来他亲信弟子优婆离长者也改信佛教，筏驮摩那听到这个消息后，悲愤交加，吐血身亡。筏驮摩那死后，信徒已经有5万余人之多，他的弟子继续传播耆那教的主张，后来耆那教发展成为印度重要的传统宗教之一。&lt;/p&gt;

&lt;h2 id=&quot;佛教&quot;&gt;佛教&lt;/h2&gt;

&lt;p&gt;在公元前600年左右，释迦牟尼创立了佛教，与婆罗门教、耆那教共同成为影响印度的重要宗教。释迦牟尼的本名是乔达摩·悉达多，释迦牟尼是后人对他的尊称，意思是释迦族的圣人。释迦牟尼的父亲净饭王是释迦族的首领，妈妈是摩耶夫人，生活富足，因为看到人们的苦难才决心出家。 &lt;br /&gt;
释迦牟尼最早用了六年的时间深入极端苦行，而苦行就是当时兴起的沙门思潮中的一种修行方式。后来他来到一棵菩提树下静坐冥想，找寻到了答案立身成佛，尊称释迦牟尼。并把自己证悟的佛法讲给最早跟随他的五个人，而这五人也成为了释迦牟尼最初的弟子，被称为五比丘。&lt;br /&gt;
而这一次释迦牟尼的首次讲法，也被称为“初转法轮”。从此要构成佛教的基本要素，佛陀：释迦牟尼、法：佛法、僧：五比丘，三宝都具备了，佛教就正式创立起来。      &lt;br /&gt;
早期佛教认为佛陀只是一位导师，而不是神，信徒是向他学习的。同时，信徒要想超脱生死轮回必须离开家，在外苦行（早期的沙门思潮下的各个教派都是以苦行僧为主）。由于这种修行太过严苛，后来佛教逐渐产生了以功德为主，简化修行，同时神话佛陀的新学派，这种学派称为：大乘佛教。相对的，早期的佛教便被称为：小乘佛教。&lt;br /&gt;
早期佛教都是以思想教义讲述修行方法为主，但是，却没有将各类思想编著成册流传下来，随着释迦牟尼去世后，这种修行方式让佛教的发展停滞不前。而此时受其他教派打压，佛教逐渐危机四伏。  &lt;br /&gt;
后来五比丘之一的伽叶担起了这个重任，开始将分散在各地的有名望的佛学大师召集起来，汇编佛教经典，佛经也是从这时出现的，这一次集结也被称为佛教第一次集结。  &lt;br /&gt;
后来，到了公元前330年左右，虽然有了佛经，但是一些僧人自行修改，印度东部僧人提出十条新戒律，其中有几条是允许比丘收取钱财、可以存储食言随时食用、如果饭没吃饱可以继续进食，而最早五比丘之一的耶舍则认定这十条新戒律非法。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220502_02.jpg&quot; alt=&quot;部派佛教&quot; /&gt;&lt;/p&gt;

&lt;p&gt;之所以有这样的新戒律主要是因为印度东部的状况跟西部差距太大，当时以耶舍为首的西部佛教大概在摩偷罗这里，而东部十条新戒律的比丘在毗舍离。后来耶舍召集了大概700名西部比丘，与东部比丘辩论。从而导致两方爆发论战，这也就是佛教第二次集结，而到最后两方谁也说服不了谁，干脆各自推行自己的做法，原始佛教也分裂出两大传统，遵守西边的称为上座部，而遵守东边毗舍离比丘戒律的僧团则称大众部。再到后来出现了18个部派，佛教也从此进入部派佛教时期。  &lt;br /&gt;
后来印度大一统的孔雀王朝将佛教定为国教，还向周边国家派遣传教师至各地宣传佛教。当时的国王早期信奉是西边的上座部，在各国宣传的也是上座部佛法，但到了晚年国王又改信大众部，这也就是为什么印度周边的斯里兰卡、缅甸、泰国、柬埔寨、老挝，及中国云南傣族都是上座部，也叫南传佛教；结果后来玄奘去取经取回来的却是大众部(也叫北传佛教)的原因。     &lt;br /&gt;
佛教受到婆罗门教很大影响，一些用语概念来自婆罗门，而部派佛教中的一派密宗与婆罗门教结合更紧密，藏传佛教就是这一支，吸收婆罗门神体系，图为婆罗门神毗那夜迦，被佛教吸收为护法。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220502_03.jpeg&quot; alt=&quot;藏传佛教&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;摩揭陀国诃黎王朝&quot;&gt;摩揭陀国诃黎王朝&lt;/h2&gt;

&lt;p&gt;摩揭陀国必须得讲，主要是佛教、耆那教都是从跟他有很大的关系。而且到最后列国末期，也是他领土最大，可以把他类比成我们这边没能一统的秦国。&lt;/p&gt;

&lt;p&gt;诃黎王朝建立约公元前544年，由于摩揭陀国国王频毗沙罗是诃黎人，由他建立的王朝就被称为诃黎王朝。当时正处于列国时代，印度本土有16个小国家并存，摩揭陀就是其中一个。除了这些小国外，还有数不清的城市和部落，也在彼此征伐战争不断。&lt;br /&gt;
摩揭陀国的崛起依靠的是频毗沙罗和阿阇世两代国王的经营。&lt;/p&gt;

&lt;p&gt;除了对外扩张，二人对佛教的发展也作出了巨大的贡献。阿阇世早期迫害过佛教，佛陀(也就是现在我们说的佛祖)释迦牟尼有个一直想要谋害他的堂兄弟叫提婆达多，跟阿阇世是好友，就是这个提婆达多劝阿阇世篡位的。提婆达多曾经加入过释迦牟尼的僧团，但是后来因为意见不合与权力斗争，另外成立教派，也是之前讲佛教的18教派之一。  &lt;br /&gt;
但是随着佛教的影响力，还有维护统治的需要，他也转而开始支持佛教。佛教历史上的第一次结集就是在阿阇世赞助下举办的。  &lt;br /&gt;
另外上面我们也提到，最早支持耆那教的国家，就是摩揭陀国了。&lt;/p&gt;

&lt;p&gt;阿阇世死后，继任的统治者残暴昏庸，无所作为，摩揭陀国渐渐衰落。 &lt;br /&gt;
约公元前430年，诃黎王朝的最后一任国王被大臣推翻，建立起幼龙王朝，继续执掌摩揭陀国，随着扩张，公元前400年左右已经基本统一印度北部。&lt;/p&gt;

]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 印度真是个神奇的国度——总章]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/history/2022/05/01/01/</br>前段时间在研究阿富汗的时候，特意了解了一下中东地区几个国家的历史，看着看着反倒觉得印度是个神奇的存在，因为整个印度的历史基本上就是印度的屈辱史，被各种民族各种国家入侵，然后关键他到现在还能活着。所以特意写一系列文章讲一讲。 - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220501_01.jpg]]></image>
        <pubDate><![CDATA[2022-05-01]]></pubDate>
        <link><![CDATA[https://kymjs.com/history/2022/05/01/01/]]></link>
        <tags>
          
          <tag>印度史</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>history</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/history/2022/05/01/01/</br>&lt;h3 id=&quot;四大文明古国&quot;&gt;四大文明古国&lt;/h3&gt;

&lt;p&gt;四大文明古国，古中国、古印度、古巴比伦、古埃及。当然之所以有这四个古文明，是因为他们都有一条或多条河流：咱们中国的长江黄河，印度河，底格里斯河幼发拉底河，尼罗河。在这个纬度上，刚好是处于北纬30度副高气压带形成的一种亚热带沙漠气候下被南北山地发源的大河滋润的平原，使得阳光水源双滋润形成的最早的农业文明。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220501_01.jpg&quot; alt=&quot;四大文明古国&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这几个大河里面尼罗河在埃及，底格里斯河幼发拉底河在伊拉克，长江黄河在我们中国，印度河就比较好玩，他竟然全境几乎都在巴基斯坦。&lt;/p&gt;

&lt;p&gt;印度和巴基斯坦到底是个什么关系呢，很早的时候，印度有可能还是个独立的岛，在印度洋上往欧亚大陆上飘，结果撞在一起变成个大陆，顺带还撞出来个世界屋脊青藏高原还加个横断山脉，包括到现在都还有研究表明说青藏高原还在升高，也就是说印度这块还在往欧亚大陆上挤。然后现在的印度次大陆就形成了两面环海两边是山，随着高山流下的雪水，和海吹向山的季风就出现了山脉脚下的高度肥沃的印度河和恒河平原。尤其是在印度河流域，也就产生了古印度人的哈拉帕文化，也就是俗称的古印度文明。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220501_09.jpg&quot; alt=&quot;四大文明古国&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;外族入侵&quot;&gt;外族入侵&lt;/h3&gt;
&lt;p&gt;但问题是印度毁就毁在印度河这一边了，因为这个闭塞的印度次大陆，他还留了个口子，西北这边有个面向亚洲的口子，就是兴都库什山脉、苏莱曼山脉、昆仑山脉那个位置。这个口子叫开伯尔山口，口子对着的，还是个要命的中亚戈壁沙漠区，这个区域生活的是一帮游牧民族叫『雅利安人』，也就是现在的伊朗、阿富汗等主体民族，就算放在今天看，他也是个要命的地形。所以现在你看印度又有黑人又有黄种人，又有白人，就是因为这个口子，谁都能进来。伊朗的波斯语是叫”ayran”，其实就是现在的『雅利安』这个词。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220501_02.jpg&quot; alt=&quot;雅利安人入侵&quot; /&gt;&lt;/p&gt;

&lt;p&gt;大概公元前1500年，遍布亚洲中心的雅利安人就侵入了印度河印度文明。&lt;br /&gt;
当时印度的土著黑人叫达罗毗(pi)荼(tu)人就逃到了南边。&lt;br /&gt;
后来雅利安人把自己的多神教改成了婆罗门教，雅利安的祭司们也就成了最高贵的婆罗门，雅利安得武士们成为了国王官员刹帝利，雅利安的普通平民就成了商人农民吠舍，然后原本的古印度人就成了苦力劳工叫首陀罗。这几个词都是来自当时雅利安传说：普鲁沙是众神为了献祭创造出的巨人，他死了以后，身体各部分就幻化成了众生。嘴巴变成婆罗门，双臂变成刹帝利，双腿变成吠舍，双脚变成首陀罗。这也就是印度种姓制的由来，虽然现如今被废弃了但依然影响着印度人的思想。当时的雅利安人随着入侵很快扩散到整个北印度，原住民就只能被挤到了南部的德干高原，然后雅利安人搞出了一套以婆罗门为精神领袖，以刹帝利为世俗领导的这些农业邦国。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220501_03.jpg&quot; alt=&quot;普鲁沙巨人&quot; /&gt;&lt;/p&gt;

&lt;p&gt;但是问题没多久，刹帝利又不乐意了，凭什么我每天打仗，你天天吆喝训斥我。于是大约在公元前800-600年，相当于中国的春秋时期，刹帝利内兴起了一种提倡众生平等、绝对精神的运动叫沙门思潮。&lt;/p&gt;

&lt;p&gt;沙门思潮他里面最重要的一个教派，就是如今的佛教了。早期的佛教并没有翻起什么浪花，毕竟还有婆罗门这一层统治着。直到不久之后，隔壁雅利安同胞的波斯，又一次大规模入侵印度河流域，后来欧洲的希腊亚历山大东征来到了印度河。这个时候差不多相当于我们的战国时期。按照历史的先例，印度人民本来应该继续躺平的，没想到恒河地区的当时一个刹帝利邦国摩揭陀国揭竿而起，不仅统一印度大半，而且为了弥和阶级组群之间的矛盾，还皈依了平等的佛教，这就是著名的孔雀王朝。&lt;/p&gt;

&lt;p&gt;上面这一段历史，大约公元前800年-公元100年，相当于我们从西周到西汉，我后面单独花时间，写到我的 &lt;a href=&quot;https://t.zsxq.com/byzrvn2&quot;&gt;知识星球&lt;/a&gt; 里。这时期印度内部各种各样的种族斗争，被称为列国时代，加上后面的希腊入侵，孔雀王朝崛起，精彩程度丝毫不亚于我们的战国时期。&lt;/p&gt;

&lt;p&gt;已填坑：&lt;a href=&quot;https://articles.zsxq.com/id_hu6h6xwpaik0.html&quot;&gt;印度列国时代的百家争鸣——沙门思潮&lt;/a&gt; &lt;br /&gt;
第二部分：&lt;a href=&quot;https://articles.zsxq.com/id_6w2kwimop0gn.html&quot;&gt;印度列国时代的大一统——孔雀王朝&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;贵霜帝国&quot;&gt;贵霜帝国&lt;/h3&gt;

&lt;p&gt;公元200年左右，进入到了我们的西汉时期，甘肃河西走廊的少数民族月氏(zhi)，被北方的匈奴给击败，逃到了中亚阿富汗这一代，这群人又和当地的雅利安融合，然后又南下看到了印度河恒河流域，建立了贵霜帝国，定都在现在巴基斯坦的白沙瓦。&lt;br /&gt;
当年汉武帝刘彻派张蹇出使西域，就是找月氏族结盟的，结果月氏人没理他，后来大汉就自己把匈奴干翻了，结果匈奴又把月氏人赶跑了，月氏人跑到雅利安大本营待了一段时间，又跑到印度，把印度本土的孔雀王朝推了。&lt;br /&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220501_11.jpg&quot; alt=&quot;风水轮流转&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这个贵霜帝国在汉朝还曾想要东进新疆，最后被班超给击败打了回来，但依然不影响这个帝国的强大。&lt;br /&gt;
当时之所以从中亚南下到白沙瓦，也是因为当时的贵霜帝国在整个中亚加印度次大陆地区，都已经没有可以与贵霜称霸的强权。大约公元130年，当时的统治者是个刚上位的国王，叫迦腻色迦，急需一场征服战争来树立自己的威望，于是他将目光转向了富庶的北印度地区。这里离印度河流域更近，也方便入侵印度打仗。&lt;br /&gt;
贵霜帝国和伊朗的安息帝国、地中海的罗马帝国和咱们东亚的汉朝，并称为四大帝国时代。&lt;br /&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220501_04.png&quot; alt=&quot;普鲁沙巨人&quot; /&gt;&lt;/p&gt;

&lt;p&gt;但是，从公元400年左右开始，地球迎来了一个大寒潮时期，现在也有推测汉朝罗马衰败都跟这次寒潮有关，当时汉朝被鲜卑人入侵，罗马被日耳曼人入侵，而这个贵霜帝国，也遇到了号称白匈奴的嚈哒(yàn dā)人。到现在认为这批嚈哒人可能就是中亚的雅利安人、月氏人、匈奴人混在一起的游牧民族。&lt;br /&gt;
后来又到公元500年左右，就相当于我们南北朝晚期时候，游牧民族里边出现了一个真正的强者，就是来自于我们北方的突厥人，他向东吞灭了鲜卑的柔然国，向西灭了白匈奴，更是把大量的雅利安人同化成白突厥，后来一直是打到了乌克兰才结束。成为了巨大的突厥汗国，只可惜突厥人生不逢时，没多久之后，在西南的沙漠地带，阿拉伯人就靠着伊斯兰教迅速的整合兴起，先占据了当时罗马的叙利亚，又灭掉了伊朗的波斯，最后建立了非常强大的阿拉伯帝国。而我们这边大家应该很清楚了，就是大唐天朝一统天下。然后突厥汗国因为突然两边面临强敌，没多久就被肢解了。后来唐朝和阿拉伯就成为了世界上仅有的两大帝国，地理位置上也是直接在中亚接壤，此时南边的印度倒是也经历了几个大国度，先后为超日王的笈多帝国、戒日王的戒日帝国。你听听这名字应该也就知道有多酱油了，也没坚持几年。 &lt;br /&gt;
西游记就是戒日王时期的事情，当时玄奘跟戒日王关系还特别好。之后有次大唐又派了个使者叫王玄策来印度，结果没想到戒日王已经死了，印度还发生了政变，把王玄策关了起来。再后来被他逃了出去，跑到隔壁吐蕃国借了点人，差点把印度给灭了。这就是王玄策“一人灭一国”的真实故事，到现在西藏还有石碑记录这个事。&lt;/p&gt;

&lt;h3 id=&quot;伊斯兰王朝时代&quot;&gt;伊斯兰王朝时代&lt;/h3&gt;

&lt;p&gt;再往后，了解历史的应该都知道了，突厥人又一次崛起了，而且这次还是吸取了阿拉伯跟大唐优势的新突厥人。这一群突厥人，建立了东起中国西到欧洲的一系列伊斯兰苏丹王朝，苏丹是一个阿拉伯语的音译词，后衍生为伊斯兰国家的统治者头衔，现在我们把苏丹统治的国家被称为“苏丹国”。然后在中亚地区出来的迦色尼王朝、古尔王朝也先后占据了阿富汗，并且以阿富汗为核心彻底控制了印度河流域。 &lt;br /&gt;
这个时候是公元1200年左右，中国的宋朝。在这种环境之下，印度是又又又一次躺平，一方面西北地区开始逐渐伊斯兰化，另一方面本土的佛教也开始走向虚幻的神秘化，而曾经婆罗门教被牛逼的商羯罗融合佛教改成了印度教，再次从各个基层复苏起来。&lt;br /&gt;
再往后呢就是世界史的大事件了，蒙古成吉思汗这个大BUG，说大半个世界被他一锅端都不过分，当时突厥的古尔王朝还有一点残余势力，他们以印度河恒河流域的枢纽，从德里这个城邦为中心建立了一个伊斯兰化的邦国叫德里苏丹国。&lt;/p&gt;

&lt;p&gt;但问题是这个国家它其实不算是个国家，因为他本身的就是四十几个阿富汗突厥贵族为了利益组成的一种诸侯联邦状态。这个状态就有点像我们的战国时期，各个诸侯表面上都说自己是周天子的子民，但实际上都自立为王。当时的德里苏丹国也是这样，内部矛盾很多，要不是印度土著擅长躺平，估计早就像陈胜吴广一样揭竿而起，干翻这个王国了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220501_07.jpg&quot; alt=&quot;英国留的坑&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这一段伊斯兰苏丹王朝时期，也是段很有意思的历史。期间有个比较大的战局叫德里之战；还有印度宗教的改革，直接宗教信仰都变了；达伽马开辟了欧洲绕道非洲前往印度的新航线；东印度公司成立也都是在这期间发生的。我后面有时间单独在我的 &lt;a href=&quot;https://t.zsxq.com/byzrvn2&quot;&gt;知识星球&lt;/a&gt; 里写一篇文章讲。&lt;/p&gt;

&lt;h3 id=&quot;莫卧儿王朝&quot;&gt;莫卧儿王朝&lt;/h3&gt;

&lt;h4 id=&quot;第一次帕尼帕特战役&quot;&gt;第一次帕尼帕特战役&lt;/h4&gt;

&lt;p&gt;到头来改变这一切的反而又是一群外来者。当时蒙古到处肆虐欧亚大陆的时候，也是有反抗者的，一个是中国的朱元璋，另一个是中亚的帖木儿。帖木儿反抗蒙古的时候建立了一个帖木儿帝国，这个帖木儿其实也是蒙古血统，不过从小在中亚长大，是个中亚突厥化的蒙古人。他儿子叫巴布尔，当时利用德里苏丹国的内乱，部分民意代表向他求援的时候，南下直接进攻德里苏丹国，前前后后大概花了一年时间，期间有一场很著名的战役叫第一次帕尼帕特战役，当时打了大约一个月就结束了。最终建立了统一的莫卧儿王朝，定都在现在巴基斯坦的拉合尔，后来印度著名的泰姬陵也是这个王朝第五代王修建的。莫卧儿这个名字也就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mughul&lt;/code&gt;，就是蒙古的意思。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220501_05.jpg&quot; alt=&quot;莫卧儿王朝&quot; /&gt;&lt;br /&gt;
最初刚成立的莫卧儿王朝大概有这么大。&lt;/p&gt;

&lt;p&gt;莫卧儿王朝在1526年建立，大约是我们明末清初的时候，在莫卧儿王朝伊斯兰跟印度教的矛盾达到了一定的缓和，因为当时伊斯兰教大多数都是上层贵族去加入，然后还有一些和伊朗接壤的印度河那里的旁遮普跟信得地区，还有商业贸易发达的恒河下游的孟加拉地区，这些地方信的多（&lt;strong&gt;看上图，记住这几个地区，对比现在的世界地图你会发现很有意思的现象&lt;/strong&gt;）。 &lt;br /&gt;
而信仰印度教的更多是北方腹地从事农业的领主，这两股势力在当时基本上井水不犯河水。王朝的官方语言也是当时印度北部的通用语言，叫印地语。但是，蒙古王公一般称叫乌尔都(urdu)语的多，这个词也就是蒙古语蒙古包的意思，也叫鄂尔多，比如鄂尔多斯(ordos)就是一堆蒙古包的意思。&lt;br /&gt;
现如今巴基斯坦的官方语言就叫乌尔都语，而印度的官方语言就叫印地语(注意不是印度语)，这俩本质上就是一门语言，完全能互通的，差异甚至还没我们河南陕西的方言差异大。&lt;br /&gt;
1530年，莫卧儿帝国的开国国王巴布尔去世，22岁的儿子胡马雍继位。历史总是相似的，年轻的子孙守不住祖业，丢了国家的案例比比皆是。1539年，当时阿富汗的叛乱者跟着北印度地区的统治者舍尔沙将胡马雍赶出了国家，他也只能流亡到同样信仰伊斯兰教的波斯，而叛乱者在原本的莫卧儿国土上建立起苏尔王朝。 &lt;br /&gt;
当时胡马雍逃到波斯后取了一个学者的女儿，生了个儿子叫阿克巴，这儿子将来很牛逼，大家要记住他。 &lt;br /&gt;
胡马雍自从逃到波斯后，卧薪尝胆猥琐发育，一直到1554年等来了个机会。舍尔沙父子都先后去世，不满10岁的孙子继承王位。又是相似的历史，年轻的子孙守不住祖业，小国王的舅舅夺取了王位，但遭到了各地反叛，国家又陷入了分裂。&lt;br /&gt;
此时的胡马雍立即率军前往，趁着速尔王朝内乱，借着波斯人的火药再次统一了北方。&lt;br /&gt;
此时距离他当时流亡，已经过了16年，结果没想到刚过了一年好日子，第二年于1556年，去祷告的时候从楼梯上摔下身亡，小阿克巴也在这时继承了王位。&lt;/p&gt;

&lt;h4 id=&quot;第二次帕尼帕特战役&quot;&gt;第二次帕尼帕特战役&lt;/h4&gt;

&lt;p&gt;1556年，胡马雍意外身亡，13岁的阿克巴继承王位。之前我们就说过这儿子很牛逼，但是刚继位的时候，基本和亡国之君差不多。他只能在一些大臣的支持下，在兰诺尔城的一个花园内登基。阿富汗人喜穆趁机联合其他印度贵族，一举夺取了首都德里，建立起自己的统治，自称“超日王”，就是前面我们说的那个酱油王。 &lt;br /&gt;
这时候的阿克巴不仅没有国土，还差点失去朝廷官员。当时，许多大臣都被喜穆吓坏了，对这个小皇帝根本不抱任何希望，纷纷劝说阿克巴撤退甚至是投降。&lt;br /&gt;
只有一名忠心耿耿的老臣白拉姆汗坚决要求作战。他年过六旬，身经百战，曾为巴布尔和胡马雍的征服立下汗马功劳。胡马雍死时，他正在阿富汗招募新兵，得知消息后立刻率军前来辅佐新主。白拉姆汗建议阿克巴，趁着敌人还没有集结完毕，做好战争准备，主动出击。这种故事在中国史里经常看到，小皇帝阿克巴采取了白拉姆汗的建议，并御驾亲征，阿克巴和白拉姆汗率领1.5万大军向德里进发，巧合的是正好又是在帕尼帕特地区，30年前的巴布尔也是在这里大败阿富汗人，建立起莫卧儿帝国的。 &lt;br /&gt;
叠上了祖辈buff的阿克巴自然也是大获全胜，喜穆也被斩杀，此战之后，他重新夺回德里。此后，阿克巴经过南征北战，逐渐统一北印度地区。&lt;br /&gt;
这就是第二次帕尼帕特战争，本质上是印度本土贵族反抗外来的莫卧儿王朝统治的起义，战争失败后，莫卧儿王朝的统治反倒更加巩固。&lt;br /&gt;
阿克巴还有个孙子，叫沙贾汗，泰姬陵就是他为了纪念老婆修的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220501_08.jpg&quot; alt=&quot;莫卧儿王朝&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;英国殖民时期&quot;&gt;英国殖民时期&lt;/h3&gt;

&lt;p&gt;英国殖民这时候已经算近代史了，当时英国从一开始只想赚钱变成统治再到跑路留下一堆烂摊子，资料比较多也比较全，后面有时间我也单独总结一篇文章。&lt;/p&gt;

&lt;p&gt;到1700-1800年左右，乾隆年间，莫卧儿王朝也是堕落到连阿富汗人都能持续暴打的状态。法国人英国人开始从孟加拉登陆，先是接手大量的印度教邦，然后迅速向西向南蚕食整个印度。后来莫卧儿的时控区仅仅剩下了德里、旁遮普、信德这些西北地区。最终莫卧儿王朝就在火烧圆明园之前的1857年被英国女王冠名为印度女皇之下，然后彻底灭亡。所以结合来看鸦片战争时候，那一句：”英国在中国之何方”，并不是说清朝连英国在哪都不知道，实际上他们真正迷惑的，可能只是不知道那个遥远的欧洲岛国什么时候把近在咫尺的印度都彻底划成自己的了。 &lt;br /&gt;
不过英国统治了，也必然影响了一大部分当地土邦皇帝的利益，虽然印度擅长躺平，但是恶心英国的行为也从来没有停止过。至于印度的穆斯林，更是在一战期间发动基拉法特运动，谋求在国内的地位。最终在二战期间英国陷入了漩涡不能自拔，印度河畔的穆斯林贵族，也提出以这个旁遮普、阿富汗、克什米尔、信德、伊朗的俾路支斯坦建立巴基斯坦。&lt;br /&gt;
事实上巴基斯坦(Pakistan)的名字也是从这几个地区来的：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;旁遮普（P）&lt;/li&gt;
  &lt;li&gt;阿富汗 (A)&lt;/li&gt;
  &lt;li&gt;克什米尔 (Ki)&lt;/li&gt;
  &lt;li&gt;信德 (S)&lt;/li&gt;
  &lt;li&gt;俾路支斯坦 (stan)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这是印度这边的，而另一边，1932年，英国更是爆发了皇家海军哗变，导致英国的殖民统治在全世界都乱套了，所以二战之后，英国就打算赶紧解决掉印度问题。当然大家都知道，英国就擅长挑事，当时英国让印度的新总督蒙巴顿照着地图硬画了一个有印度教的印度国、伊斯兰教的巴基斯坦国让这两国分制。更诡异的是在东北角孟加拉地区也有大量的伊斯兰教民，当时还搞成一个叫东巴基斯坦的飞地，后来也是独立成孟加拉国了，还记得上面莫卧儿王朝那张图吧，看看这里的印度、巴基斯坦、东巴基斯坦。&lt;br /&gt;
还一个问题是两国的北方边境，就是印度河的源头克什米尔地区，因为信仰更复杂，还特地画成了一个待解决的地区，让民众自愿加入两方，这也就导致同文同种的两个国家，因为信仰、领土争端就维持着不断的矛盾，1947年的印巴战争也就是因为这个原因。&lt;br /&gt;
顺带说一句，这问题到现在都还没解决。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220501_06.jpg&quot; alt=&quot;英国留的坑&quot; /&gt;&lt;br /&gt;
百度地图标注的红色虚线&lt;/p&gt;

&lt;p&gt;再后来，独立后的印度反倒就飘了，觉得自己是亚洲霸主，搞了一个包括的青藏在内的大印度文化圈，结果被我们一顿暴打，这也结了一个梁子，那边巴基斯坦一看那当然敌人的敌人就是朋友，也就很难不和中国走得越来越近了。而除了这个外忧，巴基斯坦内部还有着属于伊朗系统的俾路支人和阿富汗人，俾路支地处印度河西的沙漠，是很贫穷的一块地，一直有一群土邦组织反对武装不满政府，所以当年苏联入侵阿富汗就是因为巴基斯坦给阿富汗提供了补给线，苏联就支援了这个俾路支武装，骚扰巴基斯坦。阿富汗不愧帝国坟场，苏联解体之后，美国又为了获得塔利班的情报，又开始支援这个俾路支武装。这个俾路支武装很多次把中巴关系当做是巴基斯坦政府的软肋，对当地中方企业工程展开了多次袭击，当然也包括这次事件。包括前段时间阿富汗内战时候，为了在巴基斯坦跟阿富汗关系上搞事，还在中国一带一路的工程上搞自杀式袭击。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/20220501_10.jpg&quot; alt=&quot;俾路支斯坦&quot; /&gt;&lt;/p&gt;

&lt;p&gt;有个很搞笑的事，俾路支一直把民族挂在嘴上，说要独立。但是这片土地上真正互相开打的，基本上都是同种族的人。所以比起我们的血脉，或许还真是我们选择的大地才真正主宰了我们未来的命运吧。&lt;/p&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 某一天，中国真会超过美国吗？]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/stickies/2022/03/30/01/</br>最近一直在苦苦思考这个问题：会不会我们所说的某一天，永远都不会到来？ - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[https://cdn.kymjs.com:8843/qiniu/images/blog_image/2022033003.png]]></image>
        <pubDate><![CDATA[2022-03-30]]></pubDate>
        <link><![CDATA[https://kymjs.com/stickies/2022/03/30/01/]]></link>
        <tags>
          
          <tag>生活</tag>
          
        </tags>
        <columnNum><![CDATA[3]]></columnNum>
        <column><![CDATA[思绪万千]]></column>
        <categories>
          
          <category>stickies</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/stickies/2022/03/30/01/</br>&lt;p&gt;最近一直在苦苦思考这个问题：会不会我们所说的某一天，永远都不会到来？ &lt;br /&gt;
哪一天呢？&lt;br /&gt;
很长一段时间，很多国内国外的媒体，一直在说一件事：再给中国一点时间，再给中国一个战略机遇期，中国就会超越美国，成为世界第一经济大国，就会登顶了。&lt;/p&gt;

&lt;p&gt;这些年，这方面炒作很多。&lt;br /&gt;
记得不知道是2018，还是2019年时，国际货币基金组织给出的报告是：2030年左右，中国的GDP将实现对美国的赶超，上升为全球第一名。&lt;br /&gt;
后来，疫情来了。&lt;br /&gt;
又有种说法，说中国会在2024年，就超越美国，成为世界第一经济大国。&lt;br /&gt;
我们国内专家要谦虚一点，前段时间，有人说：中国在2028年到2030年，将可能超越美国，成为世界第一大经济体。&lt;br /&gt;
但不管哪种说法，都是建立一个基础上的：总有一天，中国的GDP会超越美国，成为世界第一大经济强国。&lt;br /&gt;
对绝大多数人来说，这可能已经成为一种信仰。&lt;br /&gt;
未来就在招手。&lt;/p&gt;

&lt;p&gt;但我们不妨思考另一个问题：万一这一天永远不会到来呢？&lt;br /&gt;
我之所以会这么想，是因为看到这条新闻：中国决定向斯里兰卡提供2000吨大米作为紧急粮食援助。&lt;br /&gt;
据中国驻科伦坡大使馆介绍，此次捐赠价值约250万美元（含运费），是应斯里兰卡政府要求，鉴于该岛国目前面临粮食短缺的困境而提出的。&lt;br /&gt;
看上去，这件事好像没什么，中国又多了一个朋友啊。&lt;br /&gt;
但如果你细想一下，就会发现不对劲：这个世界的老大，是美国啊。
现在这个世界体系是美国人创建的，也是美国人在维护的，当然美国人也是最大的受益者。一个国家发生粮食短缺，是对美国所创建的国际体系的一种冲击，作为这个体系最大的受益者，应该是美国人最着急才对啊。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2022033004.webp&quot; alt=&quot;拜登&quot; /&gt;&lt;/p&gt;

&lt;p&gt;还可以再思考另一件事：&lt;br /&gt;
疫情刚爆发时，中国人在干嘛，在拼命防疫。&lt;br /&gt;
拼命防疫为了什么？为了早日复工复产。&lt;br /&gt;
为什么要复工复产，是因为如果我们不复工复产，那会有很多工人失业，他们都是要养家糊口的嘛。所以为了他们，各级政府都非常努力。
结果疫情控制住了，复工复产也搞得很不错。&lt;br /&gt;
但同时美国人在干嘛？&lt;br /&gt;
他们主要干两件事：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;第一件事：很多美国人在家里蹲，拿政府补贴拿到手软。美国全面开动了核动力印钞机，这大家都知道的嘛！&lt;/li&gt;
  &lt;li&gt;第二件事：进口中国，当然还有全世界的商品。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以，我们这么努力，为的是什么？&lt;br /&gt;
过去，美国人还给我们一张绿纸，现在连绿纸都不用给了，直接在我们的账户上加一点数字就行了。&lt;br /&gt;
这种数字，归根结底，就是一张欠条。&lt;br /&gt;
只有把它花掉时，才有价值，如果没花掉，连废纸都不如，因为它只是银行账户上的一串数字。&lt;br /&gt;
所以，中国有钱没钱，是中国说了算吗？&lt;br /&gt;
不是。&lt;br /&gt;
其实是美国说了算。&lt;/p&gt;

&lt;p&gt;所以看数字，有很多让人疑惑不解之处。&lt;br /&gt;
比如说钢铁这种东西，总是重要的工业品吧？&lt;br /&gt;
2020年，中国产量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10.65&lt;/code&gt;亿吨，占全球&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;56.71%&lt;/code&gt;；美国钢铁产量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;7268&lt;/code&gt;万吨，在全球产量中占比&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3.87%&lt;/code&gt;。&lt;br /&gt;
再来比粮食，我之前说过，光论农业，美国的地理条件比中国好太多。
2020年，全球粮食产量中国也是第一，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;6.57&lt;/code&gt;亿吨，美国呢，则是第二，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5.98&lt;/code&gt;亿吨。&lt;br /&gt;
粮食还算差距最小的。  &lt;br /&gt;
美国强过中国的，可能也就是石油、天然气了。&lt;br /&gt;
更别说，你要是走遍全世界，只怕货架上最多看到的，还是中国商品，全世界不到200个国家，但中国已经是120个国家及地区的第一大贸易伙伴，而且几乎世界上数得着的国家，中国都和它们是第一大贸易伙伴。&lt;/p&gt;

&lt;p&gt;这说明什么？&lt;br /&gt;
说明中国早就是世界第一大经济体了呀。&lt;br /&gt;
但在去年，美国的 GDP 是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;23.02&lt;/code&gt;万亿美元，增长了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5.6%&lt;/code&gt;；中国呢，则是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;17.73&lt;/code&gt;万亿美元，增长了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8.1%&lt;/code&gt;。&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8.1%&lt;/code&gt;，这个数字不低；但美国的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5.6%&lt;/code&gt;，涨得可也不低啊。&lt;br /&gt;
这不是偶然现象。&lt;/p&gt;

&lt;p&gt;上一张图：中美经济历年增长对比图。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2022033001.png&quot; alt=&quot;中美经济历年增长对比图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;有没有发现这么一个现象：&lt;strong&gt;大约从2010年起，中国和美国的经济增长，就出现了一个非常奇怪的现象：中国涨得快，美国也涨得快；中国涨得慢，美国也涨得慢&lt;/strong&gt;。  &lt;br /&gt;
渐渐地，差不多玩成了两条平行线。所以这是个非常不对劲的情况。&lt;br /&gt;
伴随着这种情况，还有一个更奇怪的现象。 &lt;br /&gt;
如果把全世界比作一家企业，老板是美国，中国、日本和欧洲就很像中层管理者，底层则是众多的发展中国家。&lt;br /&gt;
照道理说，美国是企业的老板，也是最大的受益者，它应该最关心这个世界。&lt;/p&gt;

&lt;p&gt;美国不是这样。老板三天两头搞七搞八，一副不务正业，要把公司搞散架的模样。&lt;br /&gt;
这时候，企业中层就慌了：公司垮了，该怎么办呢?我可是从底层干起，一直到现在，都是在这家公司的啊。公司倒了，我吃什么呢?这时候中层就会非常努力，比老板还在意公司的生存和死活。因为他们手头已经有了一点东西，很害怕损失掉。 &lt;br /&gt;
底层员工，反而无所谓，反正到哪里都是打工。&lt;br /&gt;
中国的困境就在于：其实整家公司，技术开发不少是靠你，产品线大部分也是你在做，销售也大部分靠你。中国本来早就可以跳出来去干，自己当老板，从此天大地大。&lt;/p&gt;

&lt;p&gt;但中国整体非常犹豫。就像很多现实中的企业中层一样，走到这一步不容易啊，如果自己干，还得冒着很大的风险，很多人会犹豫来犹豫去，有时候差点话要说出口了，辞职信要递出去啊，但一想到房贷，一想到车贷，一想到老婆孩子，就又把头缩回去了。&lt;br /&gt;
因为不愿意冒这种风险，所以很多时候，现实中很多企业中层本来明明有自立的能力和本钱，却还是本着“老板虐我一千遍，我待老板如初恋”的心态，老是想着“公司是我家，发展靠大家”的心态，整天勤勤恳恳，像老黄牛一样，竭力顶住老板的破坏，坚决维持着公司的正常运转。这和我们在2020年疫情发生时，全民严防死守，抓紧防疫。&lt;br /&gt;
等疫情一消退，立即投入生产，然后排出一队队的医疗队，带着物资，带着设备，跑到全世界各国去帮助当地人，是不是很有点像？&lt;br /&gt;
甚至连美国的医疗设备、防护用品，也基本都是中国提供的。&lt;br /&gt;
是不是很像不少公司里那个最赚钱、最能干，基本公司就靠它顶起大半个天的那种部门？&lt;/p&gt;

&lt;p&gt;很多人都觉得：这种部门，老板一定很重视，会巴结着它。&lt;br /&gt;
但只要你在公司里呆过，就应该知道——其实不会。&lt;br /&gt;
真实情况是：&lt;strong&gt;老板对这种部门的头头，是充满了猜疑和嫉妒，恨不得找出一切机会，把他做掉&lt;/strong&gt;。&lt;br /&gt;
所以直到现在，美国是一边拿着中国的东西，一边有事没事就制裁中国一下。&lt;br /&gt;
其他的中层，会怎么反应呢？&lt;br /&gt;
他们的部门不重要，很多人甚至只是因为公司在，他们才能过上中层的生活。&lt;br /&gt;
对他们来说，公司就显得特别重要，因为公司没了，他们就啥也不是了。&lt;br /&gt;
所以在这种时候，不管那个主要的部门有多么重要，不管他们心里有多明白，但为了自己的利益，他们肯定会站在老板那一边，有时候甚至老板一施眼色，他们就会跟着，甚至为了让老板更看得起自己，会像疯狗一样，去骂那个支撑起公司的顶梁柱中层。&lt;/p&gt;

&lt;p&gt;2020中国远赴意大利抗疫&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2022033002.png&quot; alt=&quot;2020中国远赴意大利抗疫&quot; /&gt;&lt;/p&gt;

&lt;p&gt;想想看，这些年欧洲人对中国的态度，是不是就像这些没啥用的中层？
所以，2020年疫情后，我们看到：其实欧洲人也在疯狂印欧元，然后用这些废纸，来买中国的产品和医护用品。&lt;br /&gt;
他们一边拿着中国生产的东西，一边跟着老大，疯狂臭骂中国。&lt;br /&gt;
最典型的，就是西域问题。&lt;br /&gt;
换句话说，是因为有某个特别能干的部门，老板才可以啥事不管，一边捣蛋还能一边拿到最大利益，其他部门中层也能因此分沾雨露，虽然它们对公司的贡献，其实非常小。&lt;br /&gt;
最苦的人，就是这个部门的中层，还有整个部门的人。&lt;br /&gt;
因为他们要扛着整个公司。&lt;br /&gt;
他们还得忍受着老板的羞辱，以及其他部门的人因为老板指使，而进行的羞辱。&lt;br /&gt;
压力巨大。而且自从疫情以来，明明世界更依赖中国，但中国人却觉得压力更加巨大，甚至还创造出一个名词：卷。&lt;/p&gt;

&lt;p&gt;确实非常卷。这就奇怪了：中国生产了这么多东西，中国人比以前更勤劳了，世界也更依赖中国了，中国人不是应该享受到更美好的生活吗？为什么在这种一片前途光明时，很多人却感到卷了呢？&lt;br /&gt;
原因很简单。&lt;br /&gt;
因为疫情之后，要中国这个中层来扛的东西更多了呀。&lt;br /&gt;
比如说这次斯里兰卡粮食短缺。&lt;br /&gt;
比如说将来还会遇到的其他东西，比如说阿富汗，美国人拍拍屁股走路了，还把阿富汗的外汇储备给吞了，是谁在给阿富汗援助？&lt;br /&gt;
很多时候，也是中国啊。&lt;br /&gt;
我们会发现：这个能干的中层，在做的很多事情，都是为了维持这个公司，或者世界体系，为了养那个肆无忌惮的老板，为了养那群同为中层的同事。&lt;br /&gt;
而且越来越沉重。&lt;/p&gt;

&lt;p&gt;其实在这个体系中，我们已经越来越处在盈亏线上，有时在线上，有时在线下，但我们却始终狠不下这个心来。&lt;br /&gt;
因为我们的公司，已经习惯了做外贸，换美元或欧元，然后后面再一整套，这种体系和习惯已经形成，如果改变，就会意味着利益的巨大调整。&lt;br /&gt;
没有人喜欢风险。&lt;/p&gt;

&lt;p&gt;所以，我们就看到了那条中美经济增长率的线。&lt;br /&gt;
美国如附骨之蛆，和我们玩起了”齐步走“。&lt;br /&gt;
这就是说：它其实在吸我们的血。&lt;br /&gt;
只要这条线存在，就意味着其实中国能拿多少，最终是美国说了算的。
就像你只要在公司，哪怕公司100%的业务都是你做的，老板说给你多少钱，你还是只能拿多少钱。&lt;br /&gt;
这恐怕就是中层管理者的困境。&lt;br /&gt;
我们能突破吗？&lt;br /&gt;
如果不能突破，前车之鉴已经一大堆。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2022033003.png&quot; alt=&quot;GDP对比&quot; /&gt;&lt;/p&gt;

&lt;p&gt;日本、英国、德国、法国这些中层，早年时也有意气风发时，但它们再意气风发，也只是暂时的，到了一定阶段，就纷纷坠落了。&lt;br /&gt;
看他们的人均GDP线，刚开始还挺好看的，越到后来越曲折！&lt;br /&gt;
只有美国这个老板，才是日子过得顺风顺水，一直是条平滑斜线。&lt;br /&gt;
我们如果不摆脱这个体系，将来也极可能会和他们一样。&lt;br /&gt;
俄罗斯和乌克兰之间的这场战火，如果仔细观察的话，会发现很多东西，说不定能给我们更多启发，同样也不断地在拷问我们：这个中层，还值得继续做下去吗？&lt;/p&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 基于 C++ 的 Android 协程设计]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/session/2022/02/26/01/</br>在Android 上，Kotlin 用灵活的语法封装出语言层面的协程（coroutine）新颖地解决了异步编程编码复杂的问题。本次分享将带领听众从一个小例子开始，更加深入，自顶向下的理解协程是什么，协程的本质与工作机制，动手设计出一个真正运行时的协程库   - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2022-02-26]]></pubDate>
        <link><![CDATA[https://kymjs.com/session/2022/02/26/01/]]></link>
        <tags>
          
          <tag>C/C++</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>session</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/session/2022/02/26/01/</br>&lt;p&gt;这篇文章是我在 2022【T沙龙】技术分享时所讲内容的文字版本，修改删减了演讲时的冗余言语，现免费开放给大家阅读，希望能给买不到票参加分享的 开源实验室 读者带来帮助。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.001.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然后首先有几个问题，第一个：协程是怎么来的？&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.002.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.003.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这个问题其实要追溯到很久以前的一个事情上。long long ago。。。。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.004.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;当那个时候我们计算机就是个铁块，就一个电脑可能只能去运行一个程序。比方说举个例子，像我们炒菜，一个厨师他去炒菜，那么他在同一时间的时候只能去炒一个菜。&lt;br /&gt;
一开始大家想要同一时间执行多个代码任务，于是就有了并发。从程序员的角度可以看成是多个独立的逻辑流，内部可以是多 CPU 并行，也可以是单 CPU 时间分片。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.005.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;进程就是这样抽象出来个一个概念，搭配虚拟内存、进程表之类，用来管理独立的程序运行、切换。&lt;br /&gt;
但是一并发就有上下文切换的问题，干了一半跑去处理另一件事，我这做了一半的东西怎么保存。&lt;br /&gt;
就好像我们刚刚那个厨师炒菜的例子，那这个时候就像相当于是同一个厨师，他可以一次炒两个菜。那么这个菜他可能刚刚炒了一半，我放了油，放了盐这些调味品了以后，突然跟我说要我再继续去炒下一个菜，当前这个菜先放到一边。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.006.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;后来硬件提升了，一电脑上有了好几个 CPU 就可以一人跑一进程，就是所谓的并行。  &lt;br /&gt;
但是一并行，进程数一高，大部分系统资源就得用于进程切换的状态保存。后来搞出线程的概念，大致意思就是这个地方阻塞了，但我还有其他地方的逻辑流可以计算，不用特别麻烦的切换页表、刷新 TLB，只要把寄存器刷新一遍就行。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.007.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;但是线程是一个非常麻烦的东西，因为他是操作系统调度的，你可能正在run，不知道什么情况，突然你的CPU就被交给另一个线程了，你就得靠边站，等着下一次翻牌子了。&lt;br /&gt;
你没办法决定线程在何时运行，你甚至不能正常停止一个线程。做 Java 的同学应该都知道，线程的停止只能是 run 的内容执行完或者出异常时，当然还有一种会造成死锁的方法就是直接调用stop()。&lt;br /&gt;
我们看图，箭头最右侧竖线表示线程已经结束了。由于你在主线程无法知道子线程什么时候开始，什么时候结束，甚至有可能主线程都结束了，子线程还在跑。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.008.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;就是因为这种情况，后来引入了一个概念，叫用户态线程。&lt;br /&gt;
如果你嫌操作系统调度线程有不确定性，不知道什么时候开始什么时候切走，其实是可以自己在进程里面手写代码去管理逻辑调度，这就是用户态线程。&lt;br /&gt;
而用户态线程是不可剥夺的，也就是说不会像内核态线程那样，run 着 run 着 CPU 就没了。通常控制权是程序员交给用户的，而用户最了解何时需要放弃执行权，这么做减少了系统切换次数，实现了最高的 CPU 利用率。
但是有一个问题，毕竟是模拟的操作系统调度线程，操作系统内核不知道多线程的存在，如果一个用户态线程发生了阻塞，就会造成整个进程阻塞，所以进程需要自己拥有调度线程的能力。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.009.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;而如果用户态线程将控制权交给进程，让进程调度自己，这就是协程。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.010.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;接下来是第二个问题：协程真的能提升我们的程序的性能吗？ &lt;br /&gt;
这也就说到了一个经常会提到的点，就是协程能够提升我们代码的性能。但事实上协程提升的这个性能并不是我们开发的过程中的那种代码的效率，而是协程真正能够提升我们程序的性能的原因，是因为：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.012.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;大家看到这张图红色的这个位置，它在有一个任务正在被阻塞着，然后它去执行另一个任务。其实这个时候就是一个协程的状态了，它阻塞了一个协程，同时阻塞状态的时候它却调用了另一个协程。那么这个时候实际上阻塞的只是一个协程而已。然后我们的线程依旧是一个顺序执行的状态，也就是它减少了一次线程的切换。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.013.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这也就是为什么我们说协程能够提升性能，其实它真正提升性能的原因是让我们的线程不停地跑，让线程一直去跑，减少了线程切换给我们程序带来的一个线程切换的开销，从而达到提升了我们整个程序的性能的原因。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.014.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;那么除了这一点，协程还有一些其他的优点。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;比方说协程的可控制：我们前面讲，线程的结束都没办法由我们程序员通过代码去直接控制它。但是协程是可以的，因为协程其实在我们代码中它的本质就是一个函数，然后函数挂起了以后，我们继续去调用下一个函数，做了一次函数的切换，所以它本质是可以被控制的。&lt;/li&gt;
  &lt;li&gt;协程的轻量级：网上有很多文章都会讲到这一点，就是协程的轻量级，它占用的资源非常小。回到我们刚刚讲的协程，其实它的本质就是一个函数，如果说只是一个函数的运行的话，那么他当然占用的资源是很小的。&lt;/li&gt;
  &lt;li&gt;是协程的安全性。我们大家应该都做过多线程开发。多线程开发的时候有一个非常大的问题，就是我们得要考虑数据的线程安全性。而协程其实它的本身是没有并发概念的，就像我们前面介绍的那张图，如果说一个协程发生了阻塞的话，它其实只仍然是在当前线程里面切换到了下一个协程去继续执行它，协程与协程之间的任务都是一个线性的，所以它其实是不具备并发能力的。而如果说真正我们在写代码的时候依然是需要考虑并发现程安全问题，是因为我们现代的编程语言都是有多线程概念的，就算我们没有再发起线程，我们还有一个主线程在继续执行的，所以我们的协程都是运行在线程里面的，那么一定程度上它也具备了多线程的一个并发执行的能力，造成我们不得不应对多线程的数据安全问题。&lt;/li&gt;
  &lt;li&gt;第四点就是语法糖的优势。对于不同的语言来讲，都有不同的语法糖。几乎所有的具备协程能力的编程语言。比方说像 Python、go、C# 这样的语言。他们对于协程专门做了一层语法糖的封装，可以帮助我们更方便地去书写一个线性的代码。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.015.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;那么再回到我们今天的一个主题，就是，如果我们自己去设计一个安卓协程库的话我们要怎么去设计呢？&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.016.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;首先一个完整协程库，它所具备的功能应该是启动挂起这两个能力。然后还有线性的能力，比方说我们当前有一个协程，它需要被优先的执行。我们需要能够对这个协程做一个校验，然后同时把前一个协程挂起，最后当前的协程执行完了以后，再继续恢复之前先挂起的那个协程，再继续执行。&lt;br /&gt;
这样的这样的一些基本能力我们肯定是得要实现的。而如果要实现这样一些基本的能力的话，我们完整的一个思路肯定是分三步。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.017.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;第一步协程肯定是运行在线程里面的，所以我们基于 C 语言去构建一个 C 的线程， C 里面有一个 &amp;lt; pthread.h &amp;gt; 的库，它是可以直接帮我们构建在 C 层的线程的， C++ 也可以直接去调用这个库完成一个线程的创建。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.018.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;构建完线程了以后，就开始构建我们的协程，然后将构建出来的协程交给 C 线程去调度。按顺序去一个一个的执行协程。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.020.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;第三步就是保证我们协程的一个线性状态。就是当一个协程我们调用它 delay 方法了以后，应该是可以把当前的协程挂起，然后继续去执行接下来的协程的。&lt;br /&gt;
另外，对于另一个协程调了他的 join() 方法的话，它应该是可以优先地插入到当前正在执行的这个协程里面，同时打断也就是挂起当前正在执行的协程，然后优先去执行我们插入的这个协程。&lt;br /&gt;
当插入的这个协程执行完了以后，然后再恢复之前被挂起的协程，让他继续去执行。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.022.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Talking is chip, show me the code。&lt;br /&gt;
接下来我们来看代码，这个库其实我之前已经写了一个 demo 是放在了 github 上面，大家可以通过下面的这个链接或者直接扫就能看到这样的一个库。看完记得点个star：   &lt;br /&gt;
&lt;a href=&quot;https://github.com/kymjs/AndroidCoroutine&quot;&gt;https://github.com/kymjs/AndroidCoroutine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.023.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;首先第一步是我们要通过在 Java 层定义一个线程的实现，也就是一个线程它所要具备的一些能力。因为我们最终的目标是需要给安卓去使用它，所以我们肯定也是需要通过 JNI 调到 C 层方法的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.024.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;首先我们左上角的这个 JNI 入口，这里我们主要看到最下面的一行就是调了 OSThread 的 start() 方法。然后 OSThread 其实对应了我们刚刚在 Java 层声明的那个线程，然后调了他的 start 方法，我们就在 C 层模拟了一个 C++ 的线程，然后去调度 Java 层的线程。&lt;br /&gt;
而中间的这一块也就是我们现在看到第二张代码图，这里其实就是一个完整的在 C 层启动一个线程的模板，大家知道一下就行了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.026.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;其实前面的线程不是重点，重要的是我们协程的实现。&lt;br /&gt;
首先协程在 Java 层，它的定义有两个比较重要的点：第一个是协程需要有一个 async() 方法，我们这段代码的第29行表示我们要执行的这一个协程的协程体就是我们这个协程它的起始状态相有点类似于线程的 start() 方法，大家可以把它理解为线程的 star 方法。&lt;br /&gt;
不过 async() 方法执行完了以后，它允许你有一个回调在 await() 里面。&lt;br /&gt;
大家都知道 kotlin 里 async() 方法会返回来一个对象，然后这个对象又有一个 await() 方法可以继续执行，这是 kotlin 通过它的一个语言特性，给我们做了一个语法糖封装的。当然在这里我基于 C++ 示例，暂时先不管语法糖的能力。  &lt;br /&gt;
所以我把 await() 方法定义成了一个回调，也就是这里第 20 行列出来的 onAwait() 方法，也是当 async() 执行完了以后，它是允许响应一个回调结果的，也就是这里的一个结果参数 result。&lt;/p&gt;

&lt;p&gt;那么在协程的定义的时候，还有一个比较重要的就是我们要把协程跟一个线程做关联，也就是第 8 行。这里第 8 行有一个线程 threadid  就是它会最终在构造方法里面默认初始化。&lt;br /&gt;
构造方案我没有截出来，在第 8-20 行之间，每一次我们协程创建的时候，我默认会关联到当前的线程。如果你不希望用当前的线程去调度它，或者你想自己定义一个或复用已有的线程的话，你只需要把那个线程的 ID 传过来就可以了，然后他就会在这个线程里面去调度当前的协程。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.027.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然后协程的实现跟刚刚线程类似，我们主要看 init() 方法和 async() 方法里面。其实在我们所有协程最早的构造方法里面的时候，它会调用这个 init() 方法。然后 init() 方法其实就是把当前的协程跟要调度它的线程做一个关联。&lt;br /&gt;
我们看到这里，也就第 51 行。 threads 就是个 map, 我构建了一个全局的 map 去用来存放我们的线程，把这个 map 作为一个线程池，然后通过线程 ID 作为 Key 来取到对应要调度它的线程去执行。然后 53 行就对应的是把我们当前的这个协程交给这个线程去调度了。&lt;/p&gt;

&lt;p&gt;在我们整个方法里面，协程是被定义了一个生命周期状态的。&lt;br /&gt;
也就是看到我们左下角这里有总共 5 个状态，从 0-4。&lt;br /&gt;
0 就是一个协程被最初构建好的时候，它就是默认状态。
然后有 suspend 当前这个协程要被挂起了。&lt;br /&gt;
然后还有 need resume 表示它已经挂起了。&lt;br /&gt;
然后 3 就表示这个协程已经被恢复了，他这个协程可以继续的被执行了。&lt;br /&gt;
所以 123 其实是可以循环转换的，是从 1 到 2 再到3，然后再到 1 也是可以的，就这样循环转换。&lt;br /&gt;
4 finished 比较好理解，我们当前这个协程已经被执行完了，执行完了以后这个协程应该是要被回收，要被结束了的，就是 finished 状态。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.028.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;后面我们从这张图上面来看一下我们自己协程的运行原理。首先有两个入口，一个是 init() 第二个是 async()。 &lt;br /&gt;
我从左边开始讲从 init 开始，最早的时候也就是我们 new 了一个 Java 层的那个 coroutine 对象的时候，它会在构造方法里面调用它的 init 方法。&lt;br /&gt;
然后通过 JNI 调到 C 层里面，然后在 JNI 里面会去调用 C 的 start 方法，然后这个 start() 方法会去通过调协程的调度器，然后去在当前的线程里面做一个阻塞态的循环，去不停地通过协程的队列去不停地取协程，取出来了以后，然后开始调度这个协程，让这个协程开始执行。 &lt;br /&gt;
如果说这个协程是一个待恢复的状态的话，那么就恢复它，然后继续执行。&lt;/p&gt;

&lt;p&gt;然后另一边是我们协程的加入assync() 方法,也是一样，从 Java 层调到 C 层，然后它有一个关键点，就 attach() 方法， attach() 会把当前的这个协程和需要调度它的线程做一个关联，把我们当前的这个协程加入到那个调度的线程的队列里面去，然后那个线程就开始调度。这样的一个协程其实就是一个非常经典的生产者和消费者的模型。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.029.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然后我们接下来引入下一个概念，就是挂起点。因为我们前面讲了协程是可以随时把一个协程给挂起，挂起完了以后我们继续去执行下一个协程。我们刚才讲的时候，其实是为了让大家理解比较清楚，没有跟大家讲挂起点的这样一个概念。其实协程并不能随便地被挂起，它有一个概念就是挂起点的概念，协程只能在挂起点被挂起和恢复。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.030.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这张图是从 kotin 的官网截的一张图，就是他用来讲协程的，大家有兴趣可以去看一下 kotlin 协程的实现。其实他把整个协程的原理讲得非常的清晰，就是在 kotlin 官方的那个文档上面。 &lt;br /&gt;
我们这里看一下，从 start 到 end 是一整个协程完整的生命周期。然后中间有三个 suspension point。这三个点表示了协程的三个挂起点，协程只能在这三个点里面被挂起，以及被恢复。 &lt;br /&gt;
然后在第一个挂起点到第二个挂起点之间这样的一个段落，是协程的一个执行体，执行体是不能被中断的。 &lt;br /&gt;
那回到我们这个例子里面，协程的挂起点是在哪里呢？&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.031.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我们先还是回到刚才的运行原理图上，看到黄色这一部分，假设我们要对当前的这个协程，我给它调用了一个 delay() 方法，就是要把自己挂起一段时间。那么我们在协程的调度器里，它在执行到这个协程的时候，发现这个协程如果是要被挂起。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.033.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我们重点看到左半部分，右半部分现在不需要考虑。然后我们把这个图给拉长，假设当前的这个协程是一个需要被挂起的协程的话，那么我们会先把这个协程从队列里面给取出来。  &lt;br /&gt;
严格来说这个其实不是一个队列，但你可以把它理解为是一个双向队列，就是我们从协程头里面去取出来一个协程，然后把把它暂存起来。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.035.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;因为它已经被挂起了，所以我们先把这个挂起的协程给暂存起来，然后同时继续从队列里面去取下一个协程。取到下一个协程了以后，我们再去调度这个。然后把这个协程调度完了以后，我们再把刚刚挂起的那个协程继续给放回当前的队列头，让它放在那里了以后，等到我们当前这个协程执行完了以后，然后再去继续调度下一个那个已经被挂起的协程，然后再去恢复它，让他继续地执行。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.037.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然后我们看到刚刚讲的在我们这个库里面的协程的挂起点。其实在我们这个库里面，协程的挂起点只有一个，是因为实现的原因，后面我会给大家讲。 &lt;br /&gt;
我先讲一下为什么只有一个挂起点，因为我们是基于 C 层去做的。我们先看一下协程的整个逻辑。&lt;br /&gt;
在我们这个库里面，协程整个完整的生命周期，是从你调用了协程的 async() 开始，然后通过 Java 的 run() 方法，也就是 Java 层的 coroutine 对象里面的 run() 方法。&lt;br /&gt;
然后在 suspend await 这一步是在 C 层里面。&lt;br /&gt;
只能有这一个挂起点最主要的原因就是我们在调用在 C 层调用 Java 的时候，我们只能按方法去调用，没办法按语句去调用。如果我们要想在像 kotlin 那个样子，按照语句级别的增加挂起点的话，我们可能需要有两种方式。&lt;br /&gt;
第一种是给我们的所有的 Java 代码加入 hook, 也相当于是给每一个语句执行的执行前和执行后去调用一下我们 C 层的代码。&lt;br /&gt;
还有一种方式就是我们直接去改 JVM 的代码，因为我们都知道 Java 的 class 它最终在执行的时候都是靠 JVM 一行一行的去解释执行的。所以如果我们从 JVM 层面去改掉它的解释执行的方式，把每条语句结束作为一个挂起点其实也是能实现的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.039.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然后在 C 层我们使用的协程库其实有很多可以选择。比方说像 C++ 20 官方已经内置了协程的能力，但是它内置的那一份协程能力，只提供了一系列协程的基础实现，没有去做成封装成可用的 API 你让你能够调用。所以我猜测可能 C++ 会在马上的 C++ 23 版本里面会对它做一个封装，然后真正给到程序员，让开发者可以去用它的完善的一个调度函数体去直接调度一个协程。&lt;/p&gt;

&lt;p&gt;然后第二个其实是 C 库里面提供的一个协程的库，就是它很小，只有两个方法你就可以实现一个协程的能力，就是 setjmp() 和 longjmp() 然后我们这个库也是直接用的，因为这是成本最低的方法。&lt;br /&gt;
然后下还有剩下两个是在 github 上面比较出名的。第一个是风云写的一个库叫 Ucontext 这个库它实际上是通过 C 去嵌入了汇编，然后通过汇编去调度，把当前的调用栈给存入寄存器里面，然后再切到下一个调用栈去执行的。但是他有一个问题，他只帮你实现了 x86 和 x64 的 CPU 架构，所以它不能用在移动端上。 &lt;br /&gt;
然后第四个 libco 它是微信的一个开源库，也是可以用来实现协程能力的库，但是它也是只是用在服务端的，也只提供了 PC 的能力，它的整个实现方案也是参考了那个 Ucontext，后面包括还有一些 libcoevent 也是基于 Ucontext 跟 libco 做的一系列的封装去实现的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.038.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;看一下在我们库里面，setjmp库的使用，最主要的就是 39 行和 68 行。&lt;br /&gt;
setjmp() 有一个参数是一个指针，就是用来保存协程当前的运行上下文信息，恢复这个协程的时候也是通过这个指针恢复。&lt;br /&gt;
longjmp() 就是恢复时调用的，第一个参数就是指针位置，第二个是设置setjmp的返回值，在 C 里面，int 是可以做条件判断的非零即真，代码里传个 1 相当于 setjmp 会返回 true。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.040.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;

&lt;p&gt;最后还有坑需要填的，其实说是坑，其实也就是todo项：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;第一个，我们目前是没有类似于 kotlin 那样的一个回调语法的，所以我们现在 await 是用的典型的就是回调语法去做的， onAwait() 方法去实现的。后面其实我们是可以基于 kotlin 的 suspend 关键字，就是如果大家了解的话应该知道，suspend 其实也是 kotlinc 在编译期的时候做的一次语法翻译，他帮你在每一个方法编译以后，给这个方法在最后一个参数上面多插入了一个参数，这个参数其实就是一个用作回调的 callback。&lt;/li&gt;
  &lt;li&gt;第二个就是刚刚跟大家讲到了为什么只有一个挂起点的原因。因为 JVM 层的语法级别去实现，挂起点的这样能力开发起来太难了。就正常来说，对于一个普通的开发者是没办法去做到的。而如果用 hook，又会造成频繁 JNI 调用，影响性能。&lt;/li&gt;
  &lt;li&gt;然后第三个点是一个 IO 中断，前面跟大家讲在某一个协程发生阻塞的时候，我们应该是能够调度另一个协程去，然后把当前这个正在 IO 的协程给挂起的。这一点其实在 C 层是可以通过信号量去完成的。 &lt;br /&gt;
如果大家对于以上几个 todo 项感兴趣，也可以扫右上角的二维码，加入微信群，我们一起把遗留的几个点完善掉。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/android_coroutine.041.jpeg&quot; width=&quot;80%&quot; alt=&quot;基于 C++ 的 Android 协程设计&quot; /&gt;&lt;/p&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - Gradle 6.X 上传 aar 到 Nexus 私服]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/pay/2021/02/02/01/</br>昨天把 gradle 升级到 6.0 以后，发现这傻逼的 google 又直接干 API，以前上报的全部脚本都没法用了。我们原先上报一个 aar，是直接引入。   - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2021-02-02]]></pubDate>
        <link><![CDATA[https://kymjs.com/pay/2021/02/02/01/]]></link>
        <tags>
          
          <tag>Gradle</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>pay</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/pay/2021/02/02/01/</br>&lt;p&gt;昨天把 gradle 升级到 6.0 以后，发现这傻逼的 google 又直接干 API，以前上报的全部脚本都没法用了。我们原先上报一个 aar，是直接引入&lt;/p&gt;

&lt;div class=&quot;language-groovy highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;plugin:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;maven&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果新版本直接这个插件都被干掉了，看了官网文档以后才知道，换成了&lt;/p&gt;

&lt;div class=&quot;language-groovy highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;plugin:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;maven-publish&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后里面的API也全换掉了。&lt;/p&gt;

&lt;h3 id=&quot;解决-sourcesjar-task-过时&quot;&gt;解决 sourcesJar Task 过时&lt;/h3&gt;

&lt;p&gt;然后 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sourcesJar&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;task&lt;/code&gt; 也报了个警告，说&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;classifier&lt;/code&gt;已经过时了，以前代码是这样写的：&lt;/p&gt;

&lt;div class=&quot;language-groovy highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sourcesJar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;type:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Jar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;classifier&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;sources&apos;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个好解决，看一眼源码,要用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;archiveClassifier&lt;/code&gt; 去替代：&lt;/p&gt;

&lt;div class=&quot;language-groovy highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * Sets the classifier.
 *
 * @deprecated Use {@link #getArchiveClassifier()}
 */&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Deprecated&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setClassifier&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@Nullable&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;classifier&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// This is used by the Kotlin plugin, we should upstream a fix to avoid this API first.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// DeprecationLogger.deprecateProperty(AbstractArchiveTask.class, &quot;classifier&quot;).replaceWith(&quot;archiveClassifier&quot;).withDslReference().nagUser();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;archiveClassifier&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;convention&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;classifier&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;archiveClassifier&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;classifier&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;内部就是做了个代理，改成了新API，就解决了。&lt;/p&gt;

&lt;div class=&quot;language-groovy highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sourcesJar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;type:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Jar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;archiveClassifier&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;convention&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;sources&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;archiveClassifier&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;sources&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 自定义C/C++日志输出函数]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/code/2020/08/07/01/</br>在繁杂的项目中，日志打印必不可少。但是编写打印的工作，有时候是无趣的、繁琐的、浪费精力的。&lt;br&gt; 如何能够快速、方便的编写打印；如何清晰、准确的定位；如何简单并优雅的实现；最后才能让我们摆脱这样枯燥的、重复的工作？&lt;br&gt; 网上有很多强大的日志类工具，我也都使用过一些，有时候也并没有理想中的方便。今天我想分享给大家的一套我自己的解决方案。   - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2020-08-07]]></pubDate>
        <link><![CDATA[https://kymjs.com/code/2020/08/07/01/]]></link>
        <tags>
          
          <tag>C/C++</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>code</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/code/2020/08/07/01/</br>&lt;p&gt;在繁杂的项目中，日志打印必不可少。但是编写打印的工作，有时候是无趣的、繁琐的、浪费精力的。&lt;br /&gt;
如何能够快速、方便的编写打印；如何清晰、准确的定位；如何简单并优雅的实现；最后才能让我们摆脱这样枯燥的、重复的工作？&lt;br /&gt;
网上有很多强大的日志类工具，我也都使用过一些，有时候也并没有理想中的方便。今天我想分享给大家的一套我自己的解决方案。&lt;/p&gt;

&lt;h2 id=&quot;c-中标准输出方式&quot;&gt;C++ 中标准输出方式&lt;/h2&gt;

&lt;p&gt;对于单个变量输出，可以如下方式：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;delay:&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于多变量信息输出则需要如下方式：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;iostream&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Bill&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Age&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Score&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;86.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;121.3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Body_Weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;36&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Name:&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;, &quot;&lt;/span&gt;
              &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Age:&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Age&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;, &quot;&lt;/span&gt;
              &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Score:&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Score&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;, &quot;&lt;/span&gt;
              &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Height:&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;, &quot;&lt;/span&gt;
              &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Body_Weight:&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Body_Weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;问题的提出&quot;&gt;问题的提出&lt;/h2&gt;

&lt;p&gt;大家可以看见，需要添加很多对应变量名的字符串，导致写一次打印，非常耗时间。&lt;br /&gt;
我一直在想有没有更好的解决方案，形如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logOut(a, b, c, d);&lt;/code&gt; 这样简单方便的输出方式?&lt;br /&gt;
最后经过不断探索终于找到了一份这样的解决方案，而且只需要加入一个头文件即可，代码如下：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&quot;logger.h&quot;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Bill&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Age&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Score&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;86.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;121.3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Body_Weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;36&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;logDebug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Age&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Body_Weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输出结果：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;DEBUG&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[..&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cppdemo&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cpp&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Bill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Age&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Score&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;86.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Height&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;121.3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Body_Weight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;36&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;大家可能注意到了，背景图的内容就是这个，还有Info、Warn、Error并对应不同颜色输出。&lt;/p&gt;

&lt;h2 id=&quot;如何实现&quot;&gt;如何实现&lt;/h2&gt;
&lt;p&gt;那么对于以上效果，如何才能实现呢？&lt;br /&gt;
下面我将带领大家一步一步讲解我的心路历程与解决方案。这样也容易让大家了理解其中原理。&lt;/p&gt;

&lt;h3 id=&quot;单个变量的实现&quot;&gt;单个变量的实现&lt;/h3&gt;

&lt;p&gt;究其根本，就是减少对变量名称字符串的输入，但是打印的时候能够对应显示，便于我们分析。&lt;br /&gt;
对于单个变量，我很早就想到，用过宏“#”就可以轻松实现。&lt;/p&gt;

&lt;h3 id=&quot;宏--的妙用&quot;&gt;宏 “#” 的妙用&lt;/h3&gt;

&lt;p&gt;如果熟悉c++宏的小伙伴，可能知道里面有个“#”的用法，可以将对应的参数变成字符串，效果如下：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#define  toStr(x)  #x
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;toStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;  
&lt;span class=&quot;c1&quot;&gt;//  等价于 &lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么对于单个变量最简单的宏实现方式就是：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#define logs(x)  std::cout &amp;lt;&amp;lt; #x&quot;:&quot; &amp;lt;&amp;lt; x &amp;lt;&amp;lt; std::endl;
&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;输出结果：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;delay:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;显示文件名函数行号&quot;&gt;显示文件名、函数、行号&lt;/h3&gt;
&lt;p&gt;对于想要显示文件位置信息，c++中也有对应的宏，分别是  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__FILE__&lt;/code&gt;、 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__FUNCTION__&lt;/code&gt;、 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__LINE__&lt;/code&gt; ；&lt;br /&gt;
只需要做如下改动即可。&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#define FILE_INFO   &quot;[&quot; &amp;lt;&amp;lt; __FILE__ &amp;lt;&amp;lt; &apos;@&apos; &amp;lt;&amp;lt; __FUNCTION__ &amp;lt;&amp;lt; &apos;#&apos; &amp;lt;&amp;lt; __LINE__  &amp;lt;&amp;lt; &quot;]&quot;
#define logs(x)     std::cout &amp;lt;&amp;lt; FILE_INFO &amp;lt;&amp;lt; #x&quot;:&quot; &amp;lt;&amp;lt; x &amp;lt;&amp;lt; std::endl
&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输出结果：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[..&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cppdemo&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cpp&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[..&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cppdemo&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cpp&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;22&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;设置打印颜色&quot;&gt;设置打印颜色&lt;/h3&gt;
&lt;p&gt;大家都知道在Linux使用 ls 命令列出文件列表时，不同的文件类型会用不同的颜色显示。那么如何实现这样带颜色的文本输出呢？&lt;br /&gt;
在bash中，通常我们可以使用echo命令加-e选项输出各种颜色的文本，例如：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[31mRed Text&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[0m&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[32mGreen Text&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[0m&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[33mYellow Text&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[0m&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[34mBlue Text&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[0m&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[35mMagenta Text&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[0m&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[36mCyan Text&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[0m&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;其中：”\033[31m”、”\033[31m”、”\033[0m”等是ANSI转义序列（ANSI escape code/sequence），它控制文本输出的格式、颜色等。&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;格式&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; 
 &lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;033&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;显示方式&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;字体颜色&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;背景颜色&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;中间是变颜色的内容&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;033&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;其中各个参数意义如下：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;字体色&lt;/span&gt;            &lt;span class=&quot;err&quot;&gt;背景色&lt;/span&gt;           &lt;span class=&quot;err&quot;&gt;颜色&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;---------------------------------------------&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;                &lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;              &lt;span class=&quot;err&quot;&gt;黑色&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;31&lt;/span&gt;                &lt;span class=&quot;mi&quot;&gt;41&lt;/span&gt;              &lt;span class=&quot;err&quot;&gt;红色&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;                &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;              &lt;span class=&quot;err&quot;&gt;绿色&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;33&lt;/span&gt;                &lt;span class=&quot;mi&quot;&gt;43&lt;/span&gt;              &lt;span class=&quot;err&quot;&gt;黃色&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;34&lt;/span&gt;                &lt;span class=&quot;mi&quot;&gt;44&lt;/span&gt;              &lt;span class=&quot;err&quot;&gt;蓝色&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;35&lt;/span&gt;                &lt;span class=&quot;mi&quot;&gt;45&lt;/span&gt;              &lt;span class=&quot;err&quot;&gt;紫红色&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;36&lt;/span&gt;                &lt;span class=&quot;mi&quot;&gt;46&lt;/span&gt;              &lt;span class=&quot;err&quot;&gt;青蓝色&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;37&lt;/span&gt;                &lt;span class=&quot;mi&quot;&gt;47&lt;/span&gt;              &lt;span class=&quot;err&quot;&gt;白色&lt;/span&gt;

&lt;span class=&quot;err&quot;&gt;显示方式&lt;/span&gt;           &lt;span class=&quot;err&quot;&gt;意义&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-----------------------------------&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;                &lt;span class=&quot;err&quot;&gt;终端默认设置&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;                &lt;span class=&quot;err&quot;&gt;高亮显示&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;                &lt;span class=&quot;err&quot;&gt;使用下划线&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;                &lt;span class=&quot;err&quot;&gt;闪烁&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;                &lt;span class=&quot;err&quot;&gt;反白显示&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;                &lt;span class=&quot;err&quot;&gt;不可见&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;那么添加颜色就可以如下处理：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#define OUT_RED     &quot;\033[0;31;1m&quot;
#define OUT_GREEN   &quot;\033[0;32;1m&quot;
#define OUT_END     &quot;\033[0m&quot;
&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define FILE_INFO       &quot;[&quot; &amp;lt;&amp;lt; __FILE__ &amp;lt;&amp;lt; &apos;@&apos; &amp;lt;&amp;lt; __FUNCTION__ &amp;lt;&amp;lt; &apos;#&apos; &amp;lt;&amp;lt; __LINE__  &amp;lt;&amp;lt; &quot;]&quot;
#define logRed(x)       std::cout &amp;lt;&amp;lt; OUT_RED &amp;lt;&amp;lt; FILE_INFO &amp;lt;&amp;lt; #x&quot;:&quot; &amp;lt;&amp;lt; x &amp;lt;&amp;lt; OUT_END &amp;lt;&amp;lt; std::endl
#define logGreen(x)     std::cout &amp;lt;&amp;lt; OUT_GREEN &amp;lt;&amp;lt; FILE_INFO &amp;lt;&amp;lt; #x&quot;:&quot; &amp;lt;&amp;lt; x &amp;lt;&amp;lt; OUT_END &amp;lt;&amp;lt; std::endl
&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;logRed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logGreen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;多个变量的实现&quot;&gt;多个变量的实现&lt;/h2&gt;

&lt;p&gt;最后一个问题，在多变量的时候，如何输出呢？&lt;br /&gt;
对于多变量的输出，就是要弄清楚究竟有多少个变要输出，这样就可以扩展宏，进行足个输出即可。&lt;/p&gt;

&lt;h3 id=&quot;最简单的方案&quot;&gt;最简单的方案&lt;/h3&gt;

&lt;p&gt;非常傻瓜的方式可以直接如下：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#define logs1(a)       std::cout &amp;lt;&amp;lt; OUT_GREEN &amp;lt;&amp;lt; FILE_INFO &amp;lt;&amp;lt; #a&quot;:&quot; &amp;lt;&amp;lt; a &amp;lt;&amp;lt; OUT_END &amp;lt;&amp;lt; std::endl
#define logs2(a,b)     std::cout &amp;lt;&amp;lt; OUT_GREEN &amp;lt;&amp;lt; FILE_INFO &amp;lt;&amp;lt; #a&quot;:&quot; &amp;lt;&amp;lt; a &amp;lt;&amp;lt; &quot;, &quot;#b&quot;:&quot; &amp;lt;&amp;lt; b &amp;lt;&amp;lt; OUT_END &amp;lt;&amp;lt; std::endl
#define logs3(a,b,c)   std::cout &amp;lt;&amp;lt; OUT_GREEN &amp;lt;&amp;lt; FILE_INFO &amp;lt;&amp;lt; #a&quot;:&quot; &amp;lt;&amp;lt; a &amp;lt;&amp;lt; &quot;, &quot;#b&quot;:&quot; &amp;lt;&amp;lt; b &amp;lt;&amp;lt; &quot;, &quot;#c&quot;:&quot; &amp;lt;&amp;lt; c &amp;lt;&amp;lt; OUT_END &amp;lt;&amp;lt; std::endl
#define logs4...
#define logs5...
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;one&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;two&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;three&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;logs1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logs2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;two&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logs3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;two&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;three&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;即多少个变量，对应用哪一个函数输出；由于宏不能够重载，所以不能用相同的名字！&lt;br /&gt;
但是这种方式有如下几个问题：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;输出函数名称会很多。&lt;/li&gt;
  &lt;li&gt;还要数参数个数，与函数名对应。&lt;/li&gt;
  &lt;li&gt;如果想要不同颜色输出，则又要添加新的函数名。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;计算参数个数&quot;&gt;计算参数个数&lt;/h3&gt;

&lt;p&gt;因为C/C++多参数输入，可以直接用 “…”代替，比如常见的 printf 函数就是如此！原型如下：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;__cdecl&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_Format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;只要我们能够动态计算出参数个数，就可以通过映射的方式，绑定到对应参数数目的输出函数上面。&lt;br /&gt;
那么如何计算呢？&lt;br /&gt;
经过苦苦查找，让我找到如下的方式：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#define ARG_COUNT_PRIVATE(_0,  _1,  _2,  _3,  _4,  _5,  _6,  _7,  _8, N, ...) N
#define ARG_COUNT(...)      ARG_COUNT_PRIVATE(0, __VA_ARGS__, 8,  7,  6,  5,  4,  3,  2,  1,  0)
&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define FILE_INFO   &quot;[&quot; &amp;lt;&amp;lt; __FILE__ &amp;lt;&amp;lt; &apos;@&apos; &amp;lt;&amp;lt; __FUNCTION__ &amp;lt;&amp;lt; &apos;#&apos; &amp;lt;&amp;lt; __LINE__  &amp;lt;&amp;lt; &quot;]&quot;
#define logs(a)       std::cout &amp;lt;&amp;lt; OUT_GREEN &amp;lt;&amp;lt; FILE_INFO &amp;lt;&amp;lt; #a&quot;:&quot; &amp;lt;&amp;lt; a &amp;lt;&amp;lt; OUT_END &amp;lt;&amp;lt; std::endl
&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;one&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;two&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;three&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ARG_COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ARG_COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;two&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ARG_COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;two&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;three&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输出结果：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[..&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cppdemo&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cpp&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ARG_COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[..&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cppdemo&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cpp&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;31&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ARG_COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;two&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[..&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cppdemo&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cpp&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ARG_COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;two&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;three&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后只要我们在用C++宏里面的 “##”进行连接，就可以将 logs ## Num 变成对应的函数。&lt;/p&gt;

&lt;h3 id=&quot;最后的源码&quot;&gt;最后的源码&lt;/h3&gt;

&lt;p&gt;所有的问题都已经解决了，那么最后的代码就如下了。&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#ifndef LOGGER_H
#define LOGGER_H
&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;///================= package define =====================&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define ARG_COUNT_PRIVATE(\
     _0,  _1,  _2,  _3,  _4,  _5,  _6,  _7,  _8,  _9, \
    _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \
    _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \
    _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, \
    _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, \
    _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, \
    _60, _61, _62, _63, _64, N, ...) N
&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define ARG_COUNT(...)      ARG_COUNT_PRIVATE(0, __VA_ARGS__,\
    64, 63, 62, 61, 60, \
    59, 58, 57, 56, 55, 54, 53, 52, 51, 50, \
    49, 48, 47, 46, 45, 44, 43, 42, 41, 40, \
    39, 38, 37, 36, 35, 34, 33, 32, 31, 30, \
    29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \
    19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \
     9,  8,  7,  6,  5,  4,  3,  2,  1,  0)
&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define FUN_COUNT_GLUE(M,count)     M##count
#define FUN_JOIN_COUNT(M,count)     FUN_COUNT_GLUE(M,count)
#define FUN_JOIN_ARGS(x, y)     x y
&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define CallSomeOne(fn, ...)    FUN_JOIN_ARGS(FUN_JOIN_COUNT(fn, ARG_COUNT(__VA_ARGS__)), (__VA_ARGS__))
&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;///================= logger =====================&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;///  日志输出&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;///&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#if defined QS_LOG
#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&quot;QsLog.h&quot;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
#define PR QLOG_INFO() // QsLog 输出（一个用Qt封装的日志类，挺好用的，在此推荐一下）
#define ENDL &quot;&quot;
#elif defined QT_CORE_LIB  // Qt 标准输出
#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;QDebug&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
#define PR qDebug()
#define ENDL &quot;&quot;
#elif defined __cplusplus
#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;iostream&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define PR std::cout
#define ENDL std::endl
#endif
&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define OUT_RED     &quot;\033[0;31;1m&quot;
#define OUT_GREEN   &quot;\033[0;32;1m&quot;
#define OUT_YELLOW  &quot;\033[0;33;1m&quot;
#define OUT_BLUE    &quot;\033[0;34;1m&quot;
#define OUT_END     &quot;\033[0m&quot;
&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define FILE_INFO   &quot;[&quot; &amp;lt;&amp;lt; __FILE__ &amp;lt;&amp;lt; &apos;@&apos; &amp;lt;&amp;lt; __FUNCTION__ &amp;lt;&amp;lt; &apos;#&apos; &amp;lt;&amp;lt; __LINE__  &amp;lt;&amp;lt; &quot;]&quot;
&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define param1(a)               #a&quot;:&quot; &amp;lt;&amp;lt; a
#define param2(a,b)             #a&quot;:&quot; &amp;lt;&amp;lt; a &amp;lt;&amp;lt; &quot;, &quot;#b&quot;:&quot; &amp;lt;&amp;lt; b
#define param3(a,b,c)           #a&quot;:&quot; &amp;lt;&amp;lt; a &amp;lt;&amp;lt; &quot;, &quot;#b&quot;:&quot; &amp;lt;&amp;lt; b &amp;lt;&amp;lt; &quot;, &quot;#c&quot;:&quot; &amp;lt;&amp;lt; c
#define param4(a,b,c,d)         #a&quot;:&quot; &amp;lt;&amp;lt; a &amp;lt;&amp;lt; &quot;, &quot;#b&quot;:&quot; &amp;lt;&amp;lt; b &amp;lt;&amp;lt; &quot;, &quot;#c&quot;:&quot; &amp;lt;&amp;lt; c &amp;lt;&amp;lt; &quot;, &quot;#d&quot;:&quot; &amp;lt;&amp;lt; d
&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define pr0()           &quot;null param out&quot;
#define pr1(...)        param1(__VA_ARGS__)
#define pr2(...)        param2(__VA_ARGS__)
#define pr3(...)        param3(__VA_ARGS__)
#define pr4(...)        param4(__VA_ARGS__)
&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define pr5(a,b,c,d,e)              pr3(a,b,c) &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; param2(d,e)
#define pr6(a,b,c,d,e,f)            pr3(a,b,c) &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; param3(d,e,f)
#define pr7(a,b,c,d,e,f,g)          pr4(a,b,c,d) &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; param3(e,f,g)
#define pr8(a,b,c,d,e,f,g,h)        pr4(a,b,c,d) &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; param4(e,f,g,h)
#define pr9(a,b,c,d,e,f,g,h,i)      pr8(a,b,c,d,e,f,g,h) &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; param1(i)
#define pr10(a,b,c,d,e,f,g,h,i,j)   pr9(a,b,c,d,e,f,g,h,i) &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; param1(j)
&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//....  有兴趣大家可以继续扩充&lt;/span&gt;

&lt;span class=&quot;cp&quot;&gt;#define logStr(x)       PR &amp;lt;&amp;lt; FILE_INFO &amp;lt;&amp;lt; x &amp;lt;&amp;lt; ENDL  // 原样输出，无需格式化
&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define logDebug(...)   PR &amp;lt;&amp;lt; &quot;&quot;         &amp;lt;&amp;lt; &quot;DEBUG &quot; &amp;lt;&amp;lt; FILE_INFO &amp;lt;&amp;lt; CallSomeOne(pr, __VA_ARGS__) &amp;lt;&amp;lt; ENDL
#define logInfo(...)    PR &amp;lt;&amp;lt; OUT_GREEN  &amp;lt;&amp;lt; &quot;INFO  &quot; &amp;lt;&amp;lt; FILE_INFO &amp;lt;&amp;lt; CallSomeOne(pr, __VA_ARGS__) &amp;lt;&amp;lt; OUT_END &amp;lt;&amp;lt; ENDL
#define logWarn(...)    PR &amp;lt;&amp;lt; OUT_YELLOW &amp;lt;&amp;lt; &quot;WARN  &quot; &amp;lt;&amp;lt; FILE_INFO &amp;lt;&amp;lt; CallSomeOne(pr, __VA_ARGS__) &amp;lt;&amp;lt; OUT_END &amp;lt;&amp;lt; ENDL
#define logError(...)   PR &amp;lt;&amp;lt; OUT_RED    &amp;lt;&amp;lt; &quot;ERROR &quot; &amp;lt;&amp;lt; FILE_INFO &amp;lt;&amp;lt; CallSomeOne(pr, __VA_ARGS__) &amp;lt;&amp;lt; OUT_END &amp;lt;&amp;lt; ENDL
#endif // LOGGER_H
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;总结&quot;&gt;总结&lt;/h2&gt;

&lt;p&gt;由于受到工作方向的影响，有很多用法被局限在我们日常的工作方向中，不能很好的做出符合大家各自场景的东西。
然而我觉得学习，除了学到东西，更应该获取的是思维方式。
我的砖就抛到这里，希望对你们有用。
接下来的路就请各位小伙伴们自己走了！&lt;/p&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 扁平化管理，就是管理者的失职]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/pay/2020/06/29/01/</br>奉劝大家一句：千万不要想不开跑去小公司，尤其是在大厂已经做到高P的，即便小公司给你的许诺更好。因为在小公司呆久了你会发现只可能有两种变化，要么脾气会变的越来越暴躁，要么整个人变得越来越佛系。   - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2020-06-29]]></pubDate>
        <link><![CDATA[https://kymjs.com/pay/2020/06/29/01/]]></link>
        <tags>
          
          <tag>策划与管理</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>pay</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/pay/2020/06/29/01/</br>&lt;p&gt;奉劝大家一句：千万不要想不开跑去小公司，尤其是在大厂已经做到高P的，即便小公司给你的许诺更好。因为在小公司呆久了你会发现只可能有两种变化，要么脾气会变的越来越暴躁，要么整个人变得越来越佛系。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;本文为付费文章，订阅用户请访问小专栏查阅
&lt;a href=&quot;https://xiaozhuanlan.com/topic/9078324651&quot;&gt;https://xiaozhuanlan.com/topic/9078324651&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 再聊 Git Flow]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/manager/2020/05/29/01/</br>最近定了一些团队标准的东西，刚好聊到了 Git Flow 这个事，可以拿出来分享一下。   - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[https://cdn.kymjs.com:8843/qiniu/images/blog_image/2020052901.png]]></image>
        <pubDate><![CDATA[2020-05-29]]></pubDate>
        <link><![CDATA[https://kymjs.com/manager/2020/05/29/01/]]></link>
        <tags>
          
          <tag>策划与管理</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>manager</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/manager/2020/05/29/01/</br>&lt;p&gt;最近定了一些团队标准的东西，刚好聊到了 Git Flow 这个事，可以拿出来分享一下。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2020052901.png&quot; alt=&quot;Git Flow&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;分支应用情境&quot;&gt;分支应用情境&lt;/h3&gt;

&lt;p&gt;根据 Git Flow 的建议，主要的分支有 master、develop、hotfix、release 以及 feature 这五种分支，各种分支负责不同的功能。其中 Master 以及 Develop 这两个分支又被称作长期分支，因为他们会一直存活在整个 Git Flow 里，而其它的分支大多会因任务结束而被刪除。&lt;/p&gt;

&lt;h3 id=&quot;master-分支&quot;&gt;Master 分支&lt;/h3&gt;
&lt;p&gt;主要是用来放稳定、随时可上线的版本。这个分支的来源只能从别的分支合并过来，开发者不会直接 Commit 到这个分支。因为是稳定版本，所以通常也会在这个分支上的 Commit 上打上版本号 tag。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;每个版本发布完，develop 会合并到 master，并打tag&lt;/strong&gt;。&lt;/p&gt;

&lt;h3 id=&quot;develop-分支&quot;&gt;Develop 分支&lt;/h3&gt;
&lt;p&gt;这个分支主要是所有开发的基础分支，当要新增功能的时候，所有的 Feature 分支都是从这个分支切出去的。而 Feature 分支的功能完成后，也都会合并回来这个分支。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;开发过程中的稳定分支，develop分支应该保证每次最新的commit都是可以被run的&lt;/strong&gt;。&lt;/p&gt;

&lt;h3 id=&quot;hotfix-分支&quot;&gt;Hotfix 分支&lt;/h3&gt;
&lt;p&gt;当线上产品发生紧急问题的时候，会从 Master 分支开一个 Hotfix 分支出来进行修复，Hotfix 分支修复完成之后，会合并回 Master 分支，也同时会合并一份到 Develop 分支。&lt;/p&gt;

&lt;p&gt;为什么要合并回 Develop 分支？如果不这么做，等到时候 Develop 分支完成并且合并回 Master 分支的时候，那个问题就又再次出现了。&lt;/p&gt;

&lt;p&gt;那为什么一开始不从 Develop 分支切出来修？因为 Develop 分支的功能可能尚在开发中，这时候硬是要从这里切出去修再合并回 Master 分支，只会造成更大的灾难。&lt;/p&gt;

&lt;p&gt;其实在我们公司，hotfix 分支基本用不着，一方面是很少有紧急bug要修复，另一方面是Google Play，禁止动态下发。&lt;/p&gt;

&lt;h3 id=&quot;release-分支&quot;&gt;Release 分支&lt;/h3&gt;
&lt;p&gt;当认为 Develop 分支够成熟了，就可以把 Develop 分支合并到 Release 分支，在这边进行算是上线前的最后测试。测试完成后，Release 分支将会同时合并到 Master 以及 Develop 这两个分支上。 Master 分支是上线版本，而合并回 Develop 分支的目的，是因为可能在 Release 分支上还会测到并修正一些问题，所以需要跟 Develop 分支同步，免得之后的版本又再度出现同样的问题。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;其实正常的做法应该是提测，或测试到某个阶段以后使用。但是目前我们一般用在多版本并行开发的时候，充当特定版本的develop 分支使用&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;feature-分支&quot;&gt;Feature 分支&lt;/h3&gt;
&lt;p&gt;当要开始新增功能的时候，就是使用 Feature 分支的时候了。 Feature 分支都是从 Develop 分支来的，完成之后会再并回 Develop 分支。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;目前feature分支起名规则以 dev_ 开头，后面跟当前开发功能&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;分支提交与合并&quot;&gt;分支提交与合并&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;如果 feature 需要 develop 的功能，不要将 develop 分支 merge 到 feature 分支，应该使用 feature rebase develop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;分支提交使用 rebase 或 merge 方式都可以，一般来说commit 少的场景我会比较喜欢 rebase，但如果commit攒的多了，rebase 解决冲突会很累。&lt;/p&gt;

&lt;h3 id=&quot;rebase-操作&quot;&gt;rebase 操作：&lt;/h3&gt;

&lt;p&gt;例如，开始开发时，分支是这样的&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;* -- * -- A (develop)
           \
            * (dev_xxx)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;开发完成，准备提交时：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;* -- * -- A -- B -- C -- D -- E (develop)
           \
            * -- X -- Y -- Z (dev_xxx)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;命令行步骤如下（GUI参考命令行执行对应操作）&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git add &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;

git commit &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;xxx&quot;&lt;/span&gt;

git pull  //用于更新远端分支&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;git fetch 也可以&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

git rebase origin/develop //将当前分支base变为develop 的最新 commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这一步的实际原理是，git会从develop新开一个分支，将你当前的dev_xxx 分支的 commit 按照提交顺序挨个 cherry-pick 到新开分支上&lt;/p&gt;

&lt;p&gt;这一步执行过程中如果 develop 的 B C D E等commit 与 X Y Z 冲突，则需要依次解决冲突&lt;/p&gt;

&lt;p&gt;解决冲突以后，执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase --continue&lt;/code&gt;  或者 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase --skip&lt;/code&gt; 继续执行接下来的 rebase&lt;/p&gt;

&lt;p&gt;rebase 结束后，分支结构将变为：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;* -- * -- A -- B -- C -- D -- E (develop)
                               \
                                 -- * -- X -- Y -- Z (dev_xxx)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后需要执行&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git push &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; // 必须加 &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; 强制推送，否则由于本地分支的base与远端不一致，会报需要 git pull 无法提交
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;提交完成后去 gitlab 创建 merge request，走正常 review 流程，合并代码。&lt;/p&gt;

]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - Flutter 设置控件是否可见]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/note/2020/03/19/01/</br>共有两种实现比较简单的方式。  第一种比较好理解，将一个控件的透明度设置成0，打到隐藏的目的。第二种办法是使用 SDK 自带的 Offstage 控件包裹。   - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2020-03-19]]></pubDate>
        <link><![CDATA[https://kymjs.com/note/2020/03/19/01/]]></link>
        <tags>
          
        </tags>
        <columnNum><![CDATA[8]]></columnNum>
        <column><![CDATA[知识碎片]]></column>
        <categories>
          
          <category>note</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/note/2020/03/19/01/</br>&lt;p&gt;共有两种实现比较简单的方式&lt;/p&gt;

&lt;p&gt;第一种比较好理解，将一个控件的透明度设置成0，达到隐藏的目的。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;_HideAndShowPageState&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HideAndShowPage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visible&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@override&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Widget&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BuildContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Scaffold&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;appBar:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AppBar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;nl&quot;&gt;title:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;widget显示与隐藏&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;nl&quot;&gt;centerTitle:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;body:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ListView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;nl&quot;&gt;children:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Widget&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;[&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Padding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;nl&quot;&gt;padding:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EdgeInsets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;only&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;left:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;10.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;top:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;10.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;right:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;10.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
          &lt;span class=&quot;nl&quot;&gt;child:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RaisedButton&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
              &lt;span class=&quot;nl&quot;&gt;textColor:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Colors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;black&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;nl&quot;&gt;child:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;visible&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;隐藏B&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;显示A&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;隐藏A&lt;/span&gt;   &lt;span class=&quot;n&quot;&gt;显示B&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
              &lt;span class=&quot;nl&quot;&gt;onPressed:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;visible&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{});&lt;/span&gt;
              &lt;span class=&quot;o&quot;&gt;}),&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Padding&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;nl&quot;&gt;padding:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EdgeInsets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;only&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;left:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;10.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;top:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;10.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;right:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;10.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
          &lt;span class=&quot;nl&quot;&gt;child:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Stack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;nl&quot;&gt;children:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Widget&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;[&lt;/span&gt;
              &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;TestAWidget&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;nl&quot;&gt;visible:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
              &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;TestBWidget&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;nl&quot;&gt;visible:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;],&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TestAWidget&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StatelessWidget&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;TestAWidget&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Key&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;key:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@override&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Widget&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BuildContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AnimatedOpacity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;duration:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Duration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;milliseconds:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;opacity:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visible&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;child:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Container&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;nl&quot;&gt;color:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Colors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;blue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;nl&quot;&gt;height:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;100.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;nl&quot;&gt;child:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Center&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;child:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TestAWidget&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TestBWidget&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StatelessWidget&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;TestBWidget&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Key&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;key:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@override&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Widget&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BuildContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AnimatedOpacity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;duration:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Duration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;milliseconds:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;opacity:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visible&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;child:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Container&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;nl&quot;&gt;color:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Colors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;green&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;nl&quot;&gt;height:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;100.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;nl&quot;&gt;child:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Center&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;child:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TestBWidget&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;第二种办法是使用 SDK 自带的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Offstage&lt;/code&gt; 控件包裹。&lt;/p&gt;

&lt;p&gt;offstage的布局行为完全取决于 offstate 参数,offstage 默认为 true ,不显示;&lt;/p&gt;

&lt;p&gt;当 offstage 为 true,child 不会绘制到屏幕上,不会响应点击事件,也不会占用空间;
当 offstage 为 false,child 绘制到屏幕上;
注意,当 offstage 不可见,如果 child 有动画,应该手动停止动画, offstage 不会停止动画;&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TestCWidget&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StatelessWidget&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

 &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;TestCWidget&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Key&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;key:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

 &lt;span class=&quot;nd&quot;&gt;@override&lt;/span&gt;
 &lt;span class=&quot;nc&quot;&gt;Widget&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BuildContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Offstage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
     &lt;span class=&quot;nl&quot;&gt;offstage:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;nl&quot;&gt;child:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Container&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
       &lt;span class=&quot;nl&quot;&gt;color:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Colors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;orange&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;nl&quot;&gt;height:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;100.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;nl&quot;&gt;child:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Center&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
         &lt;span class=&quot;nl&quot;&gt;child:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TestCWidget&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
       &lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
     &lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - Flutter 线性布局：Column 和 Row]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/note/2020/03/18/01/</br>  Flutter 线性布局：Column 和 Row 的常用属性    - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2020-03-18]]></pubDate>
        <link><![CDATA[https://kymjs.com/note/2020/03/18/01/]]></link>
        <tags>
          
        </tags>
        <columnNum><![CDATA[8]]></columnNum>
        <column><![CDATA[知识碎片]]></column>
        <categories>
          
          <category>note</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/note/2020/03/18/01/</br>&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mainAxisSize&lt;/code&gt;：　　控制自己的布局方式&lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainAxisSize.min&lt;/code&gt;　　默认值，Column和Row自适应children；&lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainAxisSize.max&lt;/code&gt;　　Column填充父控件竖屏，Row填充父控件横屏；需要搭配&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainAxisAlignment&lt;/code&gt;使用才有效果；&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mainAxisAlignment&lt;/code&gt;：　　控制子集的对齐方式，Column上下对齐，Row左右对齐&lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainAxisAlignment.start&lt;/code&gt;　　默认值，Column靠上，Row靠左；&lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainAxisAlignment.center&lt;/code&gt;　　Column,Row居中；&lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainAxisAlignment.end&lt;/code&gt;　　Column靠下，Row靠右；&lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainAxisAlignment.spaceAround&lt;/code&gt;　　自己填充，等份分配空间给子集，子集各自居中对齐；&lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainAxisAlignment.spaceBetween&lt;/code&gt;　　自己填充，等份分配空间给子集，子集两侧对齐；&lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainAxisAlignment.spaceEvenly&lt;/code&gt;　　自己填充，等份分配空间给子集，子集同一中线居中对齐；&lt;br /&gt;
　　注：当设置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainAxisSize.max&lt;/code&gt;时才该值有效果。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crossAxisAlignment&lt;/code&gt;：　　　控制子集各自的对齐方式，Column左右对齐，Row上下对齐&lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossAxisAlignment.strech&lt;/code&gt;       　Column中会使子控件宽度调到最大，Row则使子控件高度调到最大&lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossAxisAlignment.start&lt;/code&gt;　　　   Column中会使子控件向左对齐，Row中会使子控件向上对齐&lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossAxisAlignment.center&lt;/code&gt;　　　默认值，子控件居中&lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossAxisAlignment.end&lt;/code&gt;　　　　Column中会使子控件向右对齐，Row中会使子控件向下对齐&lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossAxisAlignment.baseline&lt;/code&gt;　　按文本水平线对齐。与&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TextBaseline&lt;/code&gt;搭配使用&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;textBaseline&lt;/code&gt;：&lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TextBaseline.alphabetic&lt;/code&gt;　　  用于对齐字母字符底部的水平线。&lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TextBaseline.ideographic&lt;/code&gt;　　用于对齐表意字符的水平线。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;verticalDirection&lt;/code&gt;：　　控制子控件对齐方式是否相反方式  &lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VerticalDirection.down&lt;/code&gt;　　默认值，按照默认方式  &lt;br /&gt;
　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VerticalDirection.up&lt;/code&gt;　　　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossAxisAlignment.start&lt;/code&gt;跟&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossAxisAlignment.end&lt;/code&gt;对反&lt;/p&gt;

]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 开源一段 Mac 批量压缩图片的脚本]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/note/2020/03/17/01/</br>开源一个批量压缩图片的脚本，支持文件夹递归，可选质量压缩和宽高比压缩，可选设置最大宽高和大小，纯shell脚本实现。       - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2020-03-17]]></pubDate>
        <link><![CDATA[https://kymjs.com/note/2020/03/17/01/]]></link>
        <tags>
          
        </tags>
        <columnNum><![CDATA[8]]></columnNum>
        <column><![CDATA[知识碎片]]></column>
        <categories>
          
          <category>note</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/note/2020/03/17/01/</br>&lt;p&gt;近日处理了一批照片，现在分享一下如何在mac平台进行图片批量处理。&lt;/p&gt;

&lt;p&gt;0、安装 xCode 命令行工具，需要确定电脑上已经安装了 xCode，如果没有，自己去 AppStore 里面搜索就看到了。&lt;/p&gt;

&lt;p&gt;打开终端，输入命令：&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;xcode-select &lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果看到如下提示，表示已经安装了&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;xcode-select: error: &lt;span class=&quot;nb&quot;&gt;command &lt;/span&gt;line tools are already installed, use &lt;span class=&quot;s2&quot;&gt;&quot;Software Update&quot;&lt;/span&gt; to &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;updates  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;1、安装brew：&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/usr/bin/ruby &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Homebrew/install/master/install&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2、使用 brew 安装 imagemagick&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;imagemagick
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;3、 去桌面新建一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zip.sh&lt;/code&gt; 的文件，里面写入&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 查找目录及子目录的图片文件(jpg,gif,png)，将大于某值的图片进行压缩处理&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Config&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;folderPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# 图片目录路径&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;maxSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;200&apos;&lt;/span&gt;    &lt;span class=&quot;c&quot;&gt;# 图片尺寸允许值&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;maxWidth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;600   &lt;span class=&quot;c&quot;&gt;# 图片最大宽度&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;maxHeight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;500  &lt;span class=&quot;c&quot;&gt;# 图片最大高度&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;quality&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;60      &lt;span class=&quot;c&quot;&gt;# 图片质量&lt;/span&gt;


&lt;span class=&quot;c&quot;&gt;# 压缩处理&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Param $folderPath 图片目录&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;function &lt;/span&gt;compress&lt;span class=&quot;o&quot;&gt;(){&lt;/span&gt;

    &lt;span class=&quot;nv&quot;&gt;folderPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$folderPath&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then

        for &lt;/span&gt;file &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;find &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$folderPath&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-name&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;*.jpg&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-or&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-name&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;*.jpeg&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-or&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-name&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;*.gif&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-or&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-name&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;*.png&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\)&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-type&lt;/span&gt; f &lt;span class=&quot;nt&quot;&gt;-size&lt;/span&gt; +&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$maxSize&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do

            &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$file&lt;/span&gt;

            &lt;span class=&quot;c&quot;&gt;# 调用imagemagick resize图片&lt;/span&gt;
            &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;convert &lt;span class=&quot;nt&quot;&gt;-resize&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$maxWidth&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;x&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$maxHeight&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$file&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-quality&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$quality&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-colorspace&lt;/span&gt; sRGB &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$file&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;done

    else
        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$folderPath&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; 不存在&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 执行compress&lt;/span&gt;
compress &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$folderPath&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;0

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;4、 给脚本执行权限&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~/Desktop

&lt;span class=&quot;nb&quot;&gt;chmod &lt;/span&gt;a+x  zip.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;5、 可以压缩图片了，下面命令的path替换成你要压缩的图片文件夹路径&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~/Desktop
./zip.sh  path

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - Leader 的自我修养——学会预测]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/manager/2020/03/15/01/</br>用一句话概括，本文讲的就是：怎样通过自己的专业深度，帮助公司和团队预测未来。&lt;br&gt;很多时候，之所以能够做到预测未来，无非是你掌握的信息比别人要多而已。 - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[http://cdn.kymjs.com:8843/qiniu/images/blog_image/2020031501.jpeg]]></image>
        <pubDate><![CDATA[2020-03-15]]></pubDate>
        <link><![CDATA[https://kymjs.com/manager/2020/03/15/01/]]></link>
        <tags>
          
          <tag>策划与管理</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>manager</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/manager/2020/03/15/01/</br>&lt;p&gt;大概两个多月前的一次，跟朋友聊天，谈到了一些当 leader 的心路和历程，他说正好我很久没更新博客的，让我写下来，分享一波，我当时没放在心上，心想这种管理上的事情，各有各的方法，本身没有对和错，只有合适不合适，何况都是一些不成片的经历而已，适合互动交流，但很难提炼成文章。&lt;/p&gt;

&lt;p&gt;无独有偶，今年过年因为疫情在家，跟一个年前离职的同事聊天，他之前在我团队是个很不错的研发，现在也是在一个一线公司带小团队。他当时问了我同样的事情，让我讲了讲带团队的成长经验。&lt;/p&gt;

&lt;p&gt;我突然意识到，这些过往的经验如果稍微做一下抽象和总结，多少能给人一些启发。虽然我从很早开始做 Android 开发，真正带自己的团队也只是从两年前加入一条开始的。得益于职业生涯前几年的努力，自己技术累积了良好的口碑，恰好也是公司快速发展的需要，其实我相信，几乎全部的初当 leader 的同学一样，我们应该都算是 “码而优则仕”，又恰巧碰到一个机会，就这样走上了这条路。这两年，我们团队从5个人到20个人，学到的感受到的很大程度上可以总结为一些做事方法，其中主要的就是三点预判。&lt;/p&gt;

&lt;h2 id=&quot;预测业务&quot;&gt;预测业务&lt;/h2&gt;

&lt;p&gt;从技术研发初做 Leader，最大的变化就是工作内容的改变。以前需要重心放在代码上，思考怎样解决问题、了解实现原理，而现在变成了解业务，思考业务，预测业务。&lt;br /&gt;
这个过程是个非常痛苦的过程，甚至有点算一种悖论。不知道大家有没有发现，在最初我们做研发的时候，要去了解各种各样的框架，学习内部实现原理，再尝试自己去写一个类似的框架，再从这个过程中了解这项技术的本质。这其中最神奇的就是，大家都觉得做业务写需求是一件很傻的事，而做架构，搞技术预研则是一件很高大上的事，所以很多人觉得公司的架构组、基础组的人都很厉害，相对的垂直业务组的人则技术比较差。   &lt;br /&gt;
但是作为 Leader 往往是需要对公司的业务和组织架构有着深入的理解，并且能够通过产品经理提出的需求，对应的猜到这项需求上线后会对将来的业务干系方产生怎样的影响。&lt;br /&gt;
所以这两类人在很多公司都是被分割开的，比如阿里的P职级跟M职级，虽然有对应的转换，但大部分都是P职级做技术，M职级做管理。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2020031501.jpeg&quot; width=&quot;50%&quot; alt=&quot;一条&quot; /&gt;&lt;/p&gt;

&lt;p&gt;举个我之前经历过的例子，我们是电商公司，APP 的首页对布局的灵活性要求非常高，上面这张图就是我们 APP 的首页。&lt;br /&gt;
公司有一套 CMS 系统，就是专门应对这样的场景的。核心技术就是后端返回一个 JSON，前端根据 JSON 展示对应的布局和内容，这其实是行业很通用的做法了，天猫之前开源过一个叫 Tangram 的解决方案，就是应对这种动态化首页的场景的。但是这种界面，如果是纯展示后端返回的数据还好，只要稍微插入一些特殊逻辑，代码马上会变得混乱不堪。最好的办法是客户端在请求接口的时候就将用户信息都传过去，然后服务端根据业务对应返回。但现实中往往是做不到的，要么是后端需要的参数非常非常多，要么是数据由不同的系统传出，可能同时涉及到VIP信息、订单、位置、推荐、广告等等。所以就变成了服务端返回一个占位符、客户端再根据占位符请求对应的子接口。 &lt;br /&gt;
这种问题的解决就有两大思路，第一种技术层面的：就是通过良好的客户端架构设计，去充分考虑各种组件的情况，预留好应对方法。&lt;br /&gt;
第二种则是业务层面的：既然接口涉及到很多信息，那么能不能这些信息由一个专门去负责数据整合的中间层完成。&lt;br /&gt;
相比较而言，第一种改动点更小，只需要在自己工程代码中完成就可以了，第二种办法则需要充分了解公司的业务划分以及产品已定义的组件和将来可能的组件，找到相关的后端，再拉一个人或一个团队专门负责做接口整合的中间层，这时候第二种方案就对 Leader 的做事能力有着极大的挑战了。&lt;/p&gt;

&lt;h2 id=&quot;预测行业&quot;&gt;预测行业&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;移动端开发没人要了&lt;/strong&gt;。这种话作为段子我们不知道听到多少次了，事实上，每个公司都需要移动端的开发，只不过要求在不断变化而已。比如五年前，那时候创业的黄金时间，我们要的是能把业务快速做好上线的人；三年前，行业头部基本稳定，我们要的是能够把快速迭代那几年所欠下技术债还上的人；近几年，裁员不断，我们要的是能把公司成本降到最低，收益提到最高的人。理解了这一点，如何提高自己的价值，很简单，你能做公司需要而别人做不到的事，再帮助你的团队理解这一点，你就是公司最有价值的人。&lt;br /&gt;
比方说做 Android 的，近几年没有什么新技术，Kotlin、Flutter，没了。你要学吗？不一定！思考一下学完了以后对自己对公司有没有帮助，这才是重点。&lt;br /&gt;
想起来这样一个段子，说公司各种员工对老板的看法&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;员工&lt;/th&gt;
      &lt;th&gt;看法&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;普通员工&lt;/td&gt;
      &lt;td&gt;老板傻逼，什么都不懂&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;基层管理&lt;/td&gt;
      &lt;td&gt;老板有时候还是懂点的&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;高管&lt;/td&gt;
      &lt;td&gt;老板说的太对了，我怎么没想到&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;老板&lt;/td&gt;
      &lt;td&gt;我他妈太傻逼了，怎么没想到他这样&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;又是一年春天，招聘跳槽的黄金季节。没事多看看，了解一下现在其他公司的招聘要求，工作内容，细心一点你就能看出当前行业缺少的技术是什么；多跟你的上级聊一聊，打听一下公司的下一步大的战略方向是什么，你就又能提前准备一些事情。&lt;br /&gt;
很多时候，之所以能够做到预测未来，无非是你掌握的信息比别人要多而已。&lt;/p&gt;

&lt;h2 id=&quot;预测团队和项目&quot;&gt;预测团队和项目&lt;/h2&gt;

&lt;p&gt;如何找到自己团队或者项目的不足？&lt;br /&gt;
其中一个方法是直接问你的用户。就像我在文章开头时候提到的那个 CMS 系统的例子，如果你不是切实去做 CMS 系统的人，你根本不可能知道完成一个 CMS 组件有多复杂。 &lt;br /&gt;
预测团队是个很有意思的事情。他的有意思在于你很容易先入为主，去猜测某些可能会发生的事情，然后某个机缘巧合，这件事情发生了，会让你觉得，其实你早就知道这件事会发生了。比如你想知道自己团队的不足，你去找团队的人聊天，大概率会得到的答案是那个你心中已经有的答案。而这背后，并不仅仅是大家的想法都一样，事实上更多的是因为你很容易只听到那些你想听到的事。  所以得出的结论经常是前端说的后端技术太差，返回的东西乱七八糟，有用的数据没有，没用的数据一堆；后端说前端水平太差，几个字段自己拼一下就能知道数据了，什么都让我返回，这个逻辑我没法写。&lt;br /&gt;
之前在沪江的时候，我们 team 里有一句话叫：&lt;strong&gt;当一个事情发生第一次的时候，他就有可能发生第二次，当他发生了第二次的时候，就一定会发生第三次。&lt;/strong&gt;&lt;br /&gt;
像这种推脱，很难直接去解决，因为前端没发理解后端的难点，后端也不知道前端的复杂。但一个常常被忽略的重点是，他们至少能表达出自己觉得差的地方。当多个人都觉得某个东西很差的时候，那可能真的是他有问题了，需要考虑如何解决掉这个问题或者避免下次继续发生。&lt;/p&gt;

&lt;h2 id=&quot;team-的成长才是-leader-的绩效&quot;&gt;team 的成长，才是 Leader 的绩效&lt;/h2&gt;

&lt;p&gt;之前看过一部电视剧，叫《大江大河》。讲的是文化大革命刚结束的时候，男主是个大学生被分配到国企化工厂上班，因为能力出众，当上了小组长，带两个厂子弟一起做事。&lt;br /&gt;
当时他就跟上级说了一句话：他们两个做事效率实在太低，与其让他们两个人去做，还不如我一个人直接做完了。&lt;br /&gt;
我相信这是很多人刚当上 leader 的第一感受，我当时也是这样，很多事情还不如自己加个班去解决掉，也比交给别人拖两三天才完成要好。 &lt;br /&gt;
但事实上这样的后果就是：现在你只带两个人，有些事可以帮他们完成，将来将一个厂的人都交给你的时候，你要如何去帮他们完成呢？&lt;/p&gt;

&lt;p&gt;我们 CTO 每次都说 Leader 要懂得将自己的能力复制给组员。让 Leader 的能力变得可复制，这才是公司最大的财富。 &lt;br /&gt;
而懂得将预测未来的能力交给组员，这才是提升的最快办法，比很多傻逼公司强制996要好太多倍了。&lt;br /&gt;
当然，懂得跟上级形成定期的汇报机制，小事用钉钉微信，重要决策或进展当面或者日报。掌握足够的信息才是预测未来的重要依据，同时也是向上管理的必要条件，这也是我们CTO教我的。&lt;/p&gt;

&lt;p&gt;你看，我们CTO自己也需要向上管理。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2020031502.png&quot; width=&quot;50%&quot; alt=&quot;一条&quot; /&gt;&lt;/p&gt;
]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 玩玩区块链——概念]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/code/2019/11/21/01/</br>继我成功预言了 Kotlin 、Flutter 技术会火起来之后，我相信，下一个会火起来的技术是区块链。&lt;br&gt; 1. 别抬杠，我 14 年开始玩 Kotlin，16 年开始玩 Flutter (虽然看了看实现原理觉得没啥屁用就放弃了)的时候，可能你还不知道这俩名词呢。&lt;br&gt; 2. 别抬杠，我说的火起来的区块链技术是私链+合约(我不想提那俩字，我觉得智障更合适)。&lt;br&gt; 3. 抬杠吧，反正我从来没玩过数字货币，我也不怕被割韭菜。&lt;br&gt; - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2019-11-21]]></pubDate>
        <link><![CDATA[https://kymjs.com/code/2019/11/21/01/]]></link>
        <tags>
          
          <tag>区块链</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>code</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/code/2019/11/21/01/</br>&lt;h2 id=&quot;讲个故事&quot;&gt;讲个故事&lt;/h2&gt;
&lt;p&gt;这个故事是 2016 年 MDCC 大会结束一起聊天的时候，听&lt;strong&gt;冯森林&lt;/strong&gt;冯老师讲的。背景是：前几天有个人问他，说冯老师我很迷茫，前端技术天天变，而真正重要的东西都在后端，我该不该去学后端。 &lt;br /&gt;
在很早很早之前，程序员就是程序员，不分什么前端后端客户端，后来是我们人为的在中间画了一条线，规定线的左边叫后端，线的右边叫前端。在左边与右边的信息交互，我们用一种协议去完成。&lt;br /&gt;
而事实上，在当今世界跟用户打交道的都是前端，离开了用户，后端终究只是一堆冷冰冰的数字，而随着人工智能与物联网的发展，我们是有可能讲这些冷冰冰的数字计算，放在端上完成的。&lt;/p&gt;

&lt;p&gt;具体的原话我已经忘了，但大概意思就是这些。虽然哪怕 5G 已经到来，我也不太相信人工智能跟物联网能解决这种问题，但我隐隐觉得区块链跟智能合约能做到类似的实现。&lt;/p&gt;

&lt;p&gt;反正信不信由你，连潘木匠都开始跨界玩 Python 了，我玩会区块链不行吗？  😂😂😂&lt;/p&gt;

&lt;h2 id=&quot;比特币&quot;&gt;比特币&lt;/h2&gt;

&lt;h4 id=&quot;分布式数据库&quot;&gt;分布式数据库&lt;/h4&gt;

&lt;p&gt;用我们平时最常用的数据库 MySQL 举例，你在自己服务器上装一个 MySQL 就是一个中央存储器。他面临的问题就是扩展麻烦，一不小心哪里崩了，整个数据都不能用了。于是就有人做出了分布式数据库，就是多个服务器，每个服务器存储一部分。中央存储与分布式就类似数组与链表(或者树)的关系，但是面临的问题就是，链表的每个节点是有可能被篡改的，比如有人破解了你服务器，获取了 root 权限，那还不上去随便改你数据库。&lt;/p&gt;

&lt;h4 id=&quot;区块链&quot;&gt;区块链&lt;/h4&gt;

&lt;p&gt;区块链也是一个分布式数据库，他的每个区块，也就对应了上面讲的链表的一个节点，而多个区块组合起来，才是一个区块链。拿比特币举例子，他每个区块实际上存储了多笔交易的信息，每个正在挖矿的矿工(其实就是一个存储机器)手里都会有一份完整的全量数据。这时候如果有一个人的数据与其他人不一样，那肯定他的数据是有问题的。这也就是为什么我们总说区块链是不可篡改也不会丢失的了（随着技术迭代，现在有更多的链选择节点搭配的形式，不再所有人都记录全部数据了）。&lt;/p&gt;

&lt;h4 id=&quot;区块内防篡改&quot;&gt;区块内防篡改&lt;/h4&gt;

&lt;p&gt;前面讲了区块间防篡改其实就是少数服从多数的方式，接下来再看看区块内如何防篡改的，还是用比特币的防篡改来举例。比特币每个区块的结构如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/2019111801.png&quot; alt=&quot;开源实验室&quot; /&gt;&lt;/p&gt;

&lt;p&gt;核心结构是由一种称之为默克尔树的二叉树实现的。当前区块的全部交易信息位于叶子节点，然后每两个相邻的叶子节点聚合进行Hash运算，运算结果作为中间节点，中间节点再次两两聚合进行 Hash，最终生成唯一的根节点 Merkle Root。默克尔树的特性在于，任意叶子节点发生改变，改变都会影响到上层的 Hash 运算结果，最终经过层层传递，根节点的 Hash 值也将发生改变，基于此特性，比特币系统牢牢的将区块内的所有交易聚合成为一个整体，任何的篡改行为都会影响到整个区块。 &lt;br /&gt;
而比特币每次有区块新增的时候，其实都会计算一下默克尔树，但这段计算其实是很快的而真正耗时的，其实是一段没有任何意义的计算。（这也就是有人说比特币浪费能源的原因）比特币会每隔一段时间，生成一个区块，但是为了保证生成区块的时间稳定，就强制要求所有挖矿的矿工(存储机器)不停的去运算一个很复杂的运算，而这个运算的复杂度是动态变化的，整个系统能保证每次运算出结果的时间都差不多10分钟。
第一个运算出结果的人，要把这个结果告诉所有正在计算的矿工(其他存储机器)，这样，他就算是成功记录了新的区块，而别的矿工就会同步他记录的这个区块，作为最先运算出结果的矿工，也会获得奖励(也就是比特币)。 &lt;br /&gt;
所以，看出来了吧，本来是用来存储的机器，结果最大的代价居然不是存储，而是不停的计算一个毫无意义的运算，这就是个笑话。 &lt;br /&gt;
(扩展知识：双花问题、51%攻击，自行搜索)&lt;/p&gt;

&lt;h2 id=&quot;各种空气币&quot;&gt;各种空气币&lt;/h2&gt;

&lt;p&gt;比特币被称为区块链1.0，很多人希望把区块链的技术应用到数字货币之外的场景，然而，比特币本身的功能单一，对用户来讲，无非是挖矿、转账交易，于是就有了区块链2.0，以太坊。&lt;br /&gt;
前面已经说了，比特币计算一个区块是十分钟左右，而以太坊计算一个区块是十几秒，这就大大提高了数据同步的速度，也就是说交易确认将会更快（垃圾，再快能比微信支付宝快吗😂）。&lt;br /&gt;
然而，以太坊的成功并不是他的交易速度。比特币有一个很大的问题，就是其中协议的扩展性是一项不足，例如比特币网络里只有一种”token”——比特币，用户无法自定义另外的属性 token，这些 token 可以是代表公司的股票，或者是债务凭证等，这就损失了一些功能。另外，比特币协议里使用了一套基于堆栈的脚本语言，这语言虽然具有一定灵活性，使得像多重签名这样的功能得以实现，然而却不足以构建更高级的应用，例如去中心化交易所等。以太坊从设计上就是为了解决比特币扩展性不足的问题。&lt;/p&gt;

&lt;h4 id=&quot;安全可信&quot;&gt;安全可信&lt;/h4&gt;

&lt;p&gt;如果仅仅是用来发币，国家可能早就把区块链给封了。以太坊真正解决的，是共识问题，这也是为什么国家重视区块链的原因。前面我们讲，比特币给每个矿工存储数据的回报是比特币，而以太坊也有自己对应的报酬叫以太币。以太坊英文叫 Ethereum，对应的币叫 ETH。但是以太坊统一了所有基于以太坊定制的各种定制链的 token，即(Token 标准协议)。 而这里说的各种定制链，其实就是合约，这是以太坊的核心。合约是一个活在以太坊系统里的自动代理人，他有一个自己对应的 eth 地址，当用户向合约的地址里发送一笔交易后(其实就是调用一个函数)，该合约的对应回调函数就被调用，而交易是可以附带额外信息的，然后合约再根据交易中的额外信息处理自己的逻辑，最后返回一个响应，这个响应可能是从合约的地址发出另外一笔交易(触发另一个调用)。这样任何一个基于以太坊定制的 token，都是可以被互相兑换的。看到没有，交易所也出来了。&lt;/p&gt;

&lt;h2 id=&quot;共识机制&quot;&gt;共识机制&lt;/h2&gt;

&lt;h4 id=&quot;pow&quot;&gt;POW&lt;/h4&gt;

&lt;p&gt;虽然以太坊相比比特币的内部实现已经好了很多了，但依然没有从根本改变比特币那个笑话——本来是用来存储的机器，结果最大的代价居然不是存储，而是不停的计算一个毫无意义的运算。 &lt;br /&gt;
而这个笑话有一个专业的名词叫做 POW(Proof Of Work)。&lt;br /&gt;
这是机制的问题，因为如果一个存储数据的机器，想要证明自己记录的数据是准确的，还要让别人相信自己的数据是准确的，他不得不想办法证明自己是准确的(类似于证明自己是自己)。而证明自己的过程，就是他把自己的数据求一遍 hash，然后吧 hash 结果告诉别的同样在记录数据(挖矿)的机器，然后别的机器去用你的 hash 结果验证他们的数据，如果发现一致的话，那他就相信你的数据是对的(因为跟自己的结果是一致的)，然后当系统里面相信这个数据是对的机器越来越多，这个结果自然而然就成了正确的结果。 &lt;br /&gt;
这个验证的过程是一个很特殊的算法，求 hash 的时候是非常复杂耗时的操作，而验证 hash 正确性的时候，是一个非常简单的操作。举个例子：比如我给出一个 md5 值，让你去算哪一段文本的 md5 值跟他是一样的。你可能唯一的办法就是自己不停的穷举所有文本的 md5，去找出一段文本的 md5 是不是跟我给出的一样，而如果此时有人给出一段文本，说这段文本跟给定的 md5 是一样的，你很容易就能验证他说的对不对。&lt;br /&gt;
因为这种机制问题，POW 不得不花大量的资源用于计算 hash 上，而不是原本存储的目的，从而造成了很大的资源浪费(毕竟计算机算hash费电啊)。&lt;/p&gt;

&lt;h4 id=&quot;pos&quot;&gt;POS&lt;/h4&gt;

&lt;p&gt;POW 的本质目的是为了保证数据是可靠的。优点是公平而且不容易出错，缺点自然就是浪费资源。所以有人提出了一种新的方案，也可以用来保证数据的可靠性，同时还节约资源，就是 POS(Proof of Stake) &lt;br /&gt;
POS 机制实际上是模拟了人类社会的信任过程。就好像中国古代动不动就找一个德高望重的老人家来主持公道一样，POS 就是找出一个德高望重的人，让他来负责记录数据，那我们就都相信他的数据是准确的。而德高望重，实际上就是一个根据你持有货币的量和时间来决定的。持有的时间越长，币越多，你你产出的新区块被认可的概率也就越大。&lt;br /&gt;
而怎么算你持有币呢，用上面讲的以太坊举例(注:以太坊对外宣称将来会改为POS机制，但截止目前，他依然是POW)，你的每一个币其实都是放在一个数字钱包里面的，你说你持有币，其实你持有的只是数字而已，而数字钱包实际上就是一个记录数据的机器，这台机器是拥有记录数据能力的，机器拥有(钱包中)的币越多，新记录的数据越能得到认可。&lt;br /&gt;
但是，不知道你发现了没有，这又是一个套路。他已经从原本依赖机器去计算改为依赖币的量了，换句话说就是骗你去持有更多的币。你持有的币越多，你就越有可能让别人相信你的数据是正确的，而如果你让别人相信了一个新区块是正确的，系统会额外分你一些奖励(类似挖矿所得)。&lt;br /&gt;
于是就有人去吸引你把币放我这里，我去记录数据，因为币越多，放的时间越长，我发现新区块的概率也就越高，获得系统奖励的概率也越高，到时候再分你一部分奖励，这就是银行啊。&lt;/p&gt;

&lt;h4 id=&quot;dpos&quot;&gt;DPOS&lt;/h4&gt;

&lt;p&gt;POS 已经从很大程度上解决了资源浪费的问题，毕竟他基本上还原了分布式存储的本质，每台机器的主要作用是存储数据，而不是计算。&lt;br /&gt;
但 POS 实际上依然在每台机器上都还是有可能会有计算的，也就是那个根据持有币的时间和数量的概率算法。 &lt;br /&gt;
而 DPOS(Delegated Proof of Stake) 是 POS 的进一步优化，他从全部的机器中选出几台机器来指定，只有这些机器可以轮流记录新区块，别的机器都只负责同步区块。这就真正还原了分布式数据库的作用——只用于记录数据。&lt;br /&gt;
但又引入了新的问题，这些有记录新数据权力的机器，如何指定。于是又引入了一个人类社会的信任方式——投票。&lt;br /&gt;
而这里的票，也就是你手里的币，你把你的币给别人，这就是给别人投票。但是没有人会傻到把币免费给别人，想要我的币，拿东西来换啊(类似于拉票)。&lt;br /&gt;
但是你发现没，号称去中心化的区块链，居然又变得中心化了，傻逼区块链。&lt;/p&gt;

]]></content>
    </item>
    
    <item>
        <title><![CDATA[张涛 - 一条电商 Android 工程化实践]]></title>
        <description><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://kymjs.com/session/2019/11/02/01/</br>这篇文章是我在 2019【极光开发者大会】技术分享时所讲内容的文字版本，修改删减了演讲时的冗余言语，现免费开放给大家阅读， 希望能给买不到票参加大会的开源实验室读者带来帮助。 - 开源实验室]]></description>
        <recommend></recommend>
        <image><![CDATA[]]></image>
        <pubDate><![CDATA[2019-11-02]]></pubDate>
        <link><![CDATA[https://kymjs.com/session/2019/11/02/01/]]></link>
        <tags>
          
          <tag>Android</tag>
          
        </tags>
        <columnNum><![CDATA[]]></columnNum>
        <column><![CDATA[]]></column>
        <categories>
          
          <category>session</category>
          
        </categories>
        <content><![CDATA[本文开源实验室原创，转载请以链接形式注明：https://www.kymjs.com/session/2019/11/02/01/</br>&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.001.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;大家好，今天跟大家分享的主题是：从小作坊到大工厂，一条电商的 Android 工程化开发实践。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.002.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;我是张涛，就职于一条生活馆，我们公司是一家电商新零售公司，目前移动端的一些开发管理工作都是由我在负责。
对今天的分享有哪些值得探讨的也都欢迎与我交流&lt;/p&gt;

&lt;p&gt;Android 作为一个诞生近十一年的移动操作系统，占据了近 80% 的市场份额。&lt;br /&gt;
我相信在座的很多朋友，都是从2013-2015年开始从事 Android 开发的，可能晚一点的有2016年的。这也正是 Android 生态发展最快速的3年，如今我们再回过头来看一下这几年。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.004.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;这是我们曾经的 APP，携程、美团、京东、淘宝，这是我在网上找的四张图片，时间都是在2014年2015年的，主要是再久一点的也找不到了。&lt;br /&gt;
从前我们的应用，这四个应该是很典型了的，因为他们栏目多，业务也多。可以看到，携程四行三列，其他的：美团、京东、淘宝，都是两行四列。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.005.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;你们都知道，接下来我要放现在的了：这是我最近才从我手机上截的四张图，依然是携程、美团、京东、淘宝。 &lt;br /&gt;
在这四五年里，互联网公司大幅增加，而这样的老牌互联网公司，新业务也不断增多，我们看到，携程的：WiFi 电话卡、保险签证、以及上面主业务细分更加精细。&lt;br /&gt;
美团那就更是如此了，骑单车、叫外卖、美团打车、美团买菜，相信大家都是有所感受的。&lt;br /&gt;
再往后京东淘宝我就不用说了，大家也都看得到。&lt;br /&gt;
前面从业务角度回顾了我们这几年APP的发展，接下来我们从技术架构角度看一下现代的APP&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.006.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;这张图，是我们一条生活馆的移动架构图，这张图应该可以覆盖如今百分之九十 APP 的架构模型。最上层是业务层，常见的 APP 各个业务模块，比如我们一条生活馆的最重要的几个业务，CMS首页、内容相关模块、电商的商品购物、还有拍卖等等。&lt;br /&gt;
第二层，是服务于业务的通用业务层，最常见的浏览器web容器、路由功能。UI 统一、我们的 APP 越做越大，可能一个 APP 由多个设计团队去负责，那设计就需要有一个统一的控件风格。广告，各种闪屏页、内插页弹窗、小红点等等。还有 RN、Weex、Flutter这种多端共用业务。&lt;br /&gt;
最下层，是一些基础服务。这里我列出来了8种，大家可以发现一个特点就是这8种都是与用户无关的服务，比如数据上报、异常统计，我作为一个普通的用户完全不在乎我用的应用是否有异常统计，他不会影响我的使用。&lt;br /&gt;
侧边是一个贯穿我们整个应用的综合性技术，像 Kotlin 的 Coroutine(协程)、Google 新推出的类似 SwiftUI 的 Jetpack Compose、模块化、国际化、插件化等等，都是改动一块，整个应用可能都会发生变动的技术点。 &lt;br /&gt;
我们如今的应用已经到了如此惊人的一个庞大体量，但是……&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.007.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;回到我们今天的主题：如何完成作坊到工厂的转变？把上面那些技术全都用一遍吗？哪怕你说插件化 Kotlin 都不适合我们，我找出适合我们的技术都用上，就是大工厂了吗？&lt;br /&gt;
不是的，至少我认为不是的。&lt;br /&gt;
自从我投身到创业公司以后，经常有人问我大公司和小公司的差别是什么。
在我看来程序员能力的差距都不是最大的问题，当然这是前端，后端可能会因为性能问题造成比较多的损失噢。&lt;br /&gt;
在我看来最大的差距就是在基础设施建设上。小公司往往会因为老板的眼界、公司资源等各种各样的原因，缺失&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.008.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;一套成体系的工程化平台  &lt;br /&gt;
那么何谓工程化平台，就是通过标准化流程的方式，去做的能够批量生产的能力。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.009.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;比如说我们前面介绍的一个应用有多个功能模块，包含基础模块和业务模块，这也是近几年被喊的很火的移动端模块化技术。&lt;br /&gt;
第二点，数据驱动模块重组，我们为什么要让这些模块独立是如何选择的。&lt;br /&gt;
第三点，我们的研发做好了各个需求，这些需求要如何测试如何上线。&lt;br /&gt;
当然，一个前端工程化平台不止这三点能力，比如线上的APM、日志监控等等，只是这三点是我认为作为一个作坊到工厂转变必不可少的三点。&lt;br /&gt;
接下来我们从应用架构的角度去一个一个的详细聊聊，首先是业务模块独立。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.011.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;无论任何一个应用，想要做到模块化拆分，都必不可少要面临这三个问题：跨模块函数调用、跨模块界面跳转、跨模块消息传递。
当然，如果这是一个业界常见问题，一定会有开源的解决方案，比如前面两个，我们可以通过阿里开源的ARouter解决，后一个我相信做Android的没有人不知道EventBus。但是这两个框架在我们真正用起来的时候，都会碰上一些问题。比如ARouter，他从一开始设计就是为了淘宝这样大型的APP去做的，他在应用首次启动的时候要把所有类遍历一遍而这个时间是非常长的，即便是通过分组懒加载也依然很慢。&lt;br /&gt;
当然还要一个更重要的原因，是因为我们在ARouter开源之前，就已经有了一套完整的路由框架，所以就没有再使用ARouter了。
另一个EventBus也是订阅者模式都会遇到的问题，就是消息泛滥的情况。当应用内消息过多的时候，会造成消息难以追踪，调试问题的复杂度也随之增大了。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.012.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;YitBridge 使用了与后端 SOA 设计思路类似的方式：将模块之间的主动依赖倒置，变为功能的提供与使用。
那什么是 SOA 的设计思路呢，我们看到一张我画的漫画图：SOA 它是一种面向服务的架构模型。 &lt;br /&gt;
例如图上左边有一个对外提供媒体功能的服务提供者，他告知YitBridge我提供媒体服务：“嘿，老铁，我这有个媒体服务，你那边有谁要用的时候可以用我的。”&lt;br /&gt;
到了另一边，如果此刻有模块说是，我需要媒体服务：“老铁，你那有没有媒体服务，我这边需要播一个铃声啊！”。&lt;br /&gt;
“有的，给你。”&lt;br /&gt;
YitBridge 就会将之前服务提供者与服务使用者做一个桥接完成服务的调用。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.013.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;接下来我们来看具体到代码上是如何使用的：首先是作为服务使用方，也就是上一张图右半部分的Client。我们看到上面是传统的做法，首先声明一个借口类型，然后new出接口的实现类给他赋值。而使用了YitBridge的时候，你是不需要关心接口的实现类到底是谁的。这就是YitBridge唯一的用处，隐藏实现类，做到彻底的面相接口编程。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.014.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;之前说过，YitBridge将模块之间依赖倒置，由之前的服务提供方被动的接受调用方调用变为，服务方主动提供服务给调用方。那作为服务提供方需要做些什么事呢，非常简单，你只需要给你的对象提供方法加上一个@Creator注解，告诉YitBridge这是一个创建器方法就可以了。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.015.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;前面讲YitBridge实现完美实现了模块间的解耦，而YitBridge的内部实现就是通过这个APT来完成的。Annotation Processing Tool，相信大家都有听说过这个APT，即便是你没听说过，你也肯定早就用过了，只是你不知道。  Android 上有一个注明的注解绑定框架叫 Butterknife，他的内部实现就是通过APT来做的，还有 Google 出品的 Dtabinding、Dagger2，这些也都是APT来做的。那这个注解处理器在 YitBridge 中做了些什么呢，我们来看这段代码。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.016.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;这是 YitBridge 在编译以后生成的一段类：还是继续我们前面讲的那段示例来继续，在运行时，他会根据你传入的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get()&lt;/code&gt; 方法的参数，来判断你所需要的是哪个接口的实现，然后去调用对应的创建器方法。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.017.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;那么除了这个问题，还有就是多版本协作开发时候的模块依赖问题。
Android 使用的是 Gradle 构建，如果出现基础接口更新，上层所有依赖都跟着重新更改一下，再做一次兼容。这种事本来是无可避免的，但是如果我们有多个版本同时开发，这个问题就大大提升了版本管理的复杂度问题。所以我们引入了一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;baseVersion&lt;/code&gt; 的概念，也就是你当前模块构建的目标版本号。只要依赖的模块的目标版本号一致，就可以不用考虑兼容性问题直接更新，如果&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;baseVersion&lt;/code&gt; 不一致，那说明这是一次不兼容升级，需要上层模块处理完兼容性问题后再发布。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.018.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;这就是前面 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moduleApi&lt;/code&gt;的实现，其实就是判断了一下&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;baseVersion&lt;/code&gt;是否一致，如果一致，那么就直接引用最新版本，如果不一致，则抛异常退出编译。从而将运行时的错误在编译期就直接发现。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.019.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;第二部分，数据驱动模块重组&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.020.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;数据这里，我能跟大家聊的不多。&lt;br /&gt;
第一个，RMF模型。&lt;br /&gt;
RFM 模型是电商衡量用户价值和用户创利能力的一个重要数据，分别指三个纬度：最近一次消费 (Recency)、消费频率 (Frequency)、消费金额 (Monetary)。从这三个维度综合评分最终就可以量化出一个用户的价值，那么这个价值就可以辅助我们在线下建立线下店、用户社群等等数据化运营了。&lt;/p&gt;

&lt;p&gt;第二个，商品曝光与转化统计。&lt;br /&gt;
也是电商公司会经常使用的统计商品转化率的方式，比如一个商品他在用户手机上被展示了多少次，展示了以后用户点击了多少次，这些点击又有多少是收藏或加入购物车了的，以及最终下单购买等等一系列流程，最终就可以分析出每个商品的转化率，慢慢的我们肯定会优先选择跟转化率高的供应商合作。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.022.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;第三部分，标准化持续集成与交付&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.023.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;这里是我们目前使用的模块依赖构建工具，大家可以从这张图中感受一下。
构建工具，主要的功能是很明显的，就是用于构建模块，在这之上，还有隐含的功能，就是集中了构建模块的权限，可以更便于统一管理；
当然还有最重要的优势就在于模块版本的管理，你可以很清晰的知道当前主应用所接入的模块的版本是哪个，当前最新构建的SNAPSHOT是哪个，以及每个版本的更新日志；
这样做了以后，在跨团队协作上的沟通就大大降低了，如果你已经接入或者即将接入的模块是另一个团队开发的模块组件，那你可以直接关注它，它的所有版本变动日志，最新版本全都一目了然；
并且可以通过平台简化模块的测试与模块发布的流程，比如提测的时候，如果是一次兼容版本的发布，你只需要告诉测试提测分支，测试可以自己根据现在线上应用的tag，同时引入当前提测的模块替换老版本的模块重新编译，很容易就能控制变量。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.024.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;第二个就是，通过平台化的构建工具，可以很方便的动态选择模块依赖，比如我们在测试某个模块的时候，可以排除部分已经保证没有问题的模块，那么在测试的时候就可以更聚焦，让测试只看我们发生改动了的模块，最终集成测试只需要简单看一下就行了。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.025.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;接下来我们再看看发布效率。最早我们的代码，是非常严格的 N 周一个迭代的发布。但是随着业务越来越多，团队也越来越大。很多时候不得不 delay 一两天来照顾到所有的业务团队。&lt;br /&gt;
之后，就有了我们模块化解耦，每个业务按照自己的迭代排期计划好，如果 delay 了那么这个版本你们的新需求就得放到下个版本再发布了，这样主 APP 的发版不再依赖哪个业务线，同时业务组件可以提前打包，这也加快了打包时所用的时间。&lt;br /&gt;
再到最后，通过用户画像精准推送。我们可以做到代码随时发版，只要你认为你的需求做完了，测试通过了，就可以发布 APP。但是这个版本并不一定是每个用户都会接收到，而是我们提前已经分析过用户行为，那些愿意主动使用最新应用的用户才会收到应用更新。大家打开手机里面的微信、QQ 等等，点一下检查更新看看，基本上都是提示已经是最新版本了，但你用的却不一定是最新的版本，你可以去官网再自己下载一个 apk 装上看看版本号。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.026.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;在之后就是我前面提过的，工厂化发布一个APP。我们可以根据业务需要任意组装业务模块，最后拼成一个APP，快速发布到线上，查看这个应用的数据反馈，如果这个应用从数据层面看运行的非常好，那么就可以考虑引导流量到这个应用。如果数据不好，那么尽早放弃降低研发成本，准备其他的业务也更灵活。&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;img src=&quot;https://cdn.kymjs.com:8843/qiniu/images/blog_image/jiguangdahui.027.jpeg&quot; width=&quot;80%&quot; alt=&quot;从小作坊到大工厂，一条电商的 Android 工程化开发实践&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
]]></content>
    </item>
    
  </channel>
</rss>
