<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://jessyan.me/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jessyan.me/" rel="alternate" type="text/html" hreflang="zh-CN" /><updated>2026-08-27T10:19:22+08:00</updated><id>https://jessyan.me/feed.xml</id><title type="html">JessYan</title><subtitle>工程实践、架构设计与开源。MVPArms、AndroidAutoSize、RetrofitUrlManager 等开源项目作者。</subtitle><entry><title type="html">为什么进步太慢，因为你没有一个好习惯</title><link href="https://jessyan.me/habit/" rel="alternate" type="text/html" title="为什么进步太慢，因为你没有一个好习惯" /><published>2019-03-14T00:00:00+08:00</published><updated>2019-03-14T00:00:00+08:00</updated><id>https://jessyan.me/habit</id><content type="html" xml:base="https://jessyan.me/habit/"><![CDATA[<p>有人问我如何做好架构设计？怎样灵活运用设计模式？我的回答是，你做不好这些只是因为你没有养成一个良好的编程习惯</p>

<p>我为什么写这么多开源框架，还长期保持维护？</p>

<p>除了我想让更多人受益于开源外，还有一点就是，我想保持我良好的编程习惯</p>

<p>写业务代码也可以保持良好的编程习惯啊</p>

<p>能，但是太慢！</p>

<h2 id="写业务时无法注意到的细节">写业务时无法注意到的细节</h2>

<p>在写业务代码时，即使项目时间充裕，你也会忽略掉很多细节，而这些细节正是影响你进步速度的关键，但你自己却很难察觉，在不知不觉间就对你的进阶之路造成了很大的影响</p>

<p>所以你的进步速度非常之慢，做了多年也只是熟练而已，无法更进一步</p>

<p>业务代码和框架代码最直观的差别就是，业务代码可以随意修改，而框架代码，使用者不能直接修改</p>

<p>由于业务代码的修改不受限制，所以一有需求变动，你就直接通过修改问题代码的方式来快速解决问题，即使这个代码的变动非常频繁，但你的第一反应却不是通过代码设计的方式来解决后续的扩展问题，因为这看似一劳永逸的方式，会浪费你当下大量的时间</p>

<p><strong>人类的潜意识会更注重当下付出的成本，而忽略掉未来会获得的收益</strong></p>

<p>因为你上面的编码方式，所养成的不好的编程习惯，会让你本能的不注重代码的耦合性、灵活性、可扩展性</p>

<p>所以即使你天天敲代码，你的进步也如此之慢，因为你平时就缺乏架构设计、代码设计的锻炼，日积月累，你也只是搬砖的速度比之前更快一点而已</p>

<h2 id="写开源框架时给你带来的改变">写开源框架时给你带来的改变</h2>

<p>这个时候如果有一个好的 leader 能每天 review 你的代码，还时常提醒你这些问题，只要你慢慢改成，并养成习惯，那你的进阶之路也会十分顺畅</p>

<p>但如此好的 leader 是可遇不可求的，那我们如果没有一个好 leader，该如何养成一个好习惯呢？</p>

<p>我的建议就是做开源库，而且尽量做开源框架，比如架构框架、UI 框架，网络框架、图片框架，尽量少做开源 App（当然如果为了学习某项技术也是可以的）</p>

<p>因为开源 App 主要是帮助你和别人学习某些技术，也是有大量的业务代码存在的，无非就是用了一些新的技术，而且还是你一个人写，有些细节你更不会考虑 …</p>

<p>而开源框架是拿给别人调用的，所以对代码质量要求更高，考虑的细节更多，对你的提升也越大</p>

<p>并且你的框架代码是放在服务器上的，使用者无法修改你的代码，这就会强迫你更关注代码的耦合性、灵活性、可扩展性，甚至是你写业务时根本不会注重的注释和文档</p>

<p>因为使用者用得稍有不爽就会给你发起 issue，不断地督促你优化你的代码、注释、文档，不断地帮助你培养一个良好的编程习惯</p>

<p>这就好比你有成百上千个好 leader 每天 review 你的代码，并指出你的问题，给予更好的建议，而且这些 leader 中绝对藏龙卧虎，这样日积月累下去，你怎能不成长？</p>

<h2 id="良好的编程习惯助你快速进步">良好的编程习惯助你快速进步</h2>

<p>我的很多编程习惯，经过不断的纠错，优化，纠错，优化，已经深深的刻进我的脑子里了</p>

<p>我框架用到的大多数设计模式，都不是我刻意去用的，都是遇到某些问题，或者使用者需要增加什么功能，我为了不影响之前的版本，保证扩展性的前提下，去解决这些问题，而不得不去使用这些设计模式，很多情况下，代码已经写完了，才知道，原来我用的是 xx 设计模式</p>

<p>在这种不断优化代码，养成的良好编程习惯下，我写代码时，使用设计模式已经变成了潜意识的行为，根本说不出为什么要用这个设计模式，只因为觉得这样用才是最优解，这就好比拳击手，遇到攻击时会潜意识的躲闪、反击一样，这就是不断实战、不断训练的结果</p>

<p>我的所有开源框架加起来每个月平均下载量在 5 万左右，每天都有各种 issue 向我飞来，这些成千上万的使用者（监督者），迫使我不断地优化自己的代码，甚至连每个注释我都精雕细琢</p>

<p>框架要做到满足所有人的需求太难了，但只要你不断优化，能做到让大部分人满意，那对你的提升绝对是巨大的，而且你的编程习惯也会有巨大的飞跃</p>

<p>比如我写注释的编程习惯培养起来后，我的每个开源框架，都写了大量的注释，描述的非常详细，保证大部分人都能明白原理，但还是有很多人跑过来问我文档和注释上已经写了的问题</p>

<p>我真的是吐血，兄弟，我真不想回答，你杀了我好吧 …</p>

<p>一个好的习惯，坚持一天，就往目标前进一步</p>

<p>一个坏的习惯，持续一天，也就浪费一天时间</p>

<p>我做开源三年时间改掉了我很多不好的编程习惯，让我进步飞速，所以我感谢开源</p>

<p>我也鼓励更多的人积极参与开源，让别人受益的同时，别人也让你不断进步</p>

<p>当然做开源，并不只是代码写的好，就能收获大量的 star，他还需要综合性的能力，比如框架定位、产品需求分析、运营、推广等很多东西，其中最重要的就是持续维护</p>

<p>我去年创新的新框架 AndroidAutoSize，上线一个月时间就 3k star，现在每个月有 2 万以上的下载量，超过我之前的所有开源框架，这就是因为我之前所积累的开源经验所赐，关于 Github 的运营之道后面我也会分享的</p>

<h2 id="公众号">公众号</h2>
<p>扫码关注我的公众号 JessYan，一起学习进步，如果框架有更新，我也会在公众号上第一时间通知大家</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-426bef5f6d5b748d?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="公众号" /></p>

<hr />
<p><strong>Hello 我叫 JessYan，如果您喜欢我的文章，可以在以下平台关注我</strong></p>

<ul>
  <li>个人主页: <a href="https://jessyan.me">https://jessyan.me</a></li>
  <li>GitHub: <a href="https://github.com/JessYanCoding">https://github.com/JessYanCoding</a></li>
  <li>掘金: <a href="https://juejin.im/user/57a9dbd9165abd0061714613">https://juejin.im/user/57a9dbd9165abd0061714613</a></li>
  <li>简书: <a href="https://www.jianshu.com/u/1d0c0bc634db">https://www.jianshu.com/u/1d0c0bc634db</a></li>
  <li>微博: <a href="https://weibo.com/u/1786262517">https://weibo.com/u/1786262517</a></li>
</ul>

<p>– The end</p>]]></content><author><name></name></author><category term="总结" /><summary type="html"><![CDATA[有人问我如何做好架构设计？怎样灵活运用设计模式？我的回答是，你做不好这些只是因为你没有养成一个良好的编程习惯]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" /><media:content medium="image" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">效率提升百分之四十，AS模板也太好用了吧</title><link href="https://jessyan.me/templates/" rel="alternate" type="text/html" title="效率提升百分之四十，AS模板也太好用了吧" /><published>2019-02-25T00:00:00+08:00</published><updated>2019-02-25T00:00:00+08:00</updated><id>https://jessyan.me/templates</id><content type="html" xml:base="https://jessyan.me/templates/"><![CDATA[<p>CV 工程师你好，以下内容会让你感觉到不适，慎入！</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-1140507e223ed1c4?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image" /></p>

<p>在平时的开发工作中，我们经常将一个地方的代码，复制粘贴到另外一个地方，俗称搬砖，搬砖搬多了，作为一个资深的挨踢民工，难免会总结一些提高生产力的搬砖经验</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-3b1afc8d85f75091?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image" /></p>

<p>Android Studio 模板是个提高生产力的好东西，使用过 MVPArms 的朋友，都应该使用过我提供的配套模板，使用该套模板，一键即可生成整个页面需要的所有文件，甚至还可以一键生成整个 Module，真正的解放了大家的双手，让大家的双手可以去做更多热爱的事情</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-dadd1cf5da805a4d?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image" /></p>

<p>Android Stuidio 模板一共有 4 种类型，分别对应不同的功能，我先来简单介绍下这 4 种模板，最后再着重介绍下一键生成 Module 的模板，因为前面 3 种模板在网上有太多优秀的教程了，而 Module 模板的资料非常少，我再重复写一遍前 3 种模板的教程也不一定有别人写得好，意义并不大，所以我前面只做简单介绍，后面会贴出一些教程链接，没了解过的就当科普了，了解过的就直接跳过</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-e12580d4548ae690?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image" /></p>

<p>现在还差几个 star 就超过 Mozilla 在模板语言 Freemarker 中排名 star 全球第一了，让我得瑟一下😏</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-7bedd83c5eb7c978?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image" /></p>

<h2 id="live-templates">Live Templates</h2>

<p>Live Templates 的功能主要是根据关键字快速生成代码块</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-b708734058e28f8b?imageMogr2/auto-orient/strip" alt="image" /></p>

<p>在设置中找到 Live Templates，点击红框中的按钮 “+”，即可新建 Live Templates</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-06f499d2bc389c16?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image" /></p>

<h2 id="file-templates">File Templates</h2>
<p>File Templates 顾名思义，主要功能就是一键生成单个文件</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-ac257dcc05784a7c?imageMogr2/auto-orient/strip" alt="image" /></p>

<p>在设置中找到 File and Code Templates，点击红框中的按钮 “+”，即可新建 File Templates</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-21da25a356300083?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image" /></p>

<h2 id="activity-templates">Activity Templates</h2>
<p>Activity Templates 可以一键生成多个文件，但不光可以生成 Activity 文件，还可以根据需求生成任何 Java 文件、XML 文件以及其他类型的文件</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-e0d264a482e88d8e?imageMogr2/auto-orient/strip" alt="image" /></p>

<p>新建 Activity Templates 要比上面的两个模板复杂的多，需要使用到 FreeMarker，感兴趣的朋友，可以看看我开源的 <a href="https://github.com/JessYanCoding/MVPArmsTemplate">Activity Templates</a>，稍微修改一下就能应用到自己的项目当中</p>

<h2 id="module-templates">module Templates</h2>
<p>module Templates 可以一键生成 Module 以及 Module 中所需要的所有文件</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-dc02ba6b268e0745?imageMogr2/auto-orient/strip" alt="image" /></p>

<p>module Templates 也是使用 FreeMarker 创建的，如果你能理解 Activity Templates，那你也可以快速的新建一个 module Templates</p>

<h2 id="详解-module-templates">详解 module Templates</h2>
<p>不知道大家了解过一键生成 Module 的 Android Stuidio 模板吗，这种类型的模板我在网上没搜到有相关的开源库和文章，所以我就自己研究了一段时间，在去年就开源给了 MVPArms 的使用者</p>

<p>Module Templates 其实要比 Activity Templates 简单很多，因为 Module Templates 的某些限制，导致它的配置面板不能自定义一些新增的功能，所以复杂性降低很多，但是功能性和灵活性就要弱于 Activity Templates，不过即使这样也能满足我们大部分的需求了</p>

<p>Android Studio 中所有的 module Templates 都放在 gradle-projects 这个目录中（Android Studio 安装目录/plugins/android/lib/templates/gradle-projects），如果按照很多教程中对 Activity Templates 学习方法的描述，我们应该按照和 Activity Templates 一样的学习方式，打开 gradle-projects 目录中的 NewAndroidModule 进行学习</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-24df7f987cde4bc6?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image" /></p>

<p>但是我可以告诉大家，在你没有足够经验的情况下，最好不要这样做，因为这个文件夹中包含着 Android Studio 创建 Android Module 所需要的所有文件和代码，里面比较复杂，有很多判断条件，很多东西对你的项目来说都用不到，在学习时会耽误你很多时间</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-97b367d979f63a87?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image" /></p>

<p>所以为了让大家快速上手，我就用我已经开源的 <a href="https://github.com/JessYanCoding/MVPArms-Module-Template">module Templates</a> 作为学习案例，里面的所有文件和代码都是经过我筛选过后提取出来的，非常易于大家学习和理解</p>

<h2 id="module-templates-结构">module Templates 结构</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NewArmsComponent
├── root
│   ├── res //Module 中的资源文件都放这里
│   ├── ├── anim
│   ├── ├── layout
│   ├── ├── mipmap-hdpi
│   ├── ├── mipmap-xhdpi
│   ├── ├── mipmap-xxhdpi
│   ├── ├── mipmap-xxxhdpi
│   ├── ├── values
│   ├── src/app_package   //Module 中的源文件模板都放这里
│   ├── ├── ActivityLifecycleCallbacksImpl.java.ftl
│   ├── ├── Api.java.ftl
│   ├── ├── AppLifecyclesImpl.java.ftl
│   ├── ├── ...
│   ├── test/app_package   //Module 中的测试文件模板都放这里
│   ├── ├── ExampleInstrumentedTest.java.ftl
│   ├── ├── ExampleUnitTest.java.ftl
│   ├── AndroidManifest.xml.ftl   //AndroidManifest.xml 模板文件
│   ├── CMakeLists.txt.ftl   //不用 CMake 可以忽略
│   ├── build.gradle.ftl   //App build.gradle 模板文件
│   ├── native-lib.cpp.ftl   //不用 C 可以忽略
│   ├── root-build.gradle.ftl   //根目录 build.gradle 模板文件
│   ├── settings.gradle.ftl   //settings.gradle 模板文件
├── globals.xml.ftl
├── recipe.xml.ftl
├── template.xml
├── template_new_project.png
</code></pre></div></div>
<p>root 文件夹下包含的是生成 Module 所需要的模板文件，template_new_project.png 是模板的封面图，globals.xml.ftl 我们暂时用不到，recipe.xml.ftl、template.xml 这两个文件存放的是模板的配置参数，是整个模板的核心文件，我们来看看这两个文件</p>

<h2 id="templatexml">template.xml</h2>
<p>template.xml 放置的是配置面板的参数，配置面板是什么东西？看下图</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-b55dcd2c7f345ceb?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image" /></p>

<p>module Templates 和 Activity Templates 的配置面板不一样，Activity Templates 可以任意修改和新增配置面板中的项目，但是 module Templates 不可以，只能固定为图中的这四项</p>

<p>看看 template.xml 里面的内容</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0"?&gt;</span>
<span class="nt">&lt;template</span>
    <span class="na">format=</span><span class="s">"5"</span>
    <span class="na">revision=</span><span class="s">"2"</span>
    <span class="na">name=</span><span class="s">"MVPArms Module"</span>
    <span class="na">description=</span><span class="s">"Creates a new MVPArms Module."</span><span class="nt">&gt;</span>

    <span class="nt">&lt;category</span> <span class="na">value=</span><span class="s">"Application"</span> <span class="nt">/&gt;</span>

    <span class="nt">&lt;formfactor</span> <span class="na">value=</span><span class="s">"Things"</span> <span class="nt">/&gt;</span>

----------------------- 分割线 -----------------------
    <span class="nt">&lt;thumbs&gt;</span>
        <span class="nt">&lt;thumb&gt;</span>template_new_project.png<span class="nt">&lt;/thumb&gt;</span>
    <span class="nt">&lt;/thumbs&gt;</span>
    
    <span class="nt">&lt;parameter</span>
        <span class="na">id=</span><span class="s">"packageName"</span>
        <span class="na">name=</span><span class="s">"Package name"</span>
        <span class="na">type=</span><span class="s">"string"</span>
        <span class="na">constraints=</span><span class="s">"app_package|nonempty"</span>
        <span class="na">default=</span><span class="s">"com.mycompany.myapp"</span> <span class="nt">/&gt;</span>
        
    ...
    
 <span class="nt">&lt;globals</span> <span class="na">file=</span><span class="s">"globals.xml.ftl"</span> <span class="nt">/&gt;</span>
 <span class="nt">&lt;execute</span> <span class="na">file=</span><span class="s">"recipe.xml.ftl"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/template&gt;</span>        
</code></pre></div></div>

<p>只用看分割线以上的标签即可，下面的标签基本用不上，每个 parameter 标签在配置面板中都对应一个项目，但 parameter 标签只在 Activity Templates 中可以任意修改和新增，在 module Templates 中的任何修改和新增都是无效的，这点大家可以试试</p>

<p>name 标签和 description 标签顾名思义，这就不用多解释了，category 标签固定为 Application 即可</p>

<p>这里要特别要提一下 formfactor 标签，在 NewAndroidModule 中，formfactor 标签的值为 Mobile，最开始我也和它一样，将值赋为 Mobile，结果发现无法生成文件，我想可能是 Android Studio 只允许存在一个 Android 的 Module 模板，我又试着改成了 TV、Glass、Wear …，发现都不完美，要不是无法生成文件，要不就生成太多不需要的文件，最后改成 Things 后，效果就接近完美了</p>

<h2 id="recipexmlftl">recipe.xml.ftl</h2>
<p>recipe.xml.ftl 的功能就是告诉模板引擎，你打算怎么处理你的模板文件</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0"?&gt;</span>
<span class="c">&lt;!-- TODO: check include Cpp support; add driver module template --&gt;</span>
<span class="nt">&lt;recipe&gt;</span>
    <span class="nt">&lt;mkdir</span> <span class="na">at=</span><span class="s">"${escapeXmlAttribute(projectOut)}/libs"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;mkdir</span> <span class="na">at=</span><span class="s">"${escapeXmlAttribute(resOut)}/anim"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;mkdir</span> <span class="na">at=</span><span class="s">"${projectOut}/src/main/java/${slashedPackageName(packageName)}/app/utils"</span> <span class="nt">/&gt;</span>

    <span class="nt">&lt;merge</span> <span class="na">from=</span><span class="s">"root/settings.gradle.ftl"</span>
             <span class="na">to=</span><span class="s">"${escapeXmlAttribute(topOut)}/settings.gradle"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;merge</span> <span class="na">from=</span><span class="s">"root/root-build.gradle.ftl"</span>
             <span class="na">to=</span><span class="s">"${escapeXmlAttribute(topOut)}/build.gradle"</span> <span class="nt">/&gt;</span>
             
    <span class="nt">&lt;copy</span> <span class="na">from=</span><span class="s">"root/res/mipmap-hdpi/ic_launcher.png"</span>
        <span class="na">to=</span><span class="s">"${escapeXmlAttribute(resOut)}/mipmap-hdpi/ic_launcher.png"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;copy</span> <span class="na">from=</span><span class="s">"root/res/mipmap-xhdpi/ic_launcher.png"</span>
        <span class="na">to=</span><span class="s">"${escapeXmlAttribute(resOut)}/mipmap-xhdpi/ic_launcher.png"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;copy</span> <span class="na">from=</span><span class="s">"root/res/mipmap-xxhdpi/ic_launcher.png"</span>
        <span class="na">to=</span><span class="s">"${escapeXmlAttribute(resOut)}/mipmap-xxhdpi/ic_launcher.png"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;copy</span> <span class="na">from=</span><span class="s">"root/res/mipmap-xxxhdpi/ic_launcher.png"</span>
        <span class="na">to=</span><span class="s">"${escapeXmlAttribute(resOut)}/mipmap-xxxhdpi/ic_launcher.png"</span> <span class="nt">/&gt;</span>                  

    <span class="nt">&lt;instantiate</span> <span class="na">from=</span><span class="s">"root/build.gradle.ftl"</span>
                   <span class="na">to=</span><span class="s">"${escapeXmlAttribute(projectOut)}/build.gradle"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;instantiate</span> <span class="na">from=</span><span class="s">"root/AndroidManifest.xml.ftl"</span>
                   <span class="na">to=</span><span class="s">"${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;instantiate</span> <span class="na">from=</span><span class="s">"root/res/values/styles.xml.ftl"</span>
                   <span class="na">to=</span><span class="s">"${escapeXmlAttribute(resOut)}/values/styles.xml"</span> <span class="nt">/&gt;</span>

    <span class="nt">&lt;instantiate</span> <span class="na">from=</span><span class="s">"root/src/app_package/GlobalConfiguration.java.ftl"</span>
                   <span class="na">to=</span><span class="s">"${projectOut}/src/main/java/${slashedPackageName(packageName)}/app/GlobalConfiguration.java"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;open</span> <span class="na">file=</span><span class="s">"${projectOut}/src/main/java/${slashedPackageName(packageName)}/app/GlobalConfiguration.java"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;instantiate</span> <span class="na">from=</span><span class="s">"root/src/app_package/AppLifecyclesImpl.java.ftl"</span>
                   <span class="na">to=</span><span class="s">"${projectOut}/src/main/java/${slashedPackageName(packageName)}/app/AppLifecyclesImpl.java"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;open</span> <span class="na">file=</span><span class="s">"${projectOut}/src/main/java/${slashedPackageName(packageName)}/app/AppLifecyclesImpl.java"</span> <span class="nt">/&gt;</span>
       
<span class="err">&lt;</span>#if unitTestsSupported&gt;
    <span class="nt">&lt;instantiate</span> <span class="na">from=</span><span class="s">"root/test/app_package/ExampleUnitTest.java.ftl"</span>
                   <span class="na">to=</span><span class="s">"${escapeXmlAttribute(unitTestOut)}/ExampleUnitTest.java"</span> <span class="nt">/&gt;</span>
<span class="err">&lt;</span>/#if&gt;          
<span class="nt">&lt;/recipe&gt;</span>
</code></pre></div></div>

<p>Module 所需要的模板文件都放在 root 文件夹下，而使用 recipe.xml.ftl 中的标签就是为了告诉模版引擎，你打算将哪个模板文件通过什么方式放到 Module 中的什么位置</p>

<p>mkdir 标签，顾名思义，就是生成一个空的文件夹</p>

<p>merge 标签中的 from 填写模板文件的地址，to 填写 Module 中的目标位置，merge 标签会将 from 中填写的模板文件，放到 to 中填写的目标位置上，如果目标位置已经存在一个相同文件名的文件，则将模板文件中的全部内容复制到已经存在的文件中，否则则创建一个新的文件</p>

<p>copy 标签会将 from 中填写的文件，放到 to 中填写的目标位置上，如果目标位置已经存在一个相同文件名的文件，则会覆盖此文件</p>

<p>instantiate 标签会将 from 中填写的模板文件，放到 to 中填写的目标位置上，如果目标位置已经存在一个相同文件名的文件，则会覆盖此文件，看描述和 copy 标签差不多，那它们的区别是什么呢？</p>

<p>copy 标签的目标对象是普通文件，也就是没有 .ftl 后缀的文件，instantiate 标签的目标对象是模板文件，也就是有 .ftl 后缀的文件，那有 .ftl 后缀的文件和没有 .ftl 后缀的文件有什么区别呢？</p>

<p>有 .ftl 后缀的文件中可以使用 FreeMarker 语法，在生成文件时，模版引擎会根据文件中的 FreeMarker 语法对文件内容进行相应的修改，比如根据不同的条件生成不同的内容，而没有 .ftl 后缀的文件，文件中的内容一旦确定在生成时是无法改变的，就如同单纯的复制粘贴</p>

<p>${escapeXmlAttribute(resOut)} 对应的是 Module 中的 res 文件夹</p>

<p>${projectOut}/src/main/java/${slashedPackageName(packageName)} 对应的是包名下的根目录</p>

<p>${escapeXmlAttribute(topOut)} 对应的是整个工程的根目录</p>

<p>${escapeXmlAttribute(projectOut)} 对应的是 Module 的根目录</p>

<p>${escapeXmlAttribute(manifestOut)} 对应的是放置 AndroidManifest.xml 的文件夹</p>

<p>${escapeXmlAttribute(testOut)} 对应的是 Module 中放置测试文件的文件夹</p>

<h2 id="模板文件">模板文件</h2>
<p>root 文件夹下模板文件的编写也不难，因为 module Templates 没有用到自定义变量，所以大多数情况下，就是将你写好的 java 文件，后面加上 .ftl 后缀即可，看看下面我贴出的源代码链接，你很快就能上手</p>

<p>你如果还想学到更多的 FreeMarker 语法，可以翻翻下面贴出的 FreeMarker 中文文档</p>

<h2 id="开源代码链接">开源代码链接</h2>
<p><a href="https://github.com/JessYanCoding/MVPArms/blob/master/MVP_generator_solution">我给 MVPArms 写的 File Templates</a></p>

<p><a href="https://github.com/JessYanCoding/MVPArmsTemplate">我给 MVPArms 写的 Activity Templates</a></p>

<p><a href="https://github.com/JessYanCoding/MVPArms-Module-Template">我给 MVPArms 写的 module Templates</a></p>

<p><a href="https://github.com/JessYanCoding/ArmsComponent-Template">我给组件化项目写的 module Templates</a></p>

<h2 id="教程链接">教程链接</h2>
<p><a href="https://www.jianshu.com/p/e3548f441440">快速写出 Activity Templates 的插件</a></p>

<p><a href="https://www.jianshu.com/p/065877f0c33d">Live Templates 教程</a></p>

<p><a href="https://www.jianshu.com/p/57e734864b46">File Templates 教程</a></p>

<p><a href="https://blog.csdn.net/lmj623565791/article/details/51635533">Activity Templates 教程</a></p>

<p><a href="http://freemarker.foofun.cn/toc.html">FreeMarker 中文文档</a></p>

<h2 id="公众号">公众号</h2>
<p>扫码关注我的公众号 JessYan，一起学习进步，如果框架有更新，我也会在公众号上第一时间通知大家</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-222576128821ca4a?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image" /></p>

<hr />
<p><strong>Hello 我叫 JessYan，如果您喜欢我的文章，可以在以下平台关注我</strong></p>

<ul>
  <li>个人主页: <a href="https://jessyan.me">https://jessyan.me</a></li>
  <li>GitHub: <a href="https://github.com/JessYanCoding">https://github.com/JessYanCoding</a></li>
  <li>掘金: <a href="https://gold.xitu.io/user/57a9dbd9165abd0061714613">https://gold.xitu.io/user/57a9dbd9165abd0061714613</a></li>
  <li>简书: <a href="https://www.jianshu.com/u/1d0c0bc634db">https://www.jianshu.com/u/1d0c0bc634db</a></li>
  <li>微博: <a href="https://weibo.com/u/1786262517">https://weibo.com/u/1786262517</a></li>
</ul>

<p>– The end</p>]]></content><author><name></name></author><category term="Templates" /><summary type="html"><![CDATA[CV 工程师你好，以下内容会让你感觉到不适，慎入！]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" /><media:content medium="image" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">今日头条屏幕适配方案终极版正式发布!</title><link href="https://jessyan.me/autosize-publish/" rel="alternate" type="text/html" title="今日头条屏幕适配方案终极版正式发布!" /><published>2018-09-25T00:00:00+08:00</published><updated>2018-09-25T00:00:00+08:00</updated><id>https://jessyan.me/autosize-publish</id><content type="html" xml:base="https://jessyan.me/autosize-publish/"><![CDATA[<p>以下是 <strong>骚年你的屏幕适配方式该升级了!</strong> 系列文章，欢迎转发以及分享:</p>

<ul>
  <li><a href="https://jessyan.me/autosize-introduce/">骚年你的屏幕适配方式该升级了!（一）-今日头条适配方案</a></li>
  <li><a href="https://jessyan.me/smallestwidth-introduce/">骚年你的屏幕适配方式该升级了!（二）-smallestWidth 限定符适配方案</a></li>
  <li><a href="https://jessyan.me/autosize-publish/">今日头条屏幕适配方案终极版正式发布!</a></li>
</ul>

<h1 id="前言">前言</h1>
<p>我在前面两篇文章中详细介绍了 <a href="https://jessyan.me/autosize-introduce/">今日头条适配方案</a> 和 <a href="https://jessyan.me/smallestwidth-introduce/">SmallestWidth 限定符适配方案</a> 的原理，并验证了它们的可行性，以及总结了它们各自的优缺点，可以说这两个方案都是目前比较优秀、比较主流的 <strong>Android</strong> 屏幕适配方案，而且它们都已经拥有了一定的用户基数</p>

<p>但是对于一些才接触这两个方案的朋友，肯定或多或少还是不知道如何选择这两个方案，我虽然在之前的文章中给出了它们各自的优缺点，但是并没有用统一的标准对它们进行更细致的对比，所以也就没办法更形象的体现它们的优劣，那下面我就用统一的标准对它们进行对比，看看它们的对比情况</p>

<h1 id="方案对比">方案对比</h1>
<p>我始终坚定地认为在这两个方案中，并不能以单个标准就能评判出谁一定比谁好，因为它们都有各自的优缺点，都不是完美的，从更客观的角度来看，它们谁都不能成为最好的那个，只有可能明确了它们各自的优缺点，知道在它们的优缺点里什么是我能接受的，什么是我不能接受的，是否能为了某些优点做出某些妥协，从而选择出一个最适合自己项目的屏幕适配方案</p>

<p>单纯的争论谁是最好的 <strong>Android</strong> 屏幕适配方案没有任何意义，每个人的需求不一样，站的角度不一样，评判标准也不一样，你能接受的东西他不一定能接受，你觉得不可接受的东西他却觉得可以接受，你有你的理由，他有他的理由，想让一个观点让所有人都能接受太难了！所以我在这里只是列出它们的对比项和对比结果，尽可能的做到客观，最后的选择结果请自行决定，如果还有什么遗漏的对比项，请补充！</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">对比项目</th>
      <th style="text-align: center">对比对象 A</th>
      <th style="text-align: center">对比结果</th>
      <th style="text-align: center">对比对象 B</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">适配效果(越高越好)</td>
      <td style="text-align: center">今日头条适配方案</td>
      <td style="text-align: center">≈</td>
      <td style="text-align: center">SW 限定符适配方案(在未覆盖的机型上会存在一定的误差)</td>
    </tr>
    <tr>
      <td style="text-align: center">稳定性(越高越好)</td>
      <td style="text-align: center">今日头条适配方案</td>
      <td style="text-align: center">&lt;</td>
      <td style="text-align: center">SW 限定符适配方案</td>
    </tr>
    <tr>
      <td style="text-align: center">灵活性(越高越好)</td>
      <td style="text-align: center">今日头条适配方案</td>
      <td style="text-align: center">&gt;</td>
      <td style="text-align: center">SW 限定符适配方案</td>
    </tr>
    <tr>
      <td style="text-align: center">扩展性(越高越好)</td>
      <td style="text-align: center">今日头条适配方案</td>
      <td style="text-align: center">&gt;</td>
      <td style="text-align: center">SW 限定符适配方案</td>
    </tr>
    <tr>
      <td style="text-align: center">侵入性(越低越好)</td>
      <td style="text-align: center">今日头条适配方案</td>
      <td style="text-align: center">&lt;</td>
      <td style="text-align: center">SW 限定符适配方案</td>
    </tr>
    <tr>
      <td style="text-align: center">使用成本(越低越好)</td>
      <td style="text-align: center">今日头条适配方案</td>
      <td style="text-align: center">&lt;</td>
      <td style="text-align: center">SW 限定符适配方案</td>
    </tr>
    <tr>
      <td style="text-align: center">维护成本(越低越好)</td>
      <td style="text-align: center">今日头条适配方案</td>
      <td style="text-align: center">&lt;</td>
      <td style="text-align: center">SW 限定符适配方案</td>
    </tr>
    <tr>
      <td style="text-align: center">性能损耗</td>
      <td style="text-align: center">今日头条适配方案没有性能损耗</td>
      <td style="text-align: center">=</td>
      <td style="text-align: center">SW 限定符适配方案没有性能损耗</td>
    </tr>
    <tr>
      <td style="text-align: center">副作用</td>
      <td style="text-align: center">今日头条适配方案会影响一些三方库和系统控件</td>
      <td style="text-align: center">≈</td>
      <td style="text-align: center">SW 限定符适配方案会影响 App 的体积</td>
    </tr>
  </tbody>
</table>

<p>可以看到 <strong>SmallestWidth 限定符适配方案</strong> 和 <strong>今日头条适配方案</strong> 的适配效果其实都是差不多的，我在前面的文章中也通过公式计算过它们的精确度，<strong>SmallestWidth 限定符适配方案</strong> 运行在未覆盖的机型上虽然也可以适配，但是却会出现一定的误差，所以 <strong>今日头条适配方案</strong> 的适配精确度确实要比 <strong>SmallestWidth 限定符适配方案</strong> 略高的，不过只要 <strong>SmallestWidth 限定符适配方案</strong> 合理的分配资源文件，适配效果的差距应该也不大</p>

<p><strong>SmallestWidth 限定符适配方案</strong> 主打的是稳定性，在运行过程中极少会出现安全隐患，适配范围也可控，不会产生其他未知的影响，而 <strong>今日头条适配方案</strong> 主打的是降低开发成本、提高开发效率，使用上更灵活，也能满足更多的扩展需求，简单一句话概括就是，这两兄弟，一个求稳，一个求快，好了，我就介绍这么多了，自己选择吧！</p>

<h1 id="androidautosize">AndroidAutoSize</h1>
<p><img src="https://raw.githubusercontent.com/JessYanCoding/AndroidAutoSize/master/art/autosize_banner.jpg" alt="AndroidAutoSize" /></p>

<h2 id="由来">由来</h2>
<p>下面就开始介绍我根据 <strong>今日头条屏幕适配方案</strong> 优化的屏幕适配框架 <a href="https://github.com/JessYanCoding/AndroidAutoSize"><strong>AndroidAutoSize</strong></a>，大家千万不要认为，我推出的屏幕适配框架 <a href="https://github.com/JessYanCoding/AndroidAutoSize"><strong>AndroidAutoSize</strong></a> 是根据 <strong>今日头条屏幕适配方案</strong> 优化的，我本人就一定支持 <strong>今日头条屏幕适配方案</strong> 是最好的 <strong>Android</strong> 屏幕适配方案这个观点，它确实很优秀，但同样也有很多不足，我最真实的观点在上面就已经表述咯，至于我为什么要根据 <strong>今日头条屏幕适配方案</strong> 再封装一个屏幕适配框架，无外乎就以下几点原因:</p>

<ul>
  <li>
    <p><strong>SmallestWidth 限定符适配方案</strong> 已经有多个优秀的开源解决方案了，它们已经能满足我们日常开发中的所有需求</p>
  </li>
  <li><strong>今日头条</strong> 官方技术团队只公布了 <strong>今日头条屏幕适配方案</strong> 的 <a href="https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA">文章</a> 以及核心代码，但并没有在 <strong>Github</strong> 上创建公开的仓库，一个新的方案必定要有一个成长迭代的过程，在此期间，一定需要一个可以把所有使用者聚集起来的公共社区，可以让所有使用该方案的使用者在上面交流，大家一起总结、一起填坑，这样才能让该方案更成熟稳定，这就是开源的力量</li>
  <li><strong>今日头条</strong> 官方技术团队公布的核心代码并不能满足我的所有需求，已经开源的其他基于 <strong>今日头条屏幕适配方案</strong> 的开源项目以及解决方案也不能满足我的所有需求，而我有更好的实现想法</li>
  <li><a href="https://github.com/JessYanCoding/MVPArms">MVPArms</a> 需要一个适配效果还不错并且切换维护成本也比较低的屏幕适配框架，以帮助使用者用较低的成本、工作量将已经停止维护的 <strong>AndroidAutoLayout</strong> 快速替换掉</li>
</ul>

<p>我建议大家都可以去实际体验一下 <strong>今日头条屏幕适配方案</strong> 和 <strong>SmallestWidth 限定符适配方案</strong>，感受下它们的异同，我给的建议是，可以在项目中先使用 <strong>今日头条屏幕适配方案</strong>，感受下它的使用方式以及适配效果，<strong>今日头条屏幕适配方案</strong> 的侵入性非常低，如果在使用过程中遇到什么不能解决的问题，马上可以切换为其他的屏幕适配方案，在切换的过程中也花费不了多少工作量，试错成本非常低</p>

<p>但如果你在项目中先使用 <strong>SmallestWidth 限定符适配方案</strong>，之后在使用的过程中再遇到什么不能解决的问题，这时想切换为其他的屏幕适配方案，这工作量可就大了，每个 <strong>Layout</strong> 文件都含有大量的 <strong>dimens</strong> 引用，改起来这工作量得有多大，想想都觉得后怕，这就是侵入性太高导致的最致命的问题</p>

<p>如果想体验 <strong>今日头条屏幕适配方案</strong>，千万不要错过 <strong>AndroidAutoSize</strong> 哦！仅需一步即可接入项目，下面是 <strong>AndroidAutoSize</strong> 的地址:</p>

<blockquote>
  <p>Github : <a href="https://github.com/JessYanCoding/AndroidAutoSize">您的 Star 是我坚持的动力 ✊</a></p>
</blockquote>

<h3 id="与今日头条屏幕适配方案的关系">与今日头条屏幕适配方案的关系</h3>
<p><strong>AndroidAutoSize</strong> 与 <strong>今日头条屏幕适配方案</strong> 的关系，相当于汽车和发动机的关系，<strong>今日头条屏幕适配方案</strong> 官方公布的代码，只实现了修改系统 <strong>density</strong> 的相关逻辑，这的确在屏幕适配中起到了最关键的作用，但这还远远还不够</p>

<p>要想让使用者能够更傻瓜式的使用该方案，并且能够应对日常开发中的所有复杂需求，那在架构框架时，还需要考虑 <strong>API</strong> 的易用性以及合理性、框架的扩展性以及灵活性、功能的全面性、注释和文档的易读性等多个方面的问题</p>

<p>于是我带着我的这些标准在网上搜寻了很久，发现并没有任何一个开源框架或解决方案能够达到我的所有标准，它们大多数还只是停留在将 <strong>今日头条屏幕适配方案</strong> 封装成工具类来引入项目的阶段，这样在功能的扩展上有限制，并且对用户的使用体验也不好，而我想做的是一个全面性的产品级屏幕适配框架，这离我最初的构想，差距还非常大，于是我只好自己动手，将我的所有思想实现，这才有了 <strong>AndroidAutoSize</strong></p>

<p>写完 <strong>AndroidAutoSize</strong> 框架后，因为对 <strong>今日头条屏幕适配方案</strong> 有了更加深入的理解，所以才写了 <a href="https://jessyan.me/autosize-introduce/">骚年你的屏幕适配方式该升级了!（一）-今日头条适配方案</a>，以帮助大家更清晰的理解 <strong>今日头条屏幕适配方案</strong></p>

<h3 id="与-androidautolayout-的关系">与 AndroidAutoLayout 的关系</h3>
<p><strong>AndroidAutoSize</strong> 因为名字和 <strong>鸿神</strong> 的 <strong>AndroidAutoLayout</strong> 非常相似，并且在填写设计图尺寸的方式上也极为相似，再加上我写的屏幕适配系列的文章也发布在了 <strong>鸿神</strong> 的公众号上，所以很多人以为 <strong>AndroidAutoSize</strong> 是 <strong>鸿神</strong> 写的 <strong>AndroidAutoLayout</strong> 的升级版，这里我哭笑不得 😂，我只好在这里说一句，大家好，我叫 <a href="https://github.com/JessYanCoding">JessYan</a>，的确可以理解为 <strong>AndroidAutoSize</strong> 是 <strong>AndroidAutoLayout</strong> 的升级版，但是它是我写的，关注一波呗</p>

<p>但 <strong>AndroidAutoSize</strong> 和 <strong>AndroidAutoLayout</strong> 的原理，却天差地别，比如 <strong>AndroidAutoLayout</strong> 只能使用 <strong>px</strong> 作为布局单位，而 <strong>AndroidAutoSize</strong> 恰好相反，在布局中 <strong>dp、sp、pt、in、mm</strong> 所有的单位都能支持，唯独不支持 <strong>px</strong>，但这也意味着 <strong>AndroidAutoSize</strong> 和 <strong>AndroidAutoLayout</strong> 在项目中可以共存，互不影响，所以使用 <strong>AndroidAutoLayout</strong> 的老项目也可以放心的引入 <strong>AndroidAutoSize</strong>，慢慢的完成屏幕适配框架的切换</p>

<p>之所以将框架取名为 <strong>AndroidAutoSize</strong>，第一，是想致敬 <strong>AndroidAutoLayout</strong> 对 <strong>Android</strong> 屏幕适配领域的贡献，第二，也想成为在 <strong>Android</strong> 屏幕适配领域有重要影响力的框架</p>

<h2 id="结构">结构</h2>
<p>我在上面就已经说了很多开源框架以及解决方案，只是把 <strong>今日头条屏幕适配方案</strong> 简单的封装成一个工具类然后引入项目，这时很多人就会说了 <strong>今日头条屏幕适配方案</strong> 官方公布的全部代码都只有 <strong>30</strong> 行不到，你不把它封装成工具类，那封装成什么？该怎么封装？下面就来看看 <strong>AndroidAutoSize</strong> 的整体结构</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>├── external
│   ├── ExternalAdaptInfo.java
│   ├── ExternalAdaptManager.java
│── internal
│   ├── CancelAdapt.java
│   ├── CustomAdapt.java
│── unit
│   ├── Subunits.java
│   ├── UnitsManager.java
│── utils
│   ├── AutoSizeUtils.java
│   ├── LogUtils.java
│   ├── Preconditions.java
│   ├── ScreenUtils.java
├── ActivityLifecycleCallbacksImpl.java
├── AutoAdaptStrategy.java
├── AutoSize.java
├── AutoSizeConfig.java
├── DefaultAutoAdaptStrategy.java
├── DisplayMetricsInfo.java
├── FragmentLifecycleCallbacksImpl.java
├── InitProvider.java
</code></pre></div></div>

<p><strong>AndroidAutoSize</strong> 根据 <strong>今日头条屏幕适配方案</strong> 官方公布的 <strong>30</strong> 行不到的代码，经过不断的优化和扩展，发展成了现在拥有 <strong>18</strong> 个类文件，上千行代码的全面性屏幕适配框架，在迭代的过程中完善和优化了很多功能，相比 <strong>今日头条屏幕适配方案</strong> 官方公布的原始代码，<strong>AndroidAutoSize</strong> 更加稳定、更加易用、更加强大，欢迎阅读源码，注释非常详细哦！</p>

<h2 id="功能介绍">功能介绍</h2>
<p><strong>AndroidAutoSize</strong> 在使用上非常简单，只需要填写设计图尺寸这一步即可接入项目，但需要注意的是，<strong>AndroidAutoSize</strong> 有两种类型的布局单位可以选择，一个是 <strong>主单位 (dp、sp)</strong>，一个是 <strong>副单位 (pt、in、mm)</strong>，两种单位面向的应用场景都有不同，也都有各自的优缺点</p>

<ul>
  <li>
    <p><strong>主单位</strong>: 使用 <strong>dp、sp</strong> 为单位进行布局，侵入性最低，会影响其他三方库页面、三方库控件以及系统控件的布局效果，但 <strong>AndroidAutoSize</strong> 也通过这个特性，使用 <strong>ExternalAdaptManager</strong> 实现了在不修改三方库源码的情况下适配三方库的功能</p>
  </li>
  <li>
    <p><strong>副单位</strong>: 使用 <strong>pt、in、mm</strong> 为单位进行布局，侵入性高，对老项目的支持比较好，不会影响其他三方库页面、三方库控件以及系统控件的布局效果，可以彻底的屏蔽修改 <strong>density</strong> 所造成的所有未知和已知问题，但这样 <strong>AndroidAutoSize</strong> 也就无法对三方库进行适配</p>
  </li>
</ul>

<p>大家可以根据自己的应用场景在 <strong>主单位</strong> 和 <strong>副单位</strong> 中选择一个作为布局单位，建议想引入老项目并且注重稳定性的人群使用 <strong>副单位</strong>，只是想试试本框架，随时可能切换为其他屏幕适配方案的人群使用 <strong>主单位</strong></p>

<p>其实 <strong>AndroidAutoSize</strong> 可以同时支持 <strong>主单位</strong> 和 <strong>副单位</strong>，但 <strong>AndroidAutoSize</strong> 可以同时支持 <strong>主单位</strong> 和 <strong>副单位</strong> 的目的，只是为了让使用者可以在 <strong>主单位</strong> 和 <strong>副单位</strong> 之间灵活切换，因为切换单位的工作量可能非常巨大，不能立即完成，但领导又要求马上打包上线，这时就可以起到一个很好的过渡作用</p>

<h2 id="主单位">主单位</h2>
<p><strong>主单位</strong> 的 <strong>Demo</strong> 在 <a href="https://github.com/JessYanCoding/AndroidAutoSize/blob/master/demo">demo</a></p>

<h3 id="基本使用">基本使用</h3>
<p>将 <strong>AndroidAutoSize</strong> 引入项目后，只要在 <strong>app</strong> 的 <strong>AndroidManifest.xml</strong> 中填写上设计图尺寸，无需其他过多配置 (如果你没有其他自定义需求的话)，<strong>AndroidAutoSize</strong> 即可自动运行，像下面这样👇</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;manifest&gt;</span>
    <span class="nt">&lt;application&gt;</span>            
        <span class="nt">&lt;meta-data</span>
            <span class="na">android:name=</span><span class="s">"design_width_in_dp"</span>
            <span class="na">android:value=</span><span class="s">"360"</span><span class="nt">/&gt;</span>
        <span class="nt">&lt;meta-data</span>
            <span class="na">android:name=</span><span class="s">"design_height_in_dp"</span>
            <span class="na">android:value=</span><span class="s">"640"</span><span class="nt">/&gt;</span>           
     <span class="nt">&lt;/application&gt;</span>           
<span class="nt">&lt;/manifest&gt;</span>
</code></pre></div></div>

<p>在使用主单位时，<code class="language-plaintext highlighter-rouge">design_width_in_dp</code> 和 <code class="language-plaintext highlighter-rouge">design_height_in_dp</code> 的单位必须是 <strong>dp</strong>，如果设计师给你的设计图，只标注了 <strong>px</strong> 尺寸 (现在已经有很多 <strong>UI</strong> 工具可以自动标注 <strong>dp</strong> 尺寸了)，那请自行根据公式 <strong>dp = px / (DPI / 160)</strong> 将 <strong>px</strong> 尺寸转换为 <strong>dp</strong> 尺寸，如果你不知道 <strong>DPI</strong> 是多少？那请以自己测试机的 <strong>DPI</strong> 为准，如果连怎么得到设备的 <strong>DPI</strong> 都不知道？百度吧好伐，如果你实在找不到设备的 <strong>DPI</strong> 那就直接将 <strong>px</strong> 尺寸除以 <strong>3</strong> 或者 <strong>2</strong> 也是可以的</p>

<p><strong>如果你只是想使用 AndroidAutoSize 的基础功能，AndroidAutoSize 的使用方法在这里就结束了，只需要上面这一步，即可帮助你以最简单的方式接入 AndroidAutoSize，但是作为一个全面性的屏幕适配框架，在保证基础功能的简易性的同时，也必须保证复杂的需求也能在框架内被解决，从而达到一个小闭环，所以下面介绍的内容全是前人踩坑踩出来的一些必备功能，如果你没这个需求，或者觉得麻烦，可以按需查看或者跳过，下面的内容建议和 Demo 配合起来阅读，效果更佳</strong></p>

<h4 id="注意事项">注意事项</h4>
<ul>
  <li>
    <p>你在 <strong>AndroidManifest.xml</strong> 中怎么把设计图的 <strong>px</strong> 尺寸转换为 <strong>dp</strong> 尺寸，那在布局时，每个控件的大小也需要以同样的方式将设计图上标注的 <strong>px</strong> 尺寸转换为 <strong>dp</strong> 尺寸，千万不要在 <strong>AndroidManifest.xml</strong> 中填写的是 <strong>dp</strong> 尺寸，却在布局中继续填写设计图上标注的 <strong>px</strong> 尺寸</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">design_width_in_dp </code> 和 <code class="language-plaintext highlighter-rouge">design_height_in_dp</code> 虽然都需要填写，但是 <strong>AndroidAutoSize</strong> 只会将高度和宽度其中的一个作为基准进行适配，一方作为基准，另一方就会变为备用，默认以宽度为基准进行适配，可以通过 <strong>AutoSizeConfig#setBaseOnWidth(Boolean)</strong> 不停的切换，这意味着最后运行到设备上的布局效果，在高度和宽度中只有一方可以和设计图上一模一样，另外一方会和设计图出现偏差，为什么不像 <strong>AndroidAutoLayout</strong> 一样，高和宽都以设计图的效果等比例完美呈现呢？这也很简单，你无法保证所有设备的高宽比例都和你设计图上的高宽比例一致，特别是在现在全面屏全面推出的情况下，如果这里不这样做的话，当你的项目运行在与设计图高宽比例不一致的设备上时，布局会出现严重的变形，这个几率非常大，详情请看 <a href="https://github.com/JessYanCoding/AndroidAutoSize/issues/8">这里</a></p>
  </li>
</ul>

<h3 id="自动运行是如何做到的">自动运行是如何做到的？</h3>
<p>很多人有疑惑，为什么使用者只需要在 <strong>AndroidManifest.xml</strong> 中填写一下 <strong>meta-data</strong> 标签，其他什么都不做，<strong>AndroidAutoSize</strong> 就能自动运行，并在 <strong>App</strong> 启动时自动解析 <strong>AndroidManifest.xml</strong> 中填写的设计图尺寸，这里很多人不敢相信，问我真的只需要填写下设计图尺寸框架就可以正常运行吗？难道使用了什么 <strong>黑科技</strong>?</p>

<p>其实这里并没有用到什么 <strong>黑科技</strong>，原理反而非常简单，只需要声明一个 <strong>ContentProvider</strong>，在它的 <strong>onCreate</strong> 方法中启动框架即可，在 <strong>App</strong> 启动时，系统会在 <strong>App</strong> 的主进程中自动实例化你声明的这个 <strong>ContentProvider</strong>，并调用它的 <strong>onCreate</strong> 方法，执行时机比 <strong>Application#onCreate</strong> 还靠前，可以做一些初始化的工作，<strong>get</strong> 到了吗？</p>

<p>这里需要注意的是，如果你的项目拥有多进程，系统只会在主进程中实例化一个你声明的 <strong>ContentProvider</strong>，并不会在其他非主进程中实例化 <strong>ContentProvider</strong>，如果在当前进程中 <strong>ContentProvider</strong> 没有被实例化，那 <strong>ContentProvider#onCreate</strong> 就不会被调用，你的初始化代码在当前进程中也就不会执行，这时就需要在 <strong>Application#onCreate</strong> 中调用下 <strong>ContentProvider#query</strong> 执行一下查询操作，这时 <strong>ContentProvider</strong> 就会在当前进程中实例化 (每个进程中只会保证有一个实例)，所以应用到框架中就是，如果你需要在多个进程中都进行屏幕适配，那就需要在 <strong>Application#onCreate</strong> 中调用 <strong>AutoSize#initCompatMultiProcess</strong> 方法</p>

<h3 id="进阶使用">进阶使用</h3>
<p>虽然 <strong>AndroidAutoSize</strong> 不需要其他过多的配置，只需要在 <strong>AndroidManifest.xml</strong> 中填写下设计图尺寸就能正常运行，但 <strong>AndroidAutoSize</strong> 还是为大家准备了很多可配置选项，尽最大可能满足大家日常开发中的所有扩展需求</p>

<p>所有的全局配置选项在 <a href="https://github.com/JessYanCoding/AndroidAutoSize/blob/master/demo/src/main/java/me/jessyan/autosize/demo/BaseApplication.java#L55">Demo</a> 中都有介绍，每个 <strong>API</strong> 中也都有详细的注释，在这里就不过多介绍了</p>

<h4 id="自定义-activity">自定义 Activity</h4>
<p>在 <strong>AndroidManifest.xml</strong> 中填写的设计图尺寸，是整个项目的全局设计图尺寸，但是如果某些 <strong>Activity</strong> 页面由于某些原因，设计师单独出图，这个页面的设计图尺寸和在 <strong>AndroidManifest.xml</strong> 中填写的设计图尺寸不一样该怎么办呢？不要急，<strong>AndroidAutoSize</strong> 已经为你考虑好了，让这个页面的 <strong>Activity</strong> 实现 <strong>CustomAdapt</strong> 接口即可实现你的需求，<strong>CustomAdapt</strong> 接口的第一个方法可以修改当前页面的设计图尺寸，第二个方法可以切换当前页面的适配基准，下面的注释都解释的很清楚</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomAdaptActivity</span> <span class="kd">extends</span> <span class="nc">AppCompatActivity</span> <span class="kd">implements</span> <span class="nc">CustomAdapt</span> <span class="o">{</span>

	 <span class="cm">/**
     * 是否按照宽度进行等比例适配 (为了保证在高宽比不同的屏幕上也能正常适配, 所以只能在宽度和高度之中选择一个作为基准进行适配)
     *
     * @return {@code true} 为按照宽度进行适配, {@code false} 为按照高度进行适配
     */</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">isBaseOnWidth</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
    <span class="o">}</span>

	 <span class="cm">/**
     * 这里使用 iPhone 的设计图, iPhone 的设计图尺寸为 750px * 1334px, 高换算成 dp 为 667 (1334px / 2 = 667dp)
     * &lt;p&gt;
     * 返回设计图上的设计尺寸, 单位 dp
     * {@link #getSizeInDp} 须配合 {@link #isBaseOnWidth()} 使用, 规则如下:
     * 如果 {@link #isBaseOnWidth()} 返回 {@code true}, {@link #getSizeInDp} 则应该返回设计图的总宽度
     * 如果 {@link #isBaseOnWidth()} 返回 {@code false}, {@link #getSizeInDp} 则应该返回设计图的总高度
     * 如果您不需要自定义设计图上的设计尺寸, 想继续使用在 AndroidManifest 中填写的设计图尺寸, {@link #getSizeInDp} 则返回 {@code 0}
     *
     * @return 设计图上的设计尺寸, 单位 dp
     */</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">float</span> <span class="nf">getSizeInDp</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="mi">667</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>如果某个 <strong>Activity</strong> 想放弃适配，让这个 <strong>Activity</strong> 实现 <strong>CancelAdapt</strong> 接口即可，比如修改 <strong>density</strong> 影响到了老项目中的某些 <strong>Activity</strong> 页面的布局效果，这时就可以让这个 <strong>Activity</strong> 实现 <strong>CancelAdapt</strong> 接口</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CancelAdaptActivity</span> <span class="kd">extends</span> <span class="nc">AppCompatActivity</span> <span class="kd">implements</span> <span class="nc">CancelAdapt</span> <span class="o">{</span>

<span class="o">}</span>
</code></pre></div></div>

<h4 id="自定义-fragment">自定义 Fragment</h4>
<p><strong>Fragment</strong> 的自定义方式和 <strong>Activity</strong> 是一样的，只不过在使用前需要先在 <strong>App</strong> 初始化时开启对 <strong>Fragment</strong> 的支持</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">AutoSizeConfig</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">setCustomFragment</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
</code></pre></div></div>

<p>实现 <strong>CustomAdapt</strong></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomAdaptFragment</span> <span class="kd">extends</span> <span class="nc">Fragment</span> <span class="kd">implements</span> <span class="nc">CustomAdapt</span> <span class="o">{</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">isBaseOnWidth</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">float</span> <span class="nf">getSizeInDp</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="mi">667</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>实现 <strong>CancelAdapt</strong></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CancelAdaptFragment</span> <span class="kd">extends</span> <span class="nc">Fragment</span> <span class="kd">implements</span> <span class="nc">CancelAdapt</span> <span class="o">{</span>

<span class="o">}</span>
</code></pre></div></div>

<h4 id="适配三方库页面">适配三方库页面</h4>
<p>在使用主单位时可以使用 <strong>ExternalAdaptManager</strong> 来实现在不修改三方库源码的情况下，适配三方库的所有页面 (<strong>Activity、Fragment</strong>)</p>

<p>由于 <strong>AndroidAutoSize</strong> 要求需要自定义适配参数或取消适配的页面必须实现 <strong>CustomAdapt</strong>、<strong>CancelAdapt</strong>，这时问题就来了，三方库是通过远程依赖的，我们无法修改它的源码，这时我们怎么让三方库的页面也能实现自定义适配参数或取消适配呢？别急，这个需求 <strong>AndroidAutoSize</strong> 也已经为你考虑好了，当然不会让你将三方库下载到本地然后改源码！</p>

<ul>
  <li>
    <p>通过 <strong>ExternalAdaptManager#addExternalAdaptInfoOfActivity(Class, ExternalAdaptInfo)</strong> 将需要自定义的类和自定义适配参数添加进方法即可替代实现 <strong>CustomAdapt</strong> 的方式，<a href="https://github.com/JessYanCoding/AndroidAutoSize/blob/master/demo/src/main/java/me/jessyan/autosize/demo/BaseApplication.java#L105">这里</a> 展示了使用方式，以及详细的注释</p>
  </li>
  <li>
    <p>通过 <strong>ExternalAdaptManager#addCancelAdaptOfActivity(Class)</strong> 将需要取消适配的类添加进方法即可替代实现 <strong>CancelAdapt</strong> 的方式，<a href="https://github.com/JessYanCoding/AndroidAutoSize/blob/master/demo/src/main/java/me/jessyan/autosize/demo/BaseApplication.java#L94">这里</a> 也展示了使用方式，以及详细的注释</p>
  </li>
</ul>

<p>需要注意的是 <strong>ExternalAdaptManager</strong> 的方法虽然可以添加任何类，但是只能支持 <strong>Activity、Fragment</strong>，并且 <strong>ExternalAdaptManager</strong> 是支持链式调用的，以便于持续添加多个页面</p>

<p>当然 <strong>ExternalAdaptManager</strong> 不仅可以对三方库的页面使用，也可以让自己项目中的 <strong>Activity、Fragment</strong> 不用实现 <strong>CustomAdapt</strong>、<strong>CancelAdapt</strong> 即可达到自定义适配参数和取消适配的功能</p>

<h2 id="副单位">副单位</h2>
<p>前面已经介绍了 <strong>副单位</strong> 的应用场景，这里就直接介绍 <strong>副单位</strong> 如何使用，<strong>副单位</strong> 的 <strong>Demo</strong> 在 <a href="https://github.com/JessYanCoding/AndroidAutoSize/blob/master/demo-subunits">demo-subunits</a></p>

<h3 id="基本使用-1">基本使用</h3>
<p>首先和 <strong>主单位</strong> 一样也需要先在 <strong>app</strong> 的 <strong>AndroidManifest.xml</strong> 中填写上设计图尺寸，但和 <strong>主单位</strong> 不一样的是，当在使用 <strong>副单位</strong> 时 <code class="language-plaintext highlighter-rouge">design_width_in_dp</code> 和 <code class="language-plaintext highlighter-rouge">design_height_in_dp</code> 的单位不需要一定是 <strong>dp</strong>，可以直接填写设计图的 <strong>px</strong> 尺寸，在布局文件中每个控件的大小也可以直接填写设计图上标注的 <strong>px</strong> 尺寸，无需再将 <strong>px</strong> 转换为 <strong>dp</strong>，这是 <strong>副单位的</strong> 特性之一，可以帮助大家提高开发效率</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;manifest&gt;</span>
    <span class="nt">&lt;application&gt;</span>            
        <span class="nt">&lt;meta-data</span>
            <span class="na">android:name=</span><span class="s">"design_width_in_dp"</span>
            <span class="na">android:value=</span><span class="s">"1080"</span><span class="nt">/&gt;</span>
        <span class="nt">&lt;meta-data</span>
            <span class="na">android:name=</span><span class="s">"design_height_in_dp"</span>
            <span class="na">android:value=</span><span class="s">"1920"</span><span class="nt">/&gt;</span>           
     <span class="nt">&lt;/application&gt;</span>           
<span class="nt">&lt;/manifest&gt;</span>
</code></pre></div></div>

<p>由于 <strong>AndroidAutoSize</strong> 提供了 <strong>pt、in、mm</strong> 三种类型的 <strong>副单位</strong> 供使用者选择，所以在使用 <strong>副单位</strong> 时，还需要在 <strong>APP</strong> 初始化时，通过 <strong>UnitsManager#setSupportSubunits(Subunits)</strong> 方法选择一个你喜欢的副单位，然后在布局文件中使用这个副单位进行布局，三种类型的副单位，其实效果都是一样，大家按喜欢的名字选择即可</p>

<p>由于使用副单位是为了彻底屏蔽修改 <strong>density</strong> 所造成的对三方库页面、三方库控件以及系统控件的布局效果的影响，所以在使用副单位时建议调用 <strong>UnitsManager#setSupportDP(false)</strong> 和 <strong>UnitsManager#setSupportSP(false)</strong>，关闭 <strong>AndroidAutoSize</strong> 对 <strong>dp</strong>  和 <strong>sp</strong> 的支持，<strong>AndroidAutoSize</strong> 为什么不在使用 <strong>副单位</strong> 时默认关闭对 <strong>dp</strong>、<strong>sp</strong> 的支持？因为允许同时支持 <strong>主单位</strong> 和 <strong>副单位</strong> 可以帮助使用者在 <strong>主单位</strong> 和 <strong>副单位</strong> 之间切换时更好的过渡，这点在前面就已经提到过</p>

<p><strong>UnitsManager</strong> 的详细使用方法，在 <a href="https://github.com/JessYanCoding/AndroidAutoSize/blob/master/demo-subunits/src/main/java/me/jessyan/autosize/demo/subunits/BaseApplication.java#L73">demo-subunits</a> 中都有展示，注释也十分详细</p>

<h3 id="自定义-activity-和-fragment">自定义 <strong>Activity</strong> 和 <strong>Fragment</strong></h3>
<p>在使用 <strong>副单位</strong> 时自定义 <strong>Activity</strong> 和 <strong>Fragment</strong> 的方式是和 <strong>主单位</strong> 是一样的，这里就不再过多介绍了</p>

<h3 id="适配三方库页面-1">适配三方库页面</h3>
<p>如果你的项目在使用 <strong>副单位</strong> 并且关闭了对 <strong>主单位 (dp、sp)</strong> 的支持，这时 <strong>ExternalAdaptManager</strong> 对三方库的页面是不起作用的，只对自己项目中的页面起作用，除非三方库的页面也使用了副单位 <strong>(pt、in、mm)</strong> 进行布局</p>

<p>其实 <strong>副单位</strong> 之所以能彻底屏蔽修改 <strong>density</strong> 所造成的对三方库页面、三方库控件以及系统控件的布局效果的影响，就是因为三方库页面、三方库控件以及系统控件基本上使用的都是 <strong>dp、sp</strong> 进行布局，所以只要 <strong>AndroidAutoSize</strong> 关闭了对 <strong>dp、sp</strong> 的支持，转而使用 <strong>副单位</strong> 进行布局，就能彻底屏蔽修改 <strong>density</strong> 所造成的对三方库页面、三方库控件以及系统控件的布局效果的影响</p>

<p>但这也同样意味着使用 <strong>副单位</strong> 就不能适配三方库的页面了，<strong>ExternalAdaptManager</strong> 也就对三方库的页面不起作用了</p>

<h2 id="布局实时预览">布局实时预览</h2>
<p>在开发阶段布局时的实时预览是一个很重要的环节，很多情况下 <strong>Android Studio</strong> 提供的默认预览设备并不能完全展示我们的设计图，所以我们就需要自己创建模拟设备，<strong>dp、pt、in、mm</strong> 这四种单位的模拟设备创建方法请看 <a href="https://github.com/JessYanCoding/AndroidAutoSize/blob/master/README-zh.md#preview">这里</a></p>

<h1 id="总结">总结</h1>
<p><a href="https://github.com/JessYanCoding/AndroidAutoSize"><strong>AndroidAutoSize</strong></a> 在经历了 <strong>240+ commit</strong>、<strong>60+ issues</strong>、<strong>6 个版本</strong> 的洗礼后，逐渐的稳定了下来，已经在上个星期发布了首个正式版，在这里要感谢将 <strong>AndroidAutoSize</strong> 接入到自己项目中的上千个使用者，感谢他们的信赖，<strong>AndroidAutoSize</strong> 创建的初衷就是为了让所有使用 <strong>今日头条屏幕适配方案</strong> 的使用者能有一个可以一起交流、沟通的聚集地，所以后面也会持续的收集并解决 <a href="https://github.com/JessYanCoding/AndroidAutoSize/issues/13">今日头条屏幕适配方案的常见问题</a>，让 <strong>今日头条屏幕适配方案</strong> 变得更加成熟、稳定</p>

<p>至此本系列的第三篇文章也就完结了，这也预示着这个系列连载的终结，这篇文章建议结合系列的第一篇文章 <a href="https://jessyan.me/autosize-introduce/">骚年你的屏幕适配方式该升级了!（一）-今日头条适配方案</a> 一起看，这样可以对 <strong>今日头条屏幕适配方案</strong> 有一个更深入的理解，如果你能将整个系列的文章都全部认真看完，那你对 <strong>Android</strong> 屏幕适配领域的相关知识绝对会有一个飞速的提升!</p>

<p>当你的项目需要切换某个框架时，你会怎么去考察、分析、对比现有的开源方案，并有足够的理由去选择或优化一个最适合自己项目的方案呢？其实整个系列文章可以看作是我怎么去选择同类型开源方案的过程，你以后当遇到同样的选择也可以参照我的思维方式去处理，当然如果以后面试官问到你屏幕适配相关的问题，你能将我如何选择、分析、对比已有方案的过程以及文章中的核心知识点告诉给面试官，那肯定比你直接说一句我使用的是某某开源库有价值得多</p>

<hr />

<p>以下是 <strong>骚年你的屏幕适配方式该升级了!</strong> 系列文章，欢迎转发以及分享:</p>

<ul>
  <li><a href="https://jessyan.me/autosize-introduce/">骚年你的屏幕适配方式该升级了!（一）-今日头条适配方案</a></li>
  <li><a href="https://jessyan.me/smallestwidth-introduce/">骚年你的屏幕适配方式该升级了!（二）-smallestWidth 限定符适配方案</a></li>
  <li><a href="https://jessyan.me/autosize-publish/">今日头条屏幕适配方案终极版正式发布!</a></li>
</ul>

<hr />
<p><strong>Hello 我叫 JessYan，如果您喜欢我的文章，可以在以下平台关注我</strong></p>

<ul>
  <li>个人主页: <a href="https://jessyan.me">https://jessyan.me</a></li>
  <li>GitHub: <a href="https://github.com/JessYanCoding">https://github.com/JessYanCoding</a></li>
  <li>掘金: <a href="https://gold.xitu.io/user/57a9dbd9165abd0061714613">https://gold.xitu.io/user/57a9dbd9165abd0061714613</a></li>
  <li>简书: <a href="https://www.jianshu.com/u/1d0c0bc634db">https://www.jianshu.com/u/1d0c0bc634db</a></li>
  <li>微博: <a href="https://weibo.com/u/1786262517">https://weibo.com/u/1786262517</a></li>
</ul>

<p>– The end</p>]]></content><author><name></name></author><category term="屏幕适配" /><summary type="html"><![CDATA[以下是 骚年你的屏幕适配方式该升级了! 系列文章，欢迎转发以及分享:]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" /><media:content medium="image" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">骚年你的屏幕适配方式该升级了!-smallestWidth 限定符适配方案</title><link href="https://jessyan.me/smallestwidth-introduce/" rel="alternate" type="text/html" title="骚年你的屏幕适配方式该升级了!-smallestWidth 限定符适配方案" /><published>2018-08-31T00:00:00+08:00</published><updated>2018-08-31T00:00:00+08:00</updated><id>https://jessyan.me/smallestwidth-introduce</id><content type="html" xml:base="https://jessyan.me/smallestwidth-introduce/"><![CDATA[<p>以下是 <strong>骚年你的屏幕适配方式该升级了!</strong> 系列文章，欢迎转发以及分享:</p>

<ul>
  <li><a href="https://jessyan.me/autosize-introduce/">骚年你的屏幕适配方式该升级了!（一）-今日头条适配方案</a></li>
  <li><a href="https://jessyan.me/smallestwidth-introduce/">骚年你的屏幕适配方式该升级了!（二）-smallestWidth 限定符适配方案</a></li>
  <li><a href="https://jessyan.me/autosize-publish/">今日头条屏幕适配方案终极版正式发布!</a></li>
</ul>

<h1 id="前言">前言</h1>
<p><strong>ok</strong>，根据上一篇文章 <a href="https://jessyan.me/autosize-introduce/">骚年你的屏幕适配方式该升级了!-今日头条适配方案</a> 的承诺，本文是这个系列的第二篇文章，这篇文章会详细讲解 <strong>smallestWidth 限定符屏幕适配方案</strong></p>

<p>了解我的朋友一定知道，<a href="https://github.com/JessYanCoding/MVPArms">MVPArms</a> 一直使用的是 <strong>鸿神</strong> 的 <strong>AndroidAutoLayout</strong> 屏幕适配方案，得益于 <strong>AndroidAutoLayout</strong> 的便捷，所以我对屏幕适配领域研究的不是很多，<strong>AndroidAutoLayout</strong> 停止维护后，我也一直在找寻着替代方案，直到 <strong>今日头条屏幕适配方案</strong> 刷屏，后来又无意间看到了 <strong>smallestWidth 限定符屏幕适配方案</strong>，这才慢慢的将研究方向转向了屏幕适配领域</p>

<p>最近一个月才开始慢慢恶补 <strong>Android</strong> 屏幕适配的相关知识，对这两个方案也进行了更深入的研究，可以说从一个小白慢慢成长而来，所以我明白小白的痛，因此在上一篇文章 <a href="https://jessyan.me/autosize-introduce/">骚年你的屏幕适配方式该升级了!-今日头条适配方案</a> 中，把 <strong>今日头条屏幕适配方案</strong> 讲得非常的细，尽量把每一个知识点都描述清晰，深怕小白漏掉每一个细节，这篇文章我也会延续上一篇文章的优良传统，将 <strong>smallestWidth 限定符屏幕适配方案</strong> 的每一个知识点都描述清晰</p>

<p>顺便说一句，感谢大家对 <a href="https://github.com/JessYanCoding/AndroidAutoSize/blob/master/README-zh.md">AndroidAutoSize</a> 的支持，我只是在上一篇文章中提了一嘴我刚发布的屏幕适配框架 <a href="https://github.com/JessYanCoding/AndroidAutoSize/blob/master/README-zh.md">AndroidAutoSize</a>，还没给出详细的介绍和原理剖析 (原计划在本系列的第三篇文章中发布)，<a href="https://github.com/JessYanCoding/AndroidAutoSize/blob/master/README-zh.md">AndroidAutoSize</a> 就被大家推上了 <a href="https://github.com/trending/java?since=daily">Github Trending</a>，一个多星期就拿了 <strong>2k+ stars</strong>，随着关注度的增加，我在这段时间里也累坏了，<strong>issues</strong> 就没断过，不到半个月就提交了 <strong>200</strong> 多次 <strong>commit</strong>，但累并快乐着，在这里要再次感谢大家对 <a href="https://github.com/JessYanCoding/AndroidAutoSize/blob/master/README-zh.md">AndroidAutoSize</a> 的认可</p>

<h1 id="谈谈对百分比库的看法">谈谈对百分比库的看法</h1>
<p>是这样的，在上篇文章中有一些兄弟提了一些观点，我很是认同，但是我站在执行者的角度来看待这个问题，也有一些不同的观点，以下是我在上篇文章中的回复</p>

<p><img src="https://upload-images.jianshu.io/upload_images/2974769-1da405902d76470b.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/550" alt="" /></p>

<p><strong>大家要注意了！这些观点其实针对的是所有以百分比缩放布局的库，而不只是今日头条屏幕适配方案，所以这些观点也同样适用于 smallestWidth 限定符屏幕适配方案，这点有很多人存在误解，所以一定要注意！</strong></p>

<p><img src="https://upload-images.jianshu.io/upload_images/2573196-dba88e959cd33bef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/550" alt="" /></p>

<p>上图的每一个方框都代表一种 <strong>Android</strong> 设备的屏幕，<strong>Android</strong> 的 <strong>系统碎片化</strong>、<strong>机型以及屏幕尺寸碎片化</strong>、<strong>屏幕分辨率碎片化</strong> 有多严重大家可以通过 <strong>友盟指数</strong> 了解一下，有些时候在某些事情的决断标准上，并不能按照事情的对错来决断，大多数情况还是要分析成本，收益等多种因素，通过利弊来决断，每个人的利弊标准又都不一样，所以每个人的观点也都会有差别，但也都应该得到尊重，所以我只是说说自己的观点，也不否认任何人的观点</p>

<p>方案是死的人是活的，在某些大屏手机或平板电脑上，您也可以采用其他适配方案和百分比库结合使用，比如针对某个屏幕区间的设备单独出一套设计图以显示比小屏幕手机更多更精细的内容，来达到与百分比库互补的效果，<strong>没有一个方案可以说自己是完美的，但我们能清晰的认识到不同方案的优缺点，将它们的优点相结合，才能应付更复杂的开发需求，产出最好的产品</strong></p>

<p><strong>友情提示:</strong> 下面要介绍的 <strong>smallestWidth 限定符屏幕适配方案</strong>，原理也同样是按照百分比缩放布局，理论上也会存在上面所说的 <strong>大屏手机和小屏手机显示的内容相同</strong> 的问题，选择与否请仔细斟酌</p>

<h1 id="简介-smallestwidth-限定符适配方案">简介 smallestWidth 限定符适配方案</h1>
<p>这个方案的的使用方式和我们平时在布局中引用 <strong>dimens</strong> 无异，核心点在于生成 <strong>dimens.xml</strong> 文件，但是已经有大神帮我们做了这 <a href="https://www.jianshu.com/p/1302ad5a4b04">一步</a></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>├── src/main
│   ├── res
│   ├── ├──values
│   ├── ├──values-800x480
│   ├── ├──values-860x540
│   ├── ├──values-1024x600
│   ├── ├──values-1024x768
│   ├── ├──...
│   ├── ├──values-2560x1440
</code></pre></div></div>

<p>如果有人还记得上面这种 <strong>宽高限定符屏幕适配方案</strong> 的话，就可以把 <strong>smallestWidth 限定符屏幕适配方案</strong> 当成这种方案的升级版，<strong>smallestWidth 限定符屏幕适配方案</strong> 只是把 <strong>dimens.xml</strong> 文件中的值从 <strong>px</strong> 换成了 <strong>dp</strong>，原理和使用方式都是没变的，这些在上面的文章中都有介绍，下面就直接开始剖析原理，<strong>smallestWidth 限定符屏幕适配方案</strong> 长这样👇</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>├── src/main
│   ├── res
│   ├── ├──values
│   ├── ├──values-sw320dp
│   ├── ├──values-sw360dp
│   ├── ├──values-sw400dp
│   ├── ├──values-sw411dp
│   ├── ├──values-sw480dp
│   ├── ├──...
│   ├── ├──values-sw600dp
│   ├── ├──values-sw640dp
</code></pre></div></div>

<h1 id="原理">原理</h1>
<p>其实 <strong>smallestWidth 限定符屏幕适配方案</strong> 的原理也很简单，开发者先在项目中根据主流屏幕的 <strong>最小宽度 (smallestWidth)</strong> 生成一系列 <strong>values-sw&lt;N&gt;dp</strong> 文件夹 (含有 <strong>dimens.xml</strong> 文件)，当把项目运行到设备上时，系统会根据当前设备屏幕的 <strong>最小宽度 (smallestWidth)</strong> 去匹配对应的 <strong>values-sw&lt;N&gt;dp</strong> 文件夹，而对应的 <strong>values-sw&lt;N&gt;dp</strong> 文件夹中的 <strong>dimens.xml</strong> 文字中的值，又是根据当前设备屏幕的 <strong>最小宽度 (smallestWidth)</strong> 而定制的，所以一定能适配当前设备</p>

<p>如果系统根据当前设备屏幕的 <strong>最小宽度 (smallestWidth)</strong> 没找到对应的 <strong>values-sw&lt;N&gt;dp</strong> 文件夹，则会去寻找与之 <strong>最小宽度 (smallestWidth)</strong> 相近的 <strong>values-sw&lt;N&gt;dp</strong> 文件夹，系统只会寻找小于或等于当前设备 <strong>最小宽度 (smallestWidth)</strong> 的 <strong>values-sw&lt;N&gt;dp</strong>，这就是优于 <strong>宽高限定符屏幕适配方案</strong> 的容错率，并且也可以少生成很多 <strong>values-sw&lt;N&gt;dp</strong> 文件夹，减轻 <strong>App</strong> 的体积</p>

<h2 id="什么是-smallestwidth">什么是 smallestWidth</h2>
<p><strong>smallestWidth</strong> 翻译为中文的意思就是 <strong>最小宽度</strong>，那这个 <strong>最小宽度</strong> 是什么意思呢？</p>

<p>系统会根据当前设备屏幕的 <strong>最小宽度</strong> 来匹配 <strong>values-sw&lt;N&gt;dp</strong>，为什么不是根据 <strong>宽度</strong> 来匹配，而要加上 <strong>最小</strong> 这两个字呢？</p>

<p>这就要说到，移动设备都是允许屏幕可以旋转的，当屏幕旋转时，屏幕的高宽就会互换，加上 <strong>最小</strong> 这两个字，是因为这个方案是不区分屏幕方向的，它只会把屏幕的高度和宽度中值最小的一方认为是 <strong>最小宽度</strong>，这个 <strong>最小宽度</strong> 是根据屏幕来定的，是固定不变的，意思是不管您怎么旋转屏幕，只要这个屏幕的高度大于宽度，那系统就只会认定宽度的值为 <strong>最小宽度</strong>，反之如果屏幕的宽度大于高度，那系统就会认定屏幕的高度的值为 <strong>最小宽度</strong></p>

<p>如果想让屏幕宽度随着屏幕的旋转而做出改变该怎么办呢？可以再根据 <strong>values-w&lt;N&gt;dp</strong> (去掉 <strong>sw</strong> 中的 <strong>s</strong>) 生成一套资源文件</p>

<p>如果想区分屏幕的方向来做适配该怎么办呢？那就只有再根据 <strong>屏幕方向限定符</strong> 生成一套资源文件咯，后缀加上 <strong>-land</strong> 或 <strong>-port</strong> 即可，像这样，<strong>values-sw400dp-land (最小宽度 400 dp 横向)</strong>，<strong>values-sw400dp-port (最小宽度 400 dp 纵向)</strong></p>

<h2 id="smallestwidth-的值是怎么算的">smallestWidth 的值是怎么算的</h2>
<p>要先算出当前设备的 <strong>smallestWidth</strong> 值我们才能知道当前设备该匹配哪个 <strong>values-sw&lt;N&gt;dp</strong> 文件夹</p>

<p>ok，还是按照上一篇文章的叙述方式，现在来举栗说明，帮助大家更好理解</p>

<p>我们假设设备的屏幕信息是 <strong>1920 * 1080</strong>、<strong>480 dpi</strong></p>

<p>根据上面的规则我们要在屏幕的高度和宽度中选择值最小的一方作为最小宽度，<strong>1080 &lt; 1920</strong>，明显 <strong>1080 px</strong> 就是我们要找的 <strong>最小宽度</strong> 的值，但 <strong>最小宽度</strong> 的单位是 <strong>dp</strong>，所以我们要把 <strong>px</strong> 转换为 <strong>dp</strong></p>

<p>帮助大家再巩固下基础，下面的公式一定不能再忘了！</p>

<p><strong>px / density = dp</strong>，<strong>DPI / 160 = density</strong>，所以最终的公式是 <strong>px / (DPI / 160) = dp</strong></p>

<p>所以我们得到的 <strong>最小宽度</strong> 的值是 <strong>360 dp (1080 / (480 / 160) = 360)</strong></p>

<p>现在我们已经算出了当前设备的最小宽度是 <strong>360 dp</strong>，我们晓得系统会根据这个 <strong>最小宽度</strong> 帮助我们匹配到 <strong>values-sw360dp</strong> 文件夹下的 <strong>dimens.xml</strong> 文件，如果项目中没有 <strong>values-sw360dp</strong> 这个文件夹，系统才会去匹配相近的 <strong>values-sw&lt;N&gt;dp</strong> 文件夹</p>

<p><strong>dimens.xml</strong> 文件是整个方案的核心所在，所以接下来我们再来看看 <strong>values-sw360dp</strong> 文件夹中的这个 <strong>dimens.xml</strong> 是根据什么原理生成的</p>

<h2 id="dimensxml-生成原理">dimens.xml 生成原理</h2>
<p>因为我们在项目布局中引用的 <strong>dimens</strong> 的实际值，来源于根据当前设备屏幕的 <strong>最小宽度</strong> 所匹配的 <strong>values-sw&lt;N&gt;dp</strong> 文件夹中的 <strong>dimens.xml</strong>，所以搞清楚 <strong>dimens.xml</strong> 的生成原理，有助于我们理解 <strong>smallestWidth 限定符屏幕适配方案</strong></p>

<p>说到 <strong>dimens.xml</strong> 的生成，就要涉及到两个因数，第一个因素是 <strong>最小宽度基准值</strong>，第二个因素就是您的项目需要适配哪些 <strong>最小宽度</strong>，通俗理解就是需要生成多少个 <strong>values-sw&lt;N&gt;dp</strong> 文件夹</p>

<h3 id="第一个因素">第一个因素</h3>
<p><strong>最小宽度基准值</strong> 是什么意思呢？简单理解就是您需要把设备的屏幕宽度分为多少份，假设我们现在把项目的 <strong>最小宽度基准值</strong> 定为 <strong>360</strong>，那这个方案就会理解为您想把所有设备的屏幕宽度都分为 <strong>360</strong> 份，方案会帮您在 <strong>dimens.xml</strong> 文件中生成 <strong>1</strong> 到 <strong>360</strong> 的 <strong>dimens</strong> 引用，比如 <strong>values-sw360dp</strong> 中的 <strong>dimens.xml</strong> 是长这样的</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;resources&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_1"</span><span class="nt">&gt;</span>1dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_2"</span><span class="nt">&gt;</span>2dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_3"</span><span class="nt">&gt;</span>3dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_4"</span><span class="nt">&gt;</span>4dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_5"</span><span class="nt">&gt;</span>5dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_6"</span><span class="nt">&gt;</span>6dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_7"</span><span class="nt">&gt;</span>7dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_8"</span><span class="nt">&gt;</span>8dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_9"</span><span class="nt">&gt;</span>9dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_10"</span><span class="nt">&gt;</span>10dp<span class="nt">&lt;/dimen&gt;</span>
	...
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_356"</span><span class="nt">&gt;</span>356dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_357"</span><span class="nt">&gt;</span>357dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_358"</span><span class="nt">&gt;</span>358dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_359"</span><span class="nt">&gt;</span>359dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_360"</span><span class="nt">&gt;</span>360dp<span class="nt">&lt;/dimen&gt;</span>
<span class="nt">&lt;/resources&gt;</span>
</code></pre></div></div>

<p><strong>values-sw360dp</strong> 指的是当前设备屏幕的 <strong>最小宽度</strong> 为 <strong>360dp</strong> (该设备高度大于宽度，则最小宽度就是宽度，所以该设备宽度为 <strong>360dp</strong>)，把屏幕宽度分为 <strong>360</strong> 份，刚好每份等于 <strong>1dp</strong>，所以每个引用都递增 <strong>1dp</strong>，值最大的 <strong>dimens</strong> 引用 <strong>dp_360</strong> 值也是 <strong>360dp</strong>，刚好覆盖屏幕宽度</p>

<p>下面再来看看将 <strong>最小宽度基准值</strong> 定为 <strong>360</strong> 时，<strong>values-sw400dp</strong> 中的 <strong>dimens.xml</strong> 长什么样</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;resources&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_1"</span><span class="nt">&gt;</span>1.1111dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_2"</span><span class="nt">&gt;</span>2.2222dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_3"</span><span class="nt">&gt;</span>3.3333dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_4"</span><span class="nt">&gt;</span>4.4444dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_5"</span><span class="nt">&gt;</span>5.5556dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_6"</span><span class="nt">&gt;</span>6.6667dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_7"</span><span class="nt">&gt;</span>7.7778dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_8"</span><span class="nt">&gt;</span>8.8889dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_9"</span><span class="nt">&gt;</span>10.0000dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_10"</span><span class="nt">&gt;</span>11.1111dp<span class="nt">&lt;/dimen&gt;</span>
	...
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_355"</span><span class="nt">&gt;</span>394.4444dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_356"</span><span class="nt">&gt;</span>395.5556dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_357"</span><span class="nt">&gt;</span>396.6667dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_358"</span><span class="nt">&gt;</span>397.7778dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_359"</span><span class="nt">&gt;</span>398.8889dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_360"</span><span class="nt">&gt;</span>400.0000dp<span class="nt">&lt;/dimen&gt;</span>
<span class="nt">&lt;/resources&gt;</span>
</code></pre></div></div>
<p><strong>values-sw400dp</strong> 指的是当前设备屏幕的 <strong>最小宽度</strong> 为 <strong>400dp</strong> (该设备高度大于宽度，则最小宽度就是宽度，所以该设备宽度为 <strong>400dp</strong>)，把屏幕宽度同样分为 <strong>360份</strong>，这时每份就等于 <strong>1.1111dp</strong> 了，每个引用都递增 <strong>1.1111dp</strong>，值最大的 <strong>dimens</strong> 引用 <strong>dp_360</strong> 同样刚好覆盖屏幕宽度，为 <strong>400dp</strong></p>

<p>通过两个 <strong>dimens.xml</strong> 文件的比较，<strong>dimens.xml</strong> 的生成原理一目了然，方案会先确定 <strong>最小宽度基准值</strong>，然后将每个 <strong>values-sw&lt;N&gt;dp</strong> 中的 <strong>dimens.xml</strong> 文件都分配与 <strong>最小宽度基准值</strong> 相同的份数，再根据公式 <strong>屏幕最小宽度 / 份数 (最小宽度基准值)</strong> 求出每份占多少 <strong>dp</strong>，保证不管在哪个 <strong>values-sw&lt;N&gt;dp</strong> 中，<strong>份数 (最小宽度基准值) * 每份占的 dp 值</strong> 的结果都是刚好覆盖屏幕宽度，所以在 <strong>份数</strong> 不变的情况下，只需要根据屏幕的宽度在不同的设备上动态调整 <strong>每份占的 dp 值</strong>，就能完成适配</p>

<p>这样就能保证不管将项目运行到哪个设备上，只要当前设备能匹配到对应的 <strong>values-sw&lt;N&gt;dp</strong> 文件夹，那布局中的 <strong>dimens</strong> 引用就能根据当前屏幕的情况进行缩放，保证能完美适配，如果没有匹配到对应的 <strong>values-sw&lt;N&gt;dp</strong> 文件夹，也没关系，它会去寻找与之相近的 <strong>values-sw&lt;N&gt;dp</strong> 文件夹，虽然在这种情况下，布局中的 <strong>dimens</strong> 引用的值可能有些许误差，但是也能保证最大程度的完成适配</p>

<p>说到这里，那大家就应该就会明白我为什么会说 <strong>smallestWidth 限定符屏幕适配方案</strong> 的原理也同样是按百分比进行布局，如果在布局中，一个 <strong>View</strong> 的宽度引用 <strong>dp_100</strong>，那不管运行到哪个设备上，这个 <strong>View</strong> 的宽度都是当前设备屏幕总宽度的 <strong>360分之100</strong>，前提是项目提供有当前设备屏幕对应的 <strong>values-sw&lt;N&gt;dp</strong>，如果没有对应的 <strong>values-sw&lt;N&gt;dp</strong>，就会去寻找相近的 <strong>values-sw&lt;N&gt;dp</strong>，这时就会存在误差了，至于误差是大是小，这就要看您的第二个因数怎么分配了</p>

<p>其实 <strong>smallestWidth 限定符屏幕适配方案</strong> 的原理和 <strong>今日头条屏幕适配方案</strong> 挺像的，<strong>今日头条屏幕适配方案</strong> 是根据屏幕的宽度或高度动态调整每个设备的 <strong>density</strong> (每 <strong>dp</strong> 占当前设备屏幕多少像素)，而 <strong>smallestWidth 限定符屏幕适配方案</strong> 同样是根据屏幕的宽度动态调整每个设备 <strong>每份占的 dp 值</strong></p>

<h3 id="第二个因素">第二个因素</h3>
<p>第二个因数是需要适配哪些 <strong>最小宽度</strong>？比如您想适配的 <strong>最小宽度</strong> 有 <strong>320dp</strong>、<strong>360dp</strong>、<strong>400dp</strong>、<strong>411dp</strong>、<strong>480dp</strong>，那方案就会为您的项目生成 <strong>values-sw320dp</strong>、<strong>values-sw360dp</strong>、<strong>values-sw400dp</strong>、<strong>values-sw411dp</strong>、<strong>values-sw480dp</strong> 这几个资源文件夹，像这样👇</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>├── src/main
│   ├── res
│   ├── ├──values
│   ├── ├──values-sw320dp
│   ├── ├──values-sw360dp
│   ├── ├──values-sw400dp
│   ├── ├──values-sw411dp
│   ├── ├──values-sw480dp
</code></pre></div></div>

<p>方案会为您需要适配的 <strong>最小宽度</strong>，在项目中生成一系列对应的 <strong>values-sw&lt;N&gt;dp</strong>，在前面也说了，如果某个设备没有为它提供对应的 <strong>values-sw&lt;N&gt;dp</strong>，那它就会去寻找相近的 <strong>values-sw&lt;N&gt;dp</strong>，但如果这个相近的 <strong>values-sw&lt;N&gt;dp</strong> 与期望的 <strong>values-sw&lt;N&gt;dp</strong> 差距太大，那适配效果也就会大打折扣</p>

<p>那是不是 <strong>values-sw&lt;N&gt;dp</strong> 文件夹生成的越多，覆盖越多市面上的设备，就越好呢？</p>

<p>也不是，因为每个 <strong>values-sw&lt;N&gt;dp</strong> 文件夹其实都会占用一定的 <strong>App</strong> 体积，<strong>values-sw&lt;N&gt;dp</strong> 文件夹越多，<strong>App</strong> 的体积也就会越大</p>

<p>所以一定要合理分配 <strong>values-sw&lt;N&gt;dp</strong>，以越少的 <strong>values-sw&lt;N&gt;dp</strong> 文件夹，覆盖越多的机型</p>

<h1 id="验证方案可行性">验证方案可行性</h1>
<p>原理讲完了，我们还是按照老规矩，来验证一下这个方案是否可行？</p>

<p>假设设计图总宽度为 <strong>375 dp</strong>，一个 <strong>View</strong> 在这个设计图上的尺寸是 <strong>50dp * 50dp</strong>，这个 <strong>View</strong> 的宽度占整个设计图宽度的 <strong>13.3%</strong> (<strong>50 / 375 = 0.133</strong>)</p>

<p>在使用 <strong>smallestWidth 限定符屏幕适配方案</strong> 时，需要提供 <strong>最小宽度基准值</strong> 和需要适配哪些 <strong>最小宽度</strong>，我们就把 <strong>最小宽度基准值</strong> 设置为 <strong>375</strong> (和 <strong>设计图</strong> 一致)，这时方案就会为我们需要适配的 <strong>最小宽度</strong> 生成对应的 <strong>values-sw&lt;N&gt;dp</strong> 文件夹，文件夹中的 <strong>dimens.xml</strong> 文件是由从 <strong>1</strong> 到 <strong>375</strong> 组成的 <strong>dimens</strong> 引用，把所有设备的屏幕宽度都分为 <strong>375</strong> 份，所以在布局文件中我们应该把这个 <strong>View</strong> 的高宽都引用 <strong>dp_50</strong></p>

<p>下面就来验证下在使用 <strong>smallestWidth 限定符屏幕适配方案</strong> 的情况下，这个 <strong>View</strong> 与屏幕宽度的比例在分辨率不同的设备上是否还能保持和设计图中的比例一致</p>

<h2 id="验证设备-1">验证设备 1</h2>
<p><strong>设备 1</strong> 的屏幕总宽度为 <strong>1080 px</strong>，屏幕总高度为 <strong>1920 px</strong>，<strong>DPI</strong> 为 <strong>480</strong></p>

<p><strong>设备 1</strong> 的屏幕高度大于屏幕宽度，所以 <strong>设备 1</strong> 的 <strong>最小宽度</strong> 为屏幕宽度，再根据公式 <strong>px / (DPI / 160) = dp</strong>，求出 <strong>设备 1</strong> 的 <strong>最小宽度</strong> 的值为 <strong>360 dp</strong> (<strong>1080 / (480 / 160) = 360</strong>)</p>

<p>根据 <strong>设备 1</strong> 的 <strong>最小宽度</strong> 应该匹配的是 <strong>values-sw360dp</strong> 这个文件夹，假设 <strong>values-sw360dp</strong> 文件夹及里面的 <strong>dimens.xml</strong> 已经生成，且是按 <strong>最小宽度基准值</strong> 为 <strong>375</strong> 生成的，<strong>360 / 375 = 0.96</strong>，所以每份占的 <strong>dp</strong> 值为 <strong>0.96</strong>，<strong>dimens.xml</strong> 里面的内容是长下面这样的👇</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;resources&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_1"</span><span class="nt">&gt;</span>0.96dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_2"</span><span class="nt">&gt;</span>1.92dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_3"</span><span class="nt">&gt;</span>2.88dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_4"</span><span class="nt">&gt;</span>3.84dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_5"</span><span class="nt">&gt;</span>4.8dp<span class="nt">&lt;/dimen&gt;</span>
	...
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_50"</span><span class="nt">&gt;</span>48dp<span class="nt">&lt;/dimen&gt;</span>
	...
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_371"</span><span class="nt">&gt;</span>356.16dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_372"</span><span class="nt">&gt;</span>357.12dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_373"</span><span class="nt">&gt;</span>358.08dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_374"</span><span class="nt">&gt;</span>359.04dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_375"</span><span class="nt">&gt;</span>360dp<span class="nt">&lt;/dimen&gt;</span>
<span class="nt">&lt;/resources&gt;</span>
</code></pre></div></div>

<p>可以看到这个 <strong>View</strong> 在布局中引用的 <strong>dp_50</strong>，最终在 <strong>values-sw360dp</strong> 中定格在了 <strong>48 dp</strong>，所以这个 <strong>View</strong> 在 <strong>设备 1</strong> 上的高宽都为 <strong>48 dp</strong>，系统最后会将高宽都换算成 <strong>px</strong>，根据公式 <strong>dp * (DPI / 160) = px</strong>，所以这个 <strong>View</strong> 的高宽换算为 <strong>px</strong> 后等于 <strong>144 px</strong> (<strong>48 * (480 / 160) = 144</strong>)</p>

<p><strong>144 / 1080 = 0.133</strong>，<strong>View</strong> 的实际宽度与 <strong>屏幕总宽度</strong> 的比例和 <strong>View</strong> 在设计图中的比例一致 (<strong>50 / 375 = 0.133</strong>)，所以完成了等比例缩放</p>

<p>某些设备的高宽是和 <strong>设备 1</strong> 相同的，但是 <strong>DPI</strong> 可能不同，而由于 <strong>smallestWidth 限定符屏幕适配方案</strong> 并没有像 <strong>今日头条屏幕适配方案</strong> 一样去自行修改 <strong>density</strong>，所以系统就会使用默认的公式 <strong>DPI / 160</strong> 求出 <strong>density</strong>，<strong>density</strong> 又会影响到 <strong>dp</strong> 和 <strong>px</strong> 的换算，因此 <strong>DPI</strong> 的变化，是有可能会影响到 <strong>smallestWidth 限定符屏幕适配方案</strong> 的</p>

<p>所以我们再来试试在这种特殊情况下 <strong>smallestWidth 限定符屏幕适配方案</strong> 是否也能完成适配</p>

<h2 id="验证设备-2">验证设备 2</h2>
<p><strong>设备 2</strong> 的屏幕总宽度为 <strong>1080 px</strong>，屏幕总高度为 <strong>1920 px</strong>，<strong>DPI</strong> 为 <strong>420</strong></p>

<p><strong>设备 2</strong> 的屏幕高度大于屏幕宽度，所以 <strong>设备 2</strong> 的 <strong>最小宽度</strong> 为屏幕宽度，再根据公式 <strong>px / (DPI / 160) = dp</strong>，求出 <strong>设备 2</strong> 的 <strong>最小宽度</strong> 的值为 <strong>411.429 dp</strong> (<strong>1080 / (420 / 160) = 411.429</strong>)</p>

<p>根据 <strong>设备 2</strong> 的 <strong>最小宽度</strong> 应该匹配的是 <strong>values-sw411dp</strong> 这个文件夹，假设 <strong>values-sw411dp</strong> 文件夹及里面的 <strong>dimens.xml</strong> 已经生成，且是按 <strong>最小宽度基准值</strong> 为 <strong>375</strong> 生成的，<strong>411 / 375 = 1.096</strong>，所以每份占的 <strong>dp</strong> 值为 <strong>1.096</strong>，<strong>dimens.xml</strong> 里面的内容是长下面这样的👇</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;resources&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_1"</span><span class="nt">&gt;</span>1.096dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_2"</span><span class="nt">&gt;</span>2.192dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_3"</span><span class="nt">&gt;</span>3.288dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_4"</span><span class="nt">&gt;</span>4.384dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_5"</span><span class="nt">&gt;</span>5.48dp<span class="nt">&lt;/dimen&gt;</span>
	...
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_50"</span><span class="nt">&gt;</span>54.8dp<span class="nt">&lt;/dimen&gt;</span>
	...
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_371"</span><span class="nt">&gt;</span>406.616dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_372"</span><span class="nt">&gt;</span>407.712dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_373"</span><span class="nt">&gt;</span>408.808dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_374"</span><span class="nt">&gt;</span>409.904dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"dp_375"</span><span class="nt">&gt;</span>411dp<span class="nt">&lt;/dimen&gt;</span>
<span class="nt">&lt;/resources&gt;</span>
</code></pre></div></div>

<p>可以看到这个 <strong>View</strong> 在布局中引用的 <strong>dp_50</strong>，最终在 <strong>values-sw411dp</strong> 中定格在了 <strong>54.8dp</strong>，所以这个 <strong>View</strong> 在 <strong>设备 2</strong> 上的高宽都为 <strong>54.8 dp</strong>，系统最后会将高宽都换算成 <strong>px</strong>，根据公式 <strong>dp * (DPI / 160) = px</strong>，所以这个 <strong>View</strong> 的高宽换算为 <strong>px</strong> 后等于 <strong>143.85 px</strong> (<strong>54.8 * (420 / 160) = 143.85</strong>)</p>

<p><strong>143.85 / 1080 = 0.133</strong>，<strong>View</strong> 的实际宽度与 <strong>屏幕总宽度</strong> 的比例和 <strong>View</strong> 在设计图中的比例一致 (<strong>50 / 375 = 0.133</strong>)，所以完成了等比例缩放</p>

<p>虽然 <strong>View</strong> 在 <strong>设备 2</strong> 上的高宽是 <strong>143.85 px</strong>，比 <strong>设备 1</strong> 的 <strong>144 px</strong> 少了 <strong>0.15 px</strong>，但是误差非常小，整体的比例并没有发生太大的变化，是完全可以接受的</p>

<p>这个误差是怎么引起的呢，因为 <strong>设备 2</strong> 的 <strong>最小宽度</strong> 的实际值是 <strong>411.429 dp</strong>，但是匹配的 <strong>values-sw411dp</strong> 舍去了小数点后面的位数 (<strong>切记！系统会去寻找小于或等于 411.429 dp 的 values-sw&lt;N&gt;dp，所以 values-sw412dp 这个文件夹，设备 2 是匹配不了的</strong>)，所以才存在了一定的误差，因此上面介绍的第二个因数是非常重要的，这直接决定误差是大还是小</p>

<p>可以看到即使在高宽一样但 <strong>DPI</strong> 不一样的设备上，<strong>smallestWidth 限定符屏幕适配方案</strong> 也能完成等比例适配，证明这个方案是可行的，如果大家还心存疑虑，也可以再试试其他分辨率的设备，其实到最后得出的比例都是在 <strong>0.133</strong> 左右，唯一的变数就是第二个因数，如果您生成的 <strong>values-sw&lt;N&gt;dp</strong> 与设备实际的 <strong>最小宽度</strong> 差别不大，那误差也就在能接受的范围内，如果差别很大，那就直接 <strong>GG</strong></p>

<h1 id="优点">优点</h1>
<ol>
  <li>
    <p>非常稳定，极低概率出现意外</p>
  </li>
  <li>
    <p>不会有任何性能的损耗</p>
  </li>
  <li>
    <p>适配范围可自由控制，不会影响其他三方库</p>
  </li>
  <li>
    <p>在插件的配合下，学习成本低</p>
  </li>
</ol>

<h1 id="缺点">缺点</h1>
<ol>
  <li>
    <p>在布局中引用 <strong>dimens</strong> 的方式，虽然学习成本低，但是在日常维护修改时较麻烦</p>
  </li>
  <li>
    <p>侵入性高，如果项目想切换为其他屏幕适配方案，因为每个 <strong>Layout</strong> 文件中都存在有大量 <strong>dimens</strong> 的引用，这时修改起来工作量非常巨大，切换成本非常高昂</p>
  </li>
  <li>
    <p>无法覆盖全部机型，想覆盖更多机型的做法就是生成更多的资源文件，但这样会增加 <strong>App</strong> 体积，在没有覆盖的机型上还会出现一定的误差，所以有时需要在适配效果和占用空间上做一些抉择</p>
  </li>
  <li>
    <p>如果想使用 <strong>sp</strong>，也需要生成一系列的 <strong>dimens</strong>，导致再次增加 <strong>App</strong> 的体积</p>
  </li>
  <li>
    <p>不能自动支持横竖屏切换时的适配，如上文所说，如果想自动支持横竖屏切换时的适配，需要使用 <strong>values-w&lt;N&gt;dp</strong> 或 <strong>屏幕方向限定符</strong> 再生成一套资源文件，这样又会再次增加 <strong>App</strong> 的体积</p>
  </li>
  <li>
    <p>不能以高度为基准进行适配，考虑到这个方案的名字本身就叫 <strong>最小宽度限定符适配方案</strong>，所以在使用这个方案之前就应该要知道这个方案只能以宽度为基准进行适配，为什么现在的屏幕适配方案只能以高度或宽度其中的一个作为基准进行适配，请看 <a href="https://github.com/JessYanCoding/AndroidAutoSize/issues/8">这里</a></p>
  </li>
</ol>

<h1 id="使用中的问题">使用中的问题</h1>
<p>这时有人就会问了，设计师给的设计图只标注了 <strong>px</strong>，使用这个方案时，那不是还要先将 <strong>px</strong> 换算成 <strong>dp</strong>？</p>

<p>其实也可以不用换算的，那这是什么骚操作呢？</p>

<p>很简单，你把设计图的 <strong>px</strong> 总宽度设置成 <strong>最小宽度基准值</strong> 就可以了，还是以前面验证可行性的例子</p>

<p>我们在前面验证可行性时把 <strong>最小宽度基准值</strong> 设置成了 <strong>375</strong>，为什么是 <strong>375</strong> 呢？因为设计图的总宽度为 <strong>375 dp</strong>，如果换算成 <strong>px</strong>，总宽度就是 <strong>750 px</strong>，我们这时把 <strong>最小宽度基准值</strong> 设置成 <strong>750</strong>，然后看看 <strong>values-sw360dp</strong> 中的 <strong>dimens.xml</strong> 长什么样👇</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;resources&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"px_1"</span><span class="nt">&gt;</span>0.48dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"px_2"</span><span class="nt">&gt;</span>0.96dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"px_3"</span><span class="nt">&gt;</span>1.44dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"px_4"</span><span class="nt">&gt;</span>1.92dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"px_5"</span><span class="nt">&gt;</span>2.4dp<span class="nt">&lt;/dimen&gt;</span>
	...
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"px_50"</span><span class="nt">&gt;</span>24dp<span class="nt">&lt;/dimen&gt;</span>
	...
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"px_100"</span><span class="nt">&gt;</span>48dp<span class="nt">&lt;/dimen&gt;</span>
	...
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"px_746"</span><span class="nt">&gt;</span>358.08dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"px_747"</span><span class="nt">&gt;</span>358.56dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"px_748"</span><span class="nt">&gt;</span>359.04dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"px_749"</span><span class="nt">&gt;</span>359.52dp<span class="nt">&lt;/dimen&gt;</span>
	<span class="nt">&lt;dimen</span> <span class="na">name=</span><span class="s">"px_750"</span><span class="nt">&gt;</span>360dp<span class="nt">&lt;/dimen&gt;</span>
<span class="nt">&lt;/resources&gt;</span>
</code></pre></div></div>

<p><strong>360 dp</strong> 被分成了 <strong>750</strong> 份，相比之前的 <strong>375</strong> 份，现在 <strong>每份占的 dp 值</strong> 正好减少了一半，还记得在验证可行性的例子中那个 <strong>View</strong> 的尺寸是多少吗？<strong>50dp * 50dp</strong>，如果设计图只标注 <strong>px</strong>，那这个 <strong>View</strong> 在设计图上的的尺寸应该是 <strong>100px * 100px</strong>，那我们直接根据设计图上标注的 <strong>px</strong>，想都不用想直接在布局中引用 <strong>px_100</strong> 就可以了，因为在 <strong>375</strong> 份时的 <strong>dp_50</strong> 刚好等于 <strong>750</strong> 份时的 <strong>px_100</strong> (值都是 <strong>48 dp</strong>)，所以这时的适配效果和之前验证可行性时的适配效果没有任何区别</p>

<p>看懂了吗？直接将 <strong>最小宽度基准值</strong> 和布局中的引用都以 <strong>px</strong> 作为单位就可以直接填写设计图上标注的 <strong>px</strong>！</p>

<h1 id="总结">总结</h1>
<p>关于文中所列出的优缺点，列出的缺点数量确实比列出的优点数量多，但 <strong>缺点 3</strong>，<strong>缺点 4</strong>，<strong>缺点 5</strong> 其实都可以归纳于 <strong>占用 App 体积</strong> 这一个缺点，因为他们都可以通过增加资源文件来解决问题，而 <strong>缺点 6</strong> 则是这个方案的特色，只能以宽度为基准进行适配，这个从这个方案的名字就能看出</p>

<p>请大家千万不要曲解文章的意思，不要只是单纯的对比优缺点的数量，缺点的数量大于优点的数量就一定是这个方案不行？没有一个方案是完美的，每个人的需求也都不一样，作为一篇科普类文章我只可能把这个方案描述得尽可能的全面</p>

<p>这个方案能给你带来什么，不能给你带来什么，我必须客观的描述清楚，这样才有助你做出决定，你应该注重的是在这些优缺点里什么是我能接受的，什么是我不能接受的，是否能为了某些优点做出某些妥协，而不只是单纯的去看数量，这样毫无意义，有些人就是觉得稳定性最重要，其他的都可以做出妥协，那其他缺点对于他来说都是无所谓的</p>

<p>好了，这个系列的第二篇文章讲完了，这篇文章也是按照上篇文章的优良传统，写的非常详细，哪怕是新手我相信也应该能看懂，为什么这么多人都不知道自己该选择什么样的方案，就是因为自己都没搞懂这些方案的原理，懂了原理过后才知道这些方案是否是自己想要的</p>

<p>接下来的第三篇文章会详细讲解两个方案的深入对比以及该如何选择，并剖析我根据 <strong>今日头条屏幕适配方案</strong> 优化的屏幕适配框架 <a href="https://github.com/JessYanCoding/AndroidAutoSize"><strong>AndroidAutoSize</strong></a> 的原理，敬请期待</p>

<p>如果大家想使用 <strong>smallestWidth 限定符屏幕适配方案</strong>，可以参考 <a href="https://www.jianshu.com/p/1302ad5a4b04">这篇文章</a>，里面提供有自动生成资源文件的插件和 <strong>Demo</strong>，由于我并没有在项目中使用 <strong>smallestWidth 限定符屏幕适配方案</strong>，所以如果在文章中有遗漏的知识点请谅解以及补充，感谢！</p>

<hr />

<p>以下是 <strong>骚年你的屏幕适配方式该升级了!</strong> 系列文章，欢迎转发以及分享:</p>

<ul>
  <li><a href="https://jessyan.me/autosize-introduce/">骚年你的屏幕适配方式该升级了!（一）-今日头条适配方案</a></li>
  <li><a href="https://jessyan.me/smallestwidth-introduce/">骚年你的屏幕适配方式该升级了!（二）-smallestWidth 限定符适配方案</a></li>
  <li><a href="https://jessyan.me/autosize-publish/">今日头条屏幕适配方案终极版正式发布!</a></li>
</ul>

<hr />
<p><strong>Hello 我叫 JessYan，如果您喜欢我的文章，可以在以下平台关注我</strong></p>

<ul>
  <li>个人主页: <a href="https://jessyan.me">https://jessyan.me</a></li>
  <li>GitHub: <a href="https://github.com/JessYanCoding">https://github.com/JessYanCoding</a></li>
  <li>掘金: <a href="https://gold.xitu.io/user/57a9dbd9165abd0061714613">https://gold.xitu.io/user/57a9dbd9165abd0061714613</a></li>
  <li>简书: <a href="https://www.jianshu.com/u/1d0c0bc634db">https://www.jianshu.com/u/1d0c0bc634db</a></li>
  <li>微博: <a href="https://weibo.com/u/1786262517">https://weibo.com/u/1786262517</a></li>
</ul>

<p>– The end</p>]]></content><author><name></name></author><category term="屏幕适配" /><summary type="html"><![CDATA[以下是 骚年你的屏幕适配方式该升级了! 系列文章，欢迎转发以及分享:]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" /><media:content medium="image" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">骚年你的屏幕适配方式该升级了!-今日头条适配方案</title><link href="https://jessyan.me/autosize-introduce/" rel="alternate" type="text/html" title="骚年你的屏幕适配方式该升级了!-今日头条适配方案" /><published>2018-08-18T00:00:00+08:00</published><updated>2018-08-18T00:00:00+08:00</updated><id>https://jessyan.me/autosize-introduce</id><content type="html" xml:base="https://jessyan.me/autosize-introduce/"><![CDATA[<p>以下是 <strong>骚年你的屏幕适配方式该升级了!</strong> 系列文章，欢迎转发以及分享:</p>

<ul>
  <li><a href="https://jessyan.me/autosize-introduce/">骚年你的屏幕适配方式该升级了!（一）-今日头条适配方案</a></li>
  <li><a href="https://jessyan.me/smallestwidth-introduce/">骚年你的屏幕适配方式该升级了!（二）-smallestWidth 限定符适配方案</a></li>
  <li><a href="https://jessyan.me/autosize-publish/">今日头条屏幕适配方案终极版正式发布!</a></li>
</ul>

<h1 id="前言">前言</h1>
<p>这个月在 <strong>Android</strong> 技术圈中 <strong>屏幕适配</strong> 这个词曝光率挺高的，为什么这么说呢？因为这个月陆续有多个大佬发布了屏幕适配相关的文章，公布了自己认可的屏幕适配方案</p>

<p>上上个星期 <strong>Blankj</strong> 老师发表了一篇力挺今日头条屏幕适配方案的 <a href="https://juejin.im/post/5b6250bee51d451918537021">文章</a>，提出了很多优化的方案，并开源了相关源码</p>

<p>上个星期 <strong>拉丁吴</strong> 老师在 <strong>鸿神</strong> 的公众号上发布了一篇 <a href="https://mp.weixin.qq.com/s/X-aL2vb4uEhqnLzU5wjc4Q">文章</a>，详细描述了市面上主流的几种屏幕适配方案，并发布了他的 <strong>smallestWidth</strong> 限定符适配方案和相关源码 (其实早就发布了)，文章写的很好，建议大家去看看</p>

<p>其实大家最关注的不是市面上有多少种屏幕适配方案，而是自己的项目该选择哪种屏幕适配方案，可以看出两位老师最终选择的屏幕适配方案都是不同的</p>

<p>我下面就来分析分析，我作为一个才接触这两个屏幕适配方案的吃瓜群众，我是怎么来验证这两种屏幕适配方案是否可行，以及怎样根据它们的优缺点来选择一个最适合自己项目的屏幕适配方案</p>

<p><strong>这是我推荐给大家的屏幕适配框架，本来想放到最后作为福利的，害怕大家看不到，所以就将链接放到这里，提前送给大家</strong></p>

<blockquote>
  <p>Github : <a href="https://github.com/JessYanCoding/AndroidAutoSize">您的 Star 是我坚持的动力 ✊</a></p>
</blockquote>

<h1 id="浅谈适配方案">浅谈适配方案</h1>
<p>在 <strong>拉丁吴</strong> 老师的文章中谈到了两个比较经典的屏幕适配方案，在我印象中十分深刻，我想大多数兄弟都用过，在我的开发生涯里也是有很长一段时间都在用这两种屏幕适配方案</p>

<p>第一种就是宽高限定符适配，什么是宽高限定符适配呢</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>├── src/main
│   ├── res
│   ├── ├──values
│   ├── ├──values-800x480
│   ├── ├──values-860x540
│   ├── ├──values-1024x600
│   ├── ├──values-1024x768
│   ├── ├──...
│   ├── ├──values-2560x1440
</code></pre></div></div>

<p>就是这种，在资源文件下生成不同分辨率的资源文件，然后在布局文件中引用对应的 <strong>dimens</strong>，大家一定还有印象</p>

<p>第二种就是 <strong>鸿神</strong> 的 <strong>AndroidAutoLayout</strong></p>

<p>这两种方案都已经逐渐退出了历史的舞台，为什么想必大家都知道，不知道的建议看看 <strong>拉丁吴</strong> 老师的文章，所以这两种方案我在文章中就不在阐述了，主要讲讲现在最主流的两种屏幕适配方案，<a href="https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA">今日头条适配方案</a> 和 <a href="https://mp.weixin.qq.com/s/X-aL2vb4uEhqnLzU5wjc4Q"><strong>smallestWidth</strong> 限定符适配方案</a></p>

<p>建议大家不清楚这两个方案的先看看这两篇文章，才清楚我在讲什么，后面我要讲解它们的原理，以及验证这两种方案是否真的可行，最后对他们进行深入对比，对于他们的一些缺点给予对应的解决方案，绝对干货</p>

<h1 id="今日头条屏幕适配方案">今日头条屏幕适配方案</h1>
<h2 id="原理">原理</h2>
<p>上面已经告知，不了解这两个方案的先看看上面的两篇文章，所以这里我就假设大家已经看了上面的文章或者之前就了解过这两个方案，所以在本文中我就不再阐述 <strong>DPI</strong>、<strong>Density</strong> 以及一些比较基础的知识点，上面的文章已经阐述的够清楚了</p>

<p>今日头条屏幕适配方案的核心原理在于，根据以下公式算出 <strong>density</strong></p>

<p><strong>当前设备屏幕总宽度（单位为像素）/  设计图总宽度（单位为 dp) = density</strong></p>

<p><strong>density</strong> 的意思就是 <strong>1 dp</strong> 占当前设备多少像素</p>

<h3 id="为什么要算出-density这和屏幕适配有什么关系呢">为什么要算出 <strong>density</strong>，这和屏幕适配有什么关系呢？</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static float applyDimension(int unit, float value,
                                       DisplayMetrics metrics)
    {
        switch (unit) {
        case COMPLEX_UNIT_PX:
            return value;
        case COMPLEX_UNIT_DIP:
            return value * metrics.density;
        case COMPLEX_UNIT_SP:
            return value * metrics.scaledDensity;
        case COMPLEX_UNIT_PT:
            return value * metrics.xdpi * (1.0f/72);
        case COMPLEX_UNIT_IN:
            return value * metrics.xdpi;
        case COMPLEX_UNIT_MM:
            return value * metrics.xdpi * (1.0f/25.4f);
        }
        return 0;
    }
</code></pre></div></div>

<p>大家都知道，不管你在布局文件中填写的是什么单位，最后都会被转化为 <strong>px</strong>，系统就是通过上面的方法，将你在项目中任何地方填写的单位都转换为 <strong>px</strong> 的</p>

<p>所以我们常用的 <strong>px</strong> 转 <strong>dp</strong> 的公式 <strong>dp = px / density</strong>，就是根据上面的方法得来的，<strong>density</strong> 在公式的运算中扮演着至关重要的一步</p>

<p>要看懂下面的内容，还得明白，今日头条的适配方式，今日头条适配方案默认项目中只能以高或宽中的一个作为基准，进行适配，为什么不像 <strong>AndroidAutoLayout</strong> 一样，高以高为基准，宽以宽为基准，同时进行适配呢</p>

<p>这就引出了一个现在比较棘手的问题，大部分市面上的 <strong>Android</strong> 设备的屏幕高宽比都不一致，特别是现在大量全面屏的问世，这个问题更加严重，不同厂商推出的全面屏手机的屏幕高宽比都可能不一致</p>

<p>这时我们只以高或宽其中的一个作为基准进行适配，就会有效的避免布局在高宽比不一致的屏幕上出现变形的问题</p>

<p>明白这个后，我再来说说 <strong>density</strong>，<strong>density</strong> 在每个设备上都是固定的，<strong>DPI / 160 = density</strong>，<strong>屏幕的总 px 宽度 / density = 屏幕的总 dp 宽度</strong></p>

<ul>
  <li>
    <p>设备 1，屏幕宽度为 <strong>1080px</strong>，<strong>480DPI</strong>，屏幕总 <strong>dp</strong> 宽度为 <strong>1080 / (480 / 160) = 360dp</strong></p>
  </li>
  <li>
    <p>设备 2，屏幕宽度为 <strong>1440</strong>，<strong>560DPI</strong>，屏幕总 <strong>dp</strong> 宽度为 <strong>1440 / (560 / 160) = 411dp</strong></p>
  </li>
</ul>

<p>可以看到屏幕的总 <strong>dp</strong> 宽度在不同的设备上是会变化的，但是我们在布局中填写的 <strong>dp</strong> 值却是固定不变的</p>

<p>这会导致什么呢？假设我们布局中有一个 <strong>View</strong> 的宽度为 <strong>100dp</strong>，在设备 1 中 该 <strong>View</strong> 的宽度占整个屏幕宽度的 <strong>27.8%</strong> (<strong>100 / 360 = 0.278</strong>)</p>

<p>但在设备 2 中该 <strong>View</strong> 的宽度就只能占整个屏幕宽度的 <strong>24.3%</strong> (<strong>100 / 411 = 0.243</strong>)，可以看到这个 <strong>View</strong> 在像素越高的屏幕上，<strong>dp</strong> 值虽然没变，但是与屏幕的实际比例却发生了较大的变化，所以肉眼的观看效果，会越来越小，这就导致了传统的填写 <strong>dp</strong> 的屏幕适配方式产生了较大的误差</p>

<p>这时我们要想完美适配，那就必须保证这个 <strong>View</strong> 在任何分辨率的屏幕上，与屏幕的比例都是相同的</p>

<p>这时我们该怎么做呢？改变每个 <strong>View</strong> 的 <strong>dp</strong> 值？不现实，在每个设备上都要通过代码动态计算 <strong>View</strong> 的 <strong>dp</strong> 值，工作量太大</p>

<p>如果每个 <strong>View</strong> 的 <strong>dp</strong> 值是固定不变的，那我们只要保证每个设备的屏幕总 <strong>dp</strong> 宽度不变，就能保证每个 <strong>View</strong> 在所有分辨率的屏幕上与屏幕的比例都保持不变，从而完成等比例适配，并且这个屏幕总 <strong>dp</strong> 宽度如果还能保证和设计图的宽度一致的话，那我们在布局时就可以直接按照设计图上的尺寸填写 <strong>dp</strong> 值</p>

<p><strong>屏幕的总 px 宽度 / density = 屏幕的总 dp 宽度</strong></p>

<p>在这个公式中我们要保证 <strong>屏幕的总 dp 宽度</strong> 和 <strong>设计图总宽度</strong> 一致，并且在所有分辨率的屏幕上都保持不变，我们需要怎么做呢？<strong>屏幕的总 px 宽度</strong> 每个设备都不一致，这个值是肯定会变化的，这时今日头条的公式就派上用场了</p>

<p><strong>当前设备屏幕总宽度（单位为像素）/  设计图总宽度（单位为 dp) = density</strong></p>

<p>这个公式就是把上面公式中的 <strong>屏幕的总 dp 宽度</strong> 换成 <strong>设计图总宽度</strong>，原理都是一样的，只要 <strong>density</strong> 根据不同的设备进行实时计算并作出改变，就能保证 <strong>设计图总宽度</strong> 不变，也就完成了适配</p>

<h2 id="验证方案可行性">验证方案可行性</h2>
<p>上面已经把原理分析的很清楚了，很多文章只是一笔带过这个公式，公式虽然很简单但我们还是想晓得这是怎么来的，所以我就反向推理了一遍，如果还是看不懂，那我只能说我尽力了，原理讲完了，那我们再来现场验证一下这个方案是否可行?</p>

<p>假设设计图总宽度为 <strong>375 dp</strong>，一个 <strong>View</strong> 在这个设计图上的尺寸是 <strong>50dp * 50dp</strong>，这个 <strong>View</strong> 的宽度占整个设计图宽度的 <strong>13.3%</strong> (<strong>50 / 375 = 0.133</strong>)，那我们就来验证下在使用今日头条屏幕适配方案的情况下，这个 <strong>View</strong> 与屏幕宽度的比例在分辨率不同的设备上是否还能保持和设计图中的比例一致</p>

<h3 id="验证设备-1">验证设备 1</h3>
<p>屏幕总宽度为 <strong>1080 px</strong>，根据今日头条的的公式求出 <strong>density</strong>，<strong>1080 / 375 = 2.88 (density)</strong></p>

<p>这个 <strong>50dp * 50dp</strong> 的 <strong>View</strong>，系统最后会将高宽都换算成 <strong>px</strong>，<strong>50dp * 2.88 = 144 px</strong> (根据公式 <strong>dp * density = px</strong>)</p>

<p><strong>144 / 1080 = 0.133</strong>，<strong>View</strong> 实际宽度与 <strong>屏幕总宽度</strong> 的比例和 <strong>View</strong> 在设计图中的比例一致 (<strong>50 / 375 = 0.133</strong>)，所以完成了等比例缩放</p>

<p>某些设备总宽度为 <strong>1080 px</strong>，但是 <strong>DPI</strong> 可能不同，是否会对今日头条适配方案产生影响？其实这个方案根本没有根据 <strong>DPI</strong> 求出 <strong>density</strong>，是根据自己的公式求出的 <strong>density</strong>，所以这对今日头条的方案没有影响</p>

<p>上面只能确定在所有屏幕总宽度为 <strong>1080 px</strong> 的设备上能完成等比例适配，那我们再来试试其他分辨率的设备</p>

<h3 id="验证设备-2">验证设备 2</h3>
<p>屏幕总宽度为 <strong>1440 px</strong>，根据今日头条的的公式求出 <strong>density</strong>，<strong>1440 / 375 = 3.84 (density)</strong></p>

<p>这个 <strong>50dp * 50dp</strong> 的 <strong>View</strong>，系统最后会将高宽都换算成 <strong>px</strong>，<strong>50dp * 3.84 = 192 px</strong> (根据公式 <strong>dp * density = px</strong>)</p>

<p><strong>192 / 1440 = 0.133</strong>，<strong>View</strong> 实际宽度与 <strong>屏幕总宽度</strong> 的比例和 <strong>View</strong> 在设计图中的比例一致 (<strong>50 / 375 = 0.133</strong>)，所以也完成了等比例缩放</p>

<p>两个不同分辨率的设备都完成了等比例缩放，证明今日头条屏幕适配方案在不同分辨率的设备上都是有效的，如果大家还心存疑虑，可以再试试其他分辨率的设备，其实到最后得出的比例不会有任何偏差, 都是 <strong>0.133</strong></p>

<h2 id="优点">优点</h2>
<ol>
  <li>
    <p>使用成本非常低，操作非常简单，使用该方案后在页面布局时不需要额外的代码和操作，这点可以说完虐其他屏幕适配方案</p>
  </li>
  <li>
    <p>侵入性非常低，该方案和项目完全解耦，在项目布局时不会依赖哪怕一行该方案的代码，而且使用的还是 <strong>Android</strong> 官方的 <strong>API</strong>，意味着当你遇到什么问题无法解决，想切换为其他屏幕适配方案时，基本不需要更改之前的代码，整个切换过程几乎在瞬间完成，会少很多麻烦，节约很多时间，试错成本接近于 0</p>
  </li>
  <li>
    <p>可适配三方库的控件和系统的控件(不止是是 <strong>Activity</strong> 和 <strong>Fragment</strong>，<strong>Dialog</strong>、<strong>Toast</strong> 等所有系统控件都可以适配)，由于修改的 <strong>density</strong> 在整个项目中是全局的，所以只要一次修改，项目中的所有地方都会受益</p>
  </li>
  <li>
    <p>不会有任何性能的损耗</p>
  </li>
</ol>

<h2 id="缺点">缺点</h2>
<p>暂时没发现其他什么很明显的缺点，已知的缺点有一个，那就是第三个优点，它既是这个方案的优点也同样是缺点，但是就这一个缺点也是非常致命的</p>

<p>只需要修改一次 <strong>density</strong>，项目中的所有地方都会自动适配，这个看似解放了双手，减少了很多操作，但是实际上反应了一个缺点，那就是只能一刀切的将整个项目进行适配，但适配范围是不可控的</p>

<p>这样不是很好吗？这样本来是很好的，但是应用到这个方案是就不好了，因为我上面的原理也分析了，这个方案依赖于设计图尺寸，但是项目中的系统控件、三方库控件、等非我们项目自身设计的控件，它们的设计图尺寸并不会和我们项目自身的设计图尺寸一样</p>

<p>当这个适配方案不分类型，将所有控件都强行使用我们项目自身的设计图尺寸进行适配时，这时就会出现问题，<strong>当某个系统控件或三方库控件的设计图尺寸和和我们项目自身的设计图尺寸差距非常大时，这个问题就越严重</strong></p>

<h3 id="举个栗子">举个栗子</h3>

<p>假设一个三方库的 <strong>View</strong>，作者在设计时，把它设计为 <strong>100dp * 100dp</strong>，设计图的最大宽度为 <strong>1000dp</strong>，这个 <strong>View</strong> 在设计图中的比例是 <strong>100 / 1000 = 0.1</strong>，意思是这个 <strong>View</strong> 的宽度在设计图中占整个宽度的 <strong>10%</strong>，如果我们要完成等比例适配，那这个三方库 <strong>View</strong> 在所有的设备上与屏幕的总宽度的比例，都必须保持在 <strong>10%</strong></p>

<p>这时在一个使用今日头条屏幕适配方案的项目上，设置的设计图最大宽度如果是 <strong>1000dp</strong>，那这个三方库 <strong>View</strong>，与项目自身都可以完美的适配，但当我们项目自身的设计图最大宽度不是 <strong>1000dp</strong>，是 <strong>500dp</strong> 时，<strong>100 / 500 = 0.2</strong>，可以看到，比例发生了较大的变化，从 <strong>10%</strong> 上升为 <strong>20%</strong>，明显这个三方库 <strong>View</strong> 高于作者的预期，比之前更大了</p>

<p>这就是两个设计图尺寸不一致导致的非常严重的问题，当两个设计图尺寸差距越大，那适配的效果也就天差万别了</p>

<h3 id="解决方案">解决方案</h3>
<h4 id="方案-1">方案 1</h4>
<p>调整设计图尺寸，因为三方库可能是远程依赖的，无法修改源码，也就无法让三方库来适应我们项目的设计图尺寸，所以只有我们自身作出修改，去适应三方库的设计图尺寸，我们将项目自身的设计图尺寸修改为这个三方库的设计图尺寸，就能完成项目自身和三方库的适配</p>

<p><strong>这时项目的设计图尺寸修改了，所以项目布局文件中的 dp 值，也应该按照修改的设计图尺寸，按比例增减，保持与之前设计图中的比例不变</strong></p>

<p>但是如果为了适配一个三方库修改整个项目的设计图尺寸，是非常不值得的，所以这个方案支持以 <strong>Activity</strong> 为单位修改设计图尺寸，相当于每个 <strong>Activity</strong> 都可以自定义设计图尺寸，因为有些 <strong>Activity</strong> 不会使用三方库 <strong>View</strong>，也就不需要自定义尺寸，所以每个 <strong>Activity</strong> 都有控制权的话，这也是最灵活的</p>

<p>但这也有个问题，当一个 <strong>Activity</strong> 使用了多个设计图尺寸不一样的三方库 <strong>View</strong>，就会同样出现上面的问题，这也就只有把设计图改为与几个三方库比较折中的尺寸，才能勉强缓解这个问题</p>

<h4 id="方案-2">方案 2</h4>
<p>第二个方案是最简单的，也是按 <strong>Activity</strong> 为单位，取消当前 <strong>Activity</strong> 的适配效果，改用其他的适配方案</p>

<h1 id="使用中的问题">使用中的问题</h1>
<p>有些文章中提到了今日头条屏幕适配方案可以将设计图尺寸填写成以 <strong>px</strong> 为单位的宽度和高度，这样我们在布局文件中，也就能直接填写设计图上标注的 <strong>px</strong> 值，省掉了将 <strong>px</strong> 换算为 <strong>dp</strong> 的时间 (大部分公司的设计图都只标注 <strong>px</strong> 值)，而且照样能完美适配</p>

<p>但是我建议大家千万不要这样做，还是老老实实的以 <strong>dp</strong> 为单位填写 <strong>dp</strong> 值，为什么呢？</p>

<p>直接填写 <strong>px</strong> 虽然刚开始布局的时候很爽，但是这个坑就已经埋上了，会让你后面很爽，有哪些坑？</p>

<h2 id="第一个坑">第一个坑</h2>

<p>这样无疑于使项目强耦合于这个方案，当你遇到无法解决的问题想切换为其他屏幕适配方案的时候，<strong>layout</strong> 文件里曾经填写的 <strong>px</strong> 值都会作为 <strong>dp</strong></p>

<p>比如你的设计图实际宽度为 <strong>1080px</strong>，你不换算为 <strong>360dp (1080 / 3 = 360)</strong>，却直接将 <strong>1080px</strong> 作为这个方案的设计图尺寸，那你在 <strong>layout</strong> 文件中，填写的也都是设计图上标注的 <strong>px</strong> 值，但是单位却是 <strong>dp</strong></p>

<p>一个在设计图上 <strong>300px * 300px</strong> 的 <strong>View</strong>，你可以直接在 <strong>layout</strong> 文件中填写为 <strong>300dp</strong>，而由于这个方案可以动态改变 <strong>density</strong> 的原因还是可以做到等比例适配，非常爽!</p>

<p>但你不要忘了，这样你就强耦合于这个方案了，因为当你不使用这个方案时，<strong>density</strong> 是不可变的!</p>

<h3 id="举个栗子-1">举个栗子</h3>
<p>使用这个方案时，在屏幕宽度为 <strong>1080px</strong> 的设备上，将设计图宽度直接填写为 <strong>1080</strong>，根据今日头条公式</p>

<p><strong>当前设备屏幕总宽度 /  设计图总宽度 = density</strong></p>

<p>这时得出 <strong>density</strong> 为 1 (<strong>1080 / 1080 = 1</strong>)，所以你在 <strong>layout</strong> 文件中你填写的 <strong>300dp</strong> 最后转换为 <strong>px</strong> 也是 <strong>300px</strong> (<strong>300dp * 1 = 300px</strong> 根据公式 <strong>dp * density = px</strong>)</p>

<p>在这个方案的帮助下非常完美，和设计图一模一样完成了适配</p>

<p>但当你不使用这个方案时，<strong>density</strong> 的换算公式就变为官方的 <strong>DPI / 160 = density</strong>，在这个屏幕宽度为 <strong>1080px</strong>，<strong>480dpi</strong> 的设备上，<strong>density</strong> 就固定为 3 (<strong>480 / 160 = 3</strong>)</p>

<p>这时再来看看你之前在 <strong>layout</strong> 文件中填写的 <strong>dp</strong>，换算成 <strong>px</strong> 为 <strong>900 px</strong> (<strong>300dp * 3 = 900px</strong> 根据公式 <strong>dp * density = px</strong>)</p>

<p>原本在在设计图上为 <strong>300px</strong> 的 <strong>View</strong>，这时却达到了惊人的 <strong>900px</strong>，3倍的差距，恭喜你，你已经强耦合于这个方案了，你要不所有 <strong>layout</strong> 文件都改一遍，要不继续使用这个方案</p>

<h2 id="第二个坑">第二个坑</h2>
<p>第二个坑其实就是刚刚在上面说的今日头条适配方案的缺点，<strong>当某个系统控件或三方库控件的设计图尺寸和和我们项目自身的设计图尺寸差距非常大时，这个问题就越严重</strong></p>

<p>你如果直接填写以 <strong>px</strong> 为设计图的尺寸，这不用想，肯定和所有的三方库以及系统控件的设计图尺寸都不一样，而且差距都非常之大，至少两三倍的差距，这时你在当前页面弹个 <strong>Toast</strong> 就可以明显看到，比之前小很多，可以说是天差万别，用其他三方库 <strong>View</strong>，也是一样的，会小很多</p>

<p>因为你以 <strong>px</strong> 为单位填写设计图尺寸，人家却用的 <strong>dp</strong>，差距能不大吗，你如果老老实实用 <strong>dp</strong>，哪怕三方库的设计图尺寸和你项目自身的设计图尺寸不一样，那也差距不大，小到一定程度，基本都不用调整，可以忽略不计，而且很多三方库的设计图尺寸其实也都是那几个大众尺寸，很大可能和你项目自身的设计图尺寸一样</p>

<h1 id="总结">总结</h1>
<p>可以看到我讲的非常详细，可以说比今日头条官方以及任何博客写的都清楚，从原理到优缺点再到解决方案一应俱全，因为篇幅有限，如果我还想把 <strong>smallestWidth</strong> 限定符适配方案写的这么详细，那估计这篇文章得有一万字了</p>

<p>所以我把这次的屏幕适配文章归位一个系列，一共分为三篇，第一篇详细的讲 <strong>今日头条屏幕适配方案</strong>，第二篇详细的讲 <strong>smallestWidth 限定符适配方案</strong>，第三篇详细讲两个方案的深入对比以及如何选择，并发布我根据 <strong>今日头条屏幕适配方案</strong> 优化的屏幕适配框架 <a href="https://github.com/JessYanCoding/AndroidAutoSize"><strong>AndroidAutoSize</strong></a></p>

<p><strong>今日头条屏幕适配方案</strong> 官方公布的核心源码只有 30 行不到，但我这个框架的源码有 <strong>1500</strong> 行以上，在保留原有特性的情况下增加了不少功能和特性，功能增加了不少，但是使用上却变简单了</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;manifest&gt;</span>
    <span class="nt">&lt;application&gt;</span>            
        <span class="nt">&lt;meta-data</span>
            <span class="na">android:name=</span><span class="s">"design_width_in_dp"</span>
            <span class="na">android:value=</span><span class="s">"360"</span><span class="nt">/&gt;</span>
        <span class="nt">&lt;meta-data</span>
            <span class="na">android:name=</span><span class="s">"design_height_in_dp"</span>
            <span class="na">android:value=</span><span class="s">"640"</span><span class="nt">/&gt;</span>           
     <span class="nt">&lt;/application&gt;</span>           
<span class="nt">&lt;/manifest&gt;</span>
</code></pre></div></div>

<p>只要这一步填写了设计图的高宽以 <strong>dp</strong> 为单位，你什么都不做，框架就开始适配了</p>

<p>大家可以提前看看我是怎么封装和优化的，我后面的第三篇文章会给出这个框架的原理分析，敬请期待</p>

<hr />

<p>关于大家的评论以及关注的问题，我在这里统一回复一下:</p>

<p>感谢，大家的关注和回复，我介绍这个今日头条的屏幕适配方案并不是说他有多么完美，只是他确实有效而且能帮我们减少很多开发成本</p>

<p>对于很多人说的 <strong>DPI</strong> 的存在，不就是为了让大屏能显示更多的内容，如果一个大屏手机和小屏手机，显示的内容都相同，那用户买大屏手机又有什么意义呢，我觉得大家对 <strong>DPI</strong> 的理解是对的，这个观点我也是认同的，<strong>Google</strong> 设计 <strong>DPI</strong> 时可能也是这么想的，但是有一点大家没考虑到，<strong>Android</strong> 的碎片化太严重了</p>

<p>为什么 <strong>Android</strong> 诞生这么多年，<strong>Android</strong> 的百分比库层出不穷，按理说他们都违背了上面说的这个理念，但为什么还有这么多人去研究百分比库通过各种方式去实现百分比布局 (谷歌官方也曾出过百分比库)？为什么？很简单，因为需求啊！为什么需求，因为开发成本低啊！为什么今日头条的这个屏幕适配方案现在能这么火，因为他的开发成本是目前所有屏幕适配方案中最低的啊！</p>

<p><strong>DPI</strong> 的意义谁又不懂呢？难道就你懂，今日头条这种大公司的程序员不懂这个道理吗？今日头条这么大的公司难道不想把每个机型每个版本的设备都适配完美，让自己的 <strong>App</strong> 体验更好，哪怕付出更大的成本，有些时候想象是美好的，但是这个投入的成本，谁又能承担呢，连今日头条这么大的公司，这么雄厚的资本都没选择投入更大的成本，对每个机型进行更精细化的适配，难道市面上的中小型公司又有这个能力投入这么大的成本吗？</p>

<p>鱼和熊掌不可兼得，<strong>DPI</strong> 的意义在 <strong>Google</strong> 的设计理念中是完全正确的，但不是所有公司都能承受这个成本，想必今日头条的程序员，也是因为今日头条 <strong>App</strong> 的用户量足够多，机型分布足够广，也是被屏幕适配这个问题折磨的不要不要的，才想出这么个不这么完美但是却很有效的方案</p>

<hr />

<p>以下是 <strong>骚年你的屏幕适配方式该升级了!</strong> 系列文章，欢迎转发以及分享:</p>

<ul>
  <li><a href="https://jessyan.me/autosize-introduce/">骚年你的屏幕适配方式该升级了!（一）-今日头条适配方案</a></li>
  <li><a href="https://jessyan.me/smallestwidth-introduce/">骚年你的屏幕适配方式该升级了!（二）-smallestWidth 限定符适配方案</a></li>
  <li><a href="https://jessyan.me/autosize-publish/">今日头条屏幕适配方案终极版正式发布!</a></li>
</ul>

<hr />
<p><strong>Hello 我叫 JessYan，如果您喜欢我的文章，可以在以下平台关注我</strong></p>

<ul>
  <li>个人主页: <a href="https://jessyan.me">https://jessyan.me</a></li>
  <li>GitHub: <a href="https://github.com/JessYanCoding">https://github.com/JessYanCoding</a></li>
  <li>掘金: <a href="https://gold.xitu.io/user/57a9dbd9165abd0061714613">https://gold.xitu.io/user/57a9dbd9165abd0061714613</a></li>
  <li>简书: <a href="https://www.jianshu.com/u/1d0c0bc634db">https://www.jianshu.com/u/1d0c0bc634db</a></li>
  <li>微博: <a href="https://weibo.com/u/1786262517">https://weibo.com/u/1786262517</a></li>
</ul>

<p>– The end</p>]]></content><author><name></name></author><category term="屏幕适配" /><summary type="html"><![CDATA[以下是 骚年你的屏幕适配方式该升级了! 系列文章，欢迎转发以及分享:]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" /><media:content medium="image" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">解决Retrofit多BaseUrl及运行时动态改变BaseUrl(二)</title><link href="https://jessyan.me/retrofiturlmanager-intro/" rel="alternate" type="text/html" title="解决Retrofit多BaseUrl及运行时动态改变BaseUrl(二)" /><published>2018-07-09T00:00:00+08:00</published><updated>2018-07-09T00:00:00+08:00</updated><id>https://jessyan.me/retrofiturlmanager-intro</id><content type="html" xml:base="https://jessyan.me/retrofiturlmanager-intro/"><![CDATA[<h1 id="前言">前言</h1>
<p>我在之前的文章 <a href="https://www.jianshu.com/p/2919bdb8d09a">《解决Retrofit多BaseUrl及运行时动态改变BaseUrl》</a> 中，介绍了市面上能够解决此类问题的 4 个常见的解决方案，并开源了自己经过优化后的解决方案 <a href="https://github.com/JessYanCoding/RetrofitUrlManager">RetrofitUrlManager</a>，现在再为大家带来此系列的第二篇文章，这篇文章主要介绍 <a href="https://github.com/JessYanCoding/RetrofitUrlManager">RetrofitUrlManager</a> 针对 <strong>BaseUrl</strong> 替换逻辑的重大升级，因为这个升级对于 <a href="https://github.com/JessYanCoding/RetrofitUrlManager">RetrofitUrlManager</a> 足够重要，将使 <a href="https://github.com/JessYanCoding/RetrofitUrlManager">RetrofitUrlManager</a> 能够从容应对更多复杂的需求，所以单独写一篇文章让更多的人能够知道</p>

<blockquote>
  <p>Github : <a href="https://github.com/JessYanCoding/RetrofitUrlManager">您的 Star 是我坚持的动力 ✊</a></p>
</blockquote>

<h1 id="为什么不使用多-retrofit-实例的方案">为什么不使用多 Retrofit 实例的方案?</h1>
<p>在上篇文章 <a href="https://www.jianshu.com/p/2919bdb8d09a">《解决Retrofit多BaseUrl及运行时动态改变BaseUrl》</a> 中，4 种方案的特点和不足我都描述的很清楚，建议没看过这篇文章的可以去看看这篇文章，扩宽知识面，在后面的时间里经常有人问我为什么不使用多 <strong>Retrofit</strong> 实例的方案，多个 <strong>Retrofit</strong> 实例看起来并不会占用多少资源啊?</p>

<p>在回答之前为了让看这篇文章的人能了解我在说什么，所以我再粘贴下 <a href="https://www.jianshu.com/p/2919bdb8d09a">上篇文章</a> 中关于这个方案的部分描述</p>

<p><code class="language-plaintext highlighter-rouge">民间常用解决方案:</code> <br />
<code class="language-plaintext highlighter-rouge">之前也看过很多开源的聚合类 App 源码, 像一些整合 知乎、 豆瓣、 Gank 等多个平台数据的 App, 因为各自平台的域名不同, 所以大多数这类 App 会给每个平台都各自创建一个 Retrofit 对象, 即不同的 BaseUrl 使用不同的 Retrofit 对象来创建 ApiService 进行请求, 这样只要新增一个不同的 BaseUrl, 那就需要重新创建一个新的 Retrofit 对象
</code></p>

<p>我在这篇文章中重新回答下这个问题，为每个不同的 <strong>BaseUrl</strong> 都创建一个其他配置属性都一模一样的 <strong>Retrofit</strong> 实例不止会造成资源的浪费，还会造成接口管理成本的增加，这个才是最重要的一点, 举个例</p>

<p>我们平时项目中所有的 <strong>ApiService</strong> 都是使用同一个 <strong>Retrofit</strong> 实例的 <strong>Retrofit#create(ApiService)</strong> 方法进行实例化后开始接口的请求</p>

<p>但是当项目中出现多个 <strong>Retrofit</strong> 实例后，我们在开发中不光要区分哪些接口使用哪个 <strong>ApiService</strong>，还要区分哪些 <strong>ApiService</strong> 需要使用哪个 <strong>Retrofit</strong> 实例进行实例化，如果 <strong>ApiService</strong> 使用错误的 <strong>Retrofit</strong> 实例进行实例化，那这个 <strong>ApiService</strong> 的所有接口请求都注定完全失败</p>

<p>越复杂的项目，开发人数越多的项目，出错的风险就越大，并且扩展性也在大打折扣，后面一有变更将会十分痛苦，随着项目中接口的增加，以及 <strong>Retrofit</strong> 实例的增加 (<strong>BaseUrl</strong> 的增加)，这个管理成本会成几何倍的增加</p>

<p>使用多 <strong>Retrofit</strong> 实例的方案前期投入成本过高，可能会影响之前项目管理接口的方式，某些封装过 <strong>Retrofit</strong> 的项目，也可能需要大改，对于老项目的接入不利，而使用 <a href="https://github.com/JessYanCoding/RetrofitUrlManager">RetrofitUrlManager</a> 不仅可以满足多 <strong>BaseUrl</strong> 及运行时动态改变 <strong>BaseUrl</strong> 的需求，还具有热插拔以及低侵入性的特点，在使用过程中将不会影响到之前的接口管理方式和使用方式，还具有极强的扩展性，可应对后面陆续增加的极其复杂的 <strong>BaseUrl</strong> 替换需求</p>

<h1 id="升级之前的-retrofiturlmanager-的问题">升级之前的 RetrofitUrlManager 的问题</h1>
<p>此次升级之前的 <strong>RetrofitUrlManager</strong> 版本，只是将 <a href="https://www.jianshu.com/p/2919bdb8d09a">上篇文章</a> 的思想完全实现，有了整个框架的基础，但是在动态替换 <strong>BaseUrl</strong> 方面还不够强大，最被大家吐槽的就是只能替换 <strong>BaseUrl</strong> 的域名</p>

<p>比如一个需要替换 <strong>BaseUrl</strong> 的 <strong>Url</strong> 地址为 <code class="language-plaintext highlighter-rouge">"https:www.google.com/api/v2"</code>，其中 <code class="language-plaintext highlighter-rouge">"https:www.google.com/api"</code> 是我们传给 <strong>Retrofit</strong> 的 <strong>BaseUrl</strong>，这时我们使用 <strong>RetrofitUrlManager</strong> 框架，想把 <strong>BaseUrl</strong> 替换成 <code class="language-plaintext highlighter-rouge">"https:www.github.com"</code>，我们期望的替换后的 <strong>URL</strong> 地址是 <code class="language-plaintext highlighter-rouge">"https:www.github.com/v2"</code>，但使用框架替换后的实际 <strong>URL</strong> 地址是 <code class="language-plaintext highlighter-rouge">"https:www.github.com/api/v2"</code>, <code class="language-plaintext highlighter-rouge">"/api"</code> 作为 <strong>BaseUrl</strong> 的一部分并没有被新的 <strong>BaseUrl</strong> 替换掉，只是替换了 <strong>BaseUrl</strong> 中的域名</p>

<h1 id="retrofiturlmanager-是如何改善的">RetrofitUrlManager 是如何改善的</h1>
<p>改善之前先要先分析为什么会这样？因为 <strong>RetrofitUrlManager</strong> 框架在拦截器中拦截到的 <strong>URL</strong> 地址是 <strong>Retrofit</strong> 已经把 <strong>BaseUrl</strong> 和接口注解中的相对路径合并后得到的最终路径地址，所以框架并不知道您传给 <strong>Retrofit</strong> 的 <strong>BaseUrl</strong> 除了域名外还包含后面的 <code class="language-plaintext highlighter-rouge">"/api"</code>，框架不知道 <strong>BaseUrl</strong> 的具体值，所以框架只会默认所有的 <strong>BaseUrl</strong> 都只含有域名，所以也就只能替换域名</p>

<h1 id="高级模式">高级模式</h1>
<p>想要解决此类问题也很简单，告诉 <strong>RetrofitUrlManager</strong> 框架您传给 <strong>Retrofit</strong> 的 <strong>BaseUrl</strong> 具体值即可，所以框架升级后增加了 <strong>RetrofitUrlManager#startAdvancedModel(String)</strong> 方法，在 <strong>App</strong> 初始化时将您传给 <strong>Retrofit</strong> 的 <strong>BaseUrl</strong> 同样传给此方法，即可开启高级模式，高级模式即可替换拥有多个 <strong>pathSegments</strong> 的 <strong>BaseUrl</strong>，不再局限于只能替换域名</p>

<h2 id="什么是-pathsegment">什么是 pathSegment?</h2>
<p>在 <code class="language-plaintext highlighter-rouge">"https://www.github.com/wiki/part?name=jess"</code> 中，其中的 <code class="language-plaintext highlighter-rouge">"/wiki"</code> 和 <code class="language-plaintext highlighter-rouge">"/part"</code> 就是 <strong>pathSegment</strong>, <strong>PathSize</strong> 就是 <strong>pathSegment</strong> 的个数</p>

<p>这个 <strong>URL</strong> 地址的 <strong>PathSize</strong> 就是 2, 第一个 <strong>pathSegment</strong> 是 <code class="language-plaintext highlighter-rouge">"/wiki"</code>，第二个 <strong>pathSegment</strong> 是 <code class="language-plaintext highlighter-rouge">"/part"</code>，可以粗略的理解为域名后面跟了几个 <code class="language-plaintext highlighter-rouge">"/"</code> <strong>PathSize</strong> 就是几</p>

<h2 id="高级模式的替换规则">高级模式的替换规则</h2>
<ol>
  <li>
    <p>旧 <strong>URL</strong> 地址为 <code class="language-plaintext highlighter-rouge">"https://www.github.com/wiki/part"</code>，您在 <strong>App</strong> 初始化时传入 <strong>RetrofitUrlManager#startAdvancedModel(String)</strong> 的 <strong>BaseUrl</strong> 为 <code class="language-plaintext highlighter-rouge">"https://www.github.com/wiki"</code>，您想替换成的 <strong>BaseUrl</strong> 地址是 <code class="language-plaintext highlighter-rouge">"https://www.google.com/api"</code>，经过框架替换后生成的最终 <strong>URL</strong> 地址为  <code class="language-plaintext highlighter-rouge">"http://www.google.com/api/part"</code></p>
  </li>
  <li>
    <p>旧 <strong>URL</strong> 地址为 <code class="language-plaintext highlighter-rouge">"https://www.github.com/wiki/part"</code>，您在 <strong>App</strong>  初始化时传入 <strong>RetrofitUrlManager#startAdvancedModel(String)</strong> 的 <strong>BaseUrl</strong> 为 <code class="language-plaintext highlighter-rouge">"https://www.github.com/wiki"</code>，您想替换成的 <strong>BaseUrl</strong> 地址是 <code class="language-plaintext highlighter-rouge">"https://www.google.com"</code>，经过框架替换后生成的最终 <strong>URL</strong> 地址为 <code class="language-plaintext highlighter-rouge">"http://www.google.com/part"</code></p>
  </li>
  <li>
    <p>旧 <strong>URL</strong> 地址为 <code class="language-plaintext highlighter-rouge">"https://www.github.com/wiki/part"</code>, 您在 <strong>App</strong> 初始化时传入 <strong>RetrofitUrlManager#startAdvancedModel(String)</strong> 的 <strong>BaseUrl</strong> 为 <code class="language-plaintext highlighter-rouge">"https://www.github.com"</code>，您想替换成的 <strong>BaseUrl</strong> 地址是 <code class="language-plaintext highlighter-rouge">"https://www.google.com/api"</code>，经过框架替换后生成的最终 <strong>URL</strong> 地址为 <code class="language-plaintext highlighter-rouge">"http://www.google.com/api/wiki/part"</code></p>
  </li>
</ol>

<h1 id="超级模式">超级模式</h1>
<p>超级模式是高级模式的加强版，优先级高于高级模式，按理说高级模式就能满足开发中的大部分需求，那什么又是超级模式呢？那就要先来说说高级模式了</p>

<h2 id="高级模式的原理">高级模式的原理</h2>
<p>在高级模式中您需要在 <strong>App</strong> 初始化时将您传给 <strong>Retrofit</strong> 的 <strong>BaseUrl</strong> 同样传给 <strong>RetrofitUrlManager#startAdvancedModel(String)</strong> 一份，用以开启高级模式，成功开启高级模式后，这个传给 <strong>RetrofitUrlManager#startAdvancedModel(String)</strong> 的 <strong>BaseUrl</strong> 就会作为框架替换 <strong>BaseUrl</strong> 的基准</p>

<p>什么叫作基准呢? 用此 <strong>BaseUrl</strong> 开启高级模式，并不意味着框架就只能替换 <strong>域名</strong> 为 <code class="language-plaintext highlighter-rouge">"www.github.com"</code> 前两个 <strong>pathSegments</strong> 为 <code class="language-plaintext highlighter-rouge">"/wiki/part"</code> 的 <strong>URL</strong>，只要拥有域名以及大于或等于两个 <strong>pathSegments</strong> 的 <strong>URL</strong> 都可以被框架替换，因此高级模式只会保存传入 <strong>RetrofitUrlManager#startAdvancedModel(String)</strong> 的 <strong>BaseUrl</strong> 的格式 (保存 <strong>pathSegments</strong> 的个数)，并不是保存具体的值</p>

<h2 id="高级模式的局限">高级模式的局限</h2>
<p>如果传给 <strong>RetrofitUrlManager#startAdvancedModel(String)</strong> 的 <strong>BaseUrl</strong> 为 <code class="language-plaintext highlighter-rouge">"https://www.github.com/wiki/part"</code> (<strong>PathSize</strong> = 2)，框架就会将项目中所有 <strong>URL</strong> 中的 <strong>域名</strong> 以及 <strong>域名</strong> 后面的前两个 <strong>pathSegments</strong> 作为可被替换的 <strong>BaseUrl</strong> 整体，意味着框架只会将 <strong>URL</strong> 中的 <strong>域名</strong> 以及前两个 <strong>pathSegments</strong> 剪切并替换为您期望的 <strong>BaseUrl</strong></p>

<p>这时服务器突然作出调整，项目中的一部分 <strong>URL</strong> 只需要将 <code class="language-plaintext highlighter-rouge">"https://www.github.com/wiki"</code> (<strong>PathSize</strong> = 1) 替换掉, 第二个 <strong>pathSegment</strong> <code class="language-plaintext highlighter-rouge">"/part"</code> 不再作为 <strong>BaseUrl</strong> 的一部分，不能被替换掉，必须要保留下来</p>

<p>这时项目中就出现了多个需要被替换的 <strong>BaseUrl</strong> 格式 (<strong>PathSize</strong> 不同)，有些 <strong>URL</strong> 只需要替换 <strong>域名</strong> 以及前两个 <strong>pathSegments</strong>，有些又只需要替换 <strong>域名</strong> 以及前一个 <strong>pathSegments</strong>，但是在开启高级模式时，只保存了一个 <strong>BaseUrl</strong> 的格式，这时使用高级模式实现此需求就比较棘手</p>

<p>这个需求是一个比较变态的需求，可能很多人遇不上，但是我想让您知道当您遇上了也不要怕，因为 <strong>RetrofitUrlManager</strong> 的超级模式已经帮您考虑周全</p>

<h2 id="超级模式的用法">超级模式的用法</h2>
<p>超级模式与高级模式不同的是，开启超级模式并不需要调用 <strong>API</strong>，只须要在需要开启超级模式的 <strong>Url</strong> 尾部加上 <strong>RetrofitUrlManager#IDENTIFICATION_PATH_SIZE (#baseurl_path_size=) + PathSize</strong>，这样就明确的告诉了框架，在这个 <strong>URL</strong> 中需要被替换的 <strong>BaseUrl</strong> 含有几个 <strong>pathSegments</strong>，相当于每个 <strong>URL</strong> 都可以指定自己需要被替换的 <strong>BaseUrl</strong> 的格式，这样就比高级模式只能在 <strong>App</strong> 初始化时，指定一个全局的 <strong>BaseUrl</strong> 格式灵活得多，如果当您开启高级模式的同时也开启了超级模式，由于超级模式的优先级高于高级模式，所以只会执行超级模式</p>

<h2 id="超级模式的替换规则">超级模式的替换规则</h2>
<ol>
  <li>
    <p>旧 <strong>URL</strong> 地址为 <code class="language-plaintext highlighter-rouge">"https://www.github.com/wiki/part#baseurl_path_size=1"</code>，<code class="language-plaintext highlighter-rouge">"#baseurl_path_size=1"</code> 表示其中 <strong>BaseUrl</strong> 为 <code class="language-plaintext highlighter-rouge">"https://www.github.com/wiki"</code>，您想替换成的 <strong>BaseUrl</strong> 地址是 <code class="language-plaintext highlighter-rouge">"https://www.google.com/api"</code>，经过框架替换后生成的最终 <strong>URL</strong> 地址为 <code class="language-plaintext highlighter-rouge">"http://www.google.com/api/part"</code></p>
  </li>
  <li>
    <p>旧 <strong>URL</strong> 地址为 <code class="language-plaintext highlighter-rouge">"https://www.github.com/wiki/part#baseurl_path_size=1"</code>，<code class="language-plaintext highlighter-rouge">"#baseurl_path_size=1"</code> 表示其中 <strong>BaseUrl</strong> 为 <code class="language-plaintext highlighter-rouge">"https://www.github.com/wiki"</code>，您想替换成的 <strong>BaseUrl</strong> 地址是 <code class="language-plaintext highlighter-rouge">"https://www.google.com"</code>，经过框架替换后生成的最终 <strong>URL</strong> 地址为 <code class="language-plaintext highlighter-rouge">"http://www.google.com/part"</code></p>
  </li>
  <li>
    <p>旧 <strong>URL</strong> 地址为 <code class="language-plaintext highlighter-rouge">"https://www.github.com/wiki/part#baseurl_path_size=0"</code>，<code class="language-plaintext highlighter-rouge">"#baseurl_path_size=0"</code> 表示其中 <strong>BaseUrl</strong> 为 <code class="language-plaintext highlighter-rouge">"https://www.github.com"</code>，您想替换成的 <strong>BaseUrl</strong> 地址是 <code class="language-plaintext highlighter-rouge">"https://www.google.com/api"</code>，经过框架替换后生成的最终 <strong>URL</strong> 地址为 <code class="language-plaintext highlighter-rouge">"http://www.google.com/api/wiki/part"</code></p>
  </li>
  <li>
    <p>旧 <strong>URL</strong> 地址为 <code class="language-plaintext highlighter-rouge">"https://www.github.com/wiki/part/issues/1#baseurl_path_size=3"</code>，<code class="language-plaintext highlighter-rouge">"#baseurl_path_size=3"</code> 表示其中 <strong>BaseUrl</strong> 为 <code class="language-plaintext highlighter-rouge">"https://www.github.com/wiki/part/issues"</code>，您想替换成的 <strong>BaseUrl</strong> 地址是 <code class="language-plaintext highlighter-rouge">"https://www.google.com/api"</code>，经过框架替换后生成的最终 <strong>URL</strong> 地址为 <code class="language-plaintext highlighter-rouge">"http://www.google.com/api/1"</code></p>
  </li>
</ol>

<h1 id="三种模式比较">三种模式比较</h1>
<p>在升级之前，框架就只有一个默认的普通模式 (只能替换域名)，在升级之后新增了 <strong>高级模式</strong> 和 <strong>超级模式</strong>，这两个模式让框架变得更加强大，在上面的内容中也详细的介绍了这两个模式，现在就来总结下这三个模式，让大家能够按照自己的需求选择出最适合的模式</p>

<h2 id="替换-baseurl-的自由程度-可扩展性">替换 BaseUrl 的自由程度 (可扩展性)</h2>
<h3 id="普通模式--高级模式--超级模式">普通模式 &lt; 高级模式 &lt; 超级模式</h3>

<ul>
  <li>
    <p>普通模式: 只能替换域名</p>
  </li>
  <li>
    <p>高级模式: 只能替换在 <strong>RetrofitUrlManager#startAdvancedModel(String)</strong> 中传入的固定 <strong>BaseUrl</strong> 格式</p>
  </li>
  <li>
    <p>超级模式: 每个 <strong>URL</strong> 都可以随意指定可被替换的 <strong>BaseUrl</strong> 格式</p>
  </li>
</ul>

<h2 id="使用上的复杂程度">使用上的复杂程度</h2>
<h3 id="普通模式--高级模式--超级模式-1">普通模式 &lt; 高级模式 &lt; 超级模式</h3>

<ul>
  <li>
    <p>普通模式: 无需做过多配置</p>
  </li>
  <li>
    <p>高级模式: 在 <strong>App</strong> 初始化时调用一次 <strong>RetrofitUrlManager#startAdvancedModel(String)</strong> 即可</p>
  </li>
  <li>
    <p>超级模式: 每个需要开启超级模式的 <strong>URL</strong> 尾部都需要加入  <strong>RetrofitUrlManager#IDENTIFICATION_PATH_SIZE (#baseurl_path_size=) + PathSize</strong></p>
  </li>
</ul>

<h1 id="总结">总结</h1>
<p>由此可见，自由度越强，使用上也越复杂，所以可以根据自己的需求选择对应的模式，并且也可以根据需求的变化随意升级或降级这三种模式</p>

<p>这次更新让 <a href="https://github.com/JessYanCoding/RetrofitUrlManager">RetrofitUrlManager</a> 的能力提升了一个档次，足以应对各种复杂的 <strong>BaseUrl</strong> 替换需求，正因为 <a href="https://github.com/JessYanCoding/RetrofitUrlManager">RetrofitUrlManager</a> 极强的扩展性，现在甚至可以做到，让服务器可以通过远程动态控制项目中的多个 <strong>BaseUrl</strong></p>

<p>如果还有什么问题或者需求可以给我提 <strong>Issues</strong>，如果 <a href="https://github.com/JessYanCoding/RetrofitUrlManager">RetrofitUrlManager</a> 能够给您带来实质的帮助，也请不要吝啬您的 <strong>Star</strong></p>

<hr />
<p><strong>Hello 我叫Jessyan,如果您喜欢我的文章,可以在以下平台关注我</strong></p>

<ul>
  <li>GitHub:  <a href="https://github.com/JessYanCoding">https://github.com/JessYanCoding</a></li>
  <li>掘金: <a href="https://gold.xitu.io/user/57a9dbd9165abd0061714613">https://gold.xitu.io/user/57a9dbd9165abd0061714613</a></li>
  <li>简书: <a href="https://www.jianshu.com/u/1d0c0bc634db">https://www.jianshu.com/u/1d0c0bc634db</a></li>
  <li>微博: <a href="https://weibo.com/u/1786262517">https://weibo.com/u/1786262517</a></li>
</ul>

<p>– The end</p>]]></content><author><name></name></author><category term="Retrofit" /><summary type="html"><![CDATA[前言 我在之前的文章 《解决Retrofit多BaseUrl及运行时动态改变BaseUrl》 中，介绍了市面上能够解决此类问题的 4 个常见的解决方案，并开源了自己经过优化后的解决方案 RetrofitUrlManager，现在再为大家带来此系列的第二篇文章，这篇文章主要介绍 RetrofitUrlManager 针对 BaseUrl 替换逻辑的重大升级，因为这个升级对于 RetrofitUrlManager 足够重要，将使 RetrofitUrlManager 能够从容应对更多复杂的需求，所以单独写一篇文章让更多的人能够知道]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" /><media:content medium="image" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">MVPArms官方首发一键生成组件化,体验纯傻瓜式组件化开发</title><link href="https://jessyan.me/ArmsComponent-Template/" rel="alternate" type="text/html" title="MVPArms官方首发一键生成组件化,体验纯傻瓜式组件化开发" /><published>2018-05-21T00:00:00+08:00</published><updated>2018-05-21T00:00:00+08:00</updated><id>https://jessyan.me/ArmsComponent-Template</id><content type="html" xml:base="https://jessyan.me/ArmsComponent-Template/"><![CDATA[<h1 id="前言">前言</h1>
<p>我在 <a href="https://www.jianshu.com/p/f671dd76868f">上篇文章</a> 中介绍了 <a href="https://github.com/JessYanCoding/MVPArms/wiki">MVPArms</a> 的官方快速组件化方案 <a href="https://github.com/JessYanCoding/ArmsComponent/wiki">ArmsComponent</a></p>

<p>当时一直强调 <strong>ArmsComponent</strong> 是 <strong>快速</strong> 的组件化方案, 但是在文章中只提供了一个近万字的官方文档, 却没展现出这个组件化方案的快速之处</p>

<p>看到近万字的文档后, 新手已经开始瑟瑟发抖了? 觉得入门成本太高想放弃?</p>

<p>写这篇文章的意义就是为了展现 <strong>快速</strong> 这两个字, 到底有多快? 飞快!</p>

<p>现在我可以不开玩笑的告诉大家, 官方文档上介绍的大部分内容和规范, 现在只需要一键就可以生成, 快速并且零差错, 让新手也可以很愉快的玩耍组件化, 极大的提升开发体验和效率, 你还有什么理由不选择 <a href="https://github.com/JessYanCoding/ArmsComponent/wiki">ArmsComponent</a> 开启组件化的大门呢？</p>

<p>好了, 进入正题, 大家直接看下图</p>

<p><img src="https://raw.githubusercontent.com/JessYanCoding/ArmsComponent-Template/master/art/ArmsComponent-Template.gif" alt="gif" /></p>

<p>看了这个 <strong>GIF</strong> 图过后, 是不是已经跃跃欲试了? 点几下就可以生成组件? <strong>Are you kidding me?</strong> 那好, 您如果不信的话立马去安装然后试一试!</p>

<blockquote>
  <p>Github : <a href="https://github.com/JessYanCoding/ArmsComponent-Template">您的 Star 是我坚持的动力 ✊</a></p>
</blockquote>

<h1 id="使用须知">使用须知</h1>
<ol>
  <li>
    <p>先使用 <strong>ArmsComponent-Template</strong> (<strong>Module</strong> 级一键模板) 一键搭建整体组件架构, 再使用 <a href="https://github.com/JessYanCoding/MVPArmsTemplate">MVPArmsTemplate</a> (<strong>页面</strong> 级一键模板) 一键生成每个业务页面所需要的 <strong>MVP</strong> 及 <strong>Dagger2</strong> 相关类, 即可让什么都不懂的新手也可以一秒开始组件化项目!</p>
  </li>
  <li>
    <p>若您基于本模板修改并且开源于网络, 请注明出处, 尊重开源, 才有人愿意开源, 谢谢!</p>
  </li>
</ol>

<h1 id="如何安装">如何安装？</h1>
<p>请将 <strong>NewArmsComponent</strong> 这个文件夹复制到 <strong>AndroidStudio Module</strong> 模版的存放路径, 请注意是复制整个文件夹, 不是里面的内容!</p>

<p><strong>AndroidStudio Module</strong> 模版存放路径 (<strong>请注意 Module 级模板和页面级模板的存放路径不一样, 不要放错了!</strong>):</p>

<ul>
  <li>
    <p>Windows : AS安装目录/plugins/android/lib/templates/gradle-projects</p>
  </li>
  <li>
    <p>Mac : /Applications/Android Studio.app/Contents/plugins/android/lib/templates/gradle-projects</p>
  </li>
</ul>

<p><strong>最后记得重启 AndroidStudio !</strong></p>

<h1 id="如何使用">如何使用?</h1>
<p>使用时按下图步骤即可, 也可以使用快捷键, <strong>Mac</strong> 的快捷键是在项目名上按 <strong>Command + n</strong>, 选择  <strong>Module</strong>, <strong>Windows</strong> 快捷键自己百度</p>

<p><img src="https://raw.githubusercontent.com/JessYanCoding/ArmsComponent-Template/master/art/step.jpeg" alt="step" /></p>

<h1 id="注意事项">注意事项</h1>
<ul>
  <li>
    <p>本模板是基于 <a href="https://github.com/JessYanCoding/ArmsComponent">ArmsComponent</a> 开发的, 所以使用的是 <strong>ArmsComponent</strong> 的整体架构, 最优的方式是直接 <strong>clone</strong> 或下载 <strong>ArmsComponent</strong> 工程后, 在工程上面直接使用本模板, 开始业务的开发, 让您体验纯傻瓜式的组件化开发</p>
  </li>
  <li>使用本模板生成的组件马上就可以独立运行, 但是如果您想要集成调试, 还需要在宿主 <strong>App</strong> 中 <a href="https://github.com/JessYanCoding/ArmsComponent/blob/master/app/build.gradle#L55">依赖此组件</a>
    <div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">dependencies</span> <span class="o">{</span>
    
  <span class="k">if</span> <span class="o">(!</span><span class="n">isBuildModule</span><span class="o">.</span><span class="na">toBoolean</span><span class="o">())</span> <span class="o">{</span>
      <span class="c1">//在宿主 App 中使用 implementation 依赖业务组件, 业务组件的代码就会完全暴露给宿主 App, 不利于代码的隔离</span>
      <span class="c1">//使用 runtimeOnly 依赖业务组件, 可实现业务组件的代码在编译时对宿主 App 不可见, 仅在运行时对宿主 App 可见</span>
      <span class="n">runtimeOnly</span> <span class="nf">project</span><span class="o">(</span><span class="s2">":ModuleZhihu"</span><span class="o">)</span>
      <span class="n">runtimeOnly</span> <span class="nf">project</span><span class="o">(</span><span class="s2">":ModuleGank"</span><span class="o">)</span>
      <span class="n">runtimeOnly</span> <span class="nf">project</span><span class="o">(</span><span class="s2">":ModuleGold"</span><span class="o">)</span>
      <span class="n">runtimeOnly</span> <span class="nf">project</span><span class="o">(</span><span class="s2">":组件名(Module name)"</span><span class="o">)</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>此模板会持续保持更新, 但只保证能够兼容最新的 <strong>AndroidStudio 稳定版</strong>, 暂不提供其他版本</p>

    <p><img src="https://raw.githubusercontent.com/JessYanCoding/ArmsComponent-Template/master/art/attention-1.jpeg" alt="attention-1" /></p>

    <p><img src="https://raw.githubusercontent.com/JessYanCoding/ArmsComponent-Template/master/art/attention-2.jpeg" alt="attention-2" /></p>
  </li>
</ul>

<hr />
<p><strong>Hello 我叫Jessyan,如果您喜欢我的文章,可以在以下平台关注我</strong></p>

<ul>
  <li>GitHub:  <a href="https://github.com/JessYanCoding">https://github.com/JessYanCoding</a></li>
  <li>掘金: <a href="https://gold.xitu.io/user/57a9dbd9165abd0061714613">https://gold.xitu.io/user/57a9dbd9165abd0061714613</a></li>
  <li>简书: <a href="https://www.jianshu.com/u/1d0c0bc634db">https://www.jianshu.com/u/1d0c0bc634db</a></li>
  <li>微博: <a href="https://weibo.com/u/1786262517">https://weibo.com/u/1786262517</a></li>
</ul>

<p>– The end</p>]]></content><author><name></name></author><category term="Architecture" /><summary type="html"><![CDATA[前言 我在 上篇文章 中介绍了 MVPArms 的官方快速组件化方案 ArmsComponent]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" /><media:content medium="image" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">MVPArms官方快速组件化方案开源,来自5K star的信赖</title><link href="https://jessyan.me/ArmsComponent/" rel="alternate" type="text/html" title="MVPArms官方快速组件化方案开源,来自5K star的信赖" /><published>2018-05-07T00:00:00+08:00</published><updated>2018-05-07T00:00:00+08:00</updated><id>https://jessyan.me/ArmsComponent</id><content type="html" xml:base="https://jessyan.me/ArmsComponent/"><![CDATA[<p><img src="https://raw.githubusercontent.com/JessYanCoding/ArmsComponent/master/arts/arms_component_banner_v1.0.jpg" alt="Logo" /></p>

<ul>
  <li>0 <a href="#0">前言</a>
    <ul>
      <li>0.1 <a href="#0.1">起源</a></li>
      <li>0.2 <a href="#0.2">组件化方案分析</a>
        <ul>
          <li>0.2.1 <a href="#0.2.1">业务组件的划分和代码隔离</a></li>
          <li>0.2.2 <a href="#0.2.2">路由框架</a></li>
          <li>0.2.3 <a href="#0.2.3">基础库</a></li>
        </ul>
      </li>
      <li>0.3 <a href="#0.3">ArmsComponent 的优势</a></li>
    </ul>
  </li>
  <li>1 <a href="#1">简介</a>
    <ul>
      <li>1.1 <a href="#1.1">什么是组件化?</a></li>
      <li>1.2 <a href="#1.2">为什么要组件化?</a></li>
      <li>1.3 <a href="#1.3">分析现有的组件化方案</a></li>
      <li>1.4 <a href="#1.4">如何选择组件化方案?</a></li>
    </ul>
  </li>
  <li>2 <a href="#2">组件化方案描述</a>
    <ul>
      <li>2.1 <a href="#2.1">架构图一览</a></li>
      <li>2.2 <a href="#2.2">架构图详解</a>
        <ul>
          <li>2.2.1 <a href="#2.2.1">宿主层</a></li>
          <li>2.2.2 <a href="#2.2.2">业务层</a>
            <ul>
              <li>2.2.2.1 <a href="#2.2.2.1">业务模块的拆分</a></li>
            </ul>
          </li>
          <li>2.2.3 <a href="#2.2.3">基础层</a>
            <ul>
              <li>2.2.3.1 <a href="#2.2.3.1">核心基础业务</a></li>
              <li>2.2.3.2 <a href="#2.2.3.2">公共服务</a></li>
              <li>2.2.3.3 <a href="#2.2.3.3">基础 SDK</a>
                <ul>
                  <li>2.2.3.3.1 <a href="#2.2.3.3.1">MVPArms</a></li>
                  <li>2.2.3.3.2 <a href="#2.2.3.3.2">UI 组件</a></li>
                  <li>2.2.3.3.3 <a href="#2.2.3.3.3">其他 SDK</a></li>
                </ul>
              </li>
            </ul>
          </li>
        </ul>
      </li>
      <li>2.3 <a href="#2.3">跨组件通信</a>
        <ul>
          <li>2.3.1 <a href="#2.3.1">为什么需要跨组件化通信?</a></li>
          <li>2.3.2 <a href="#2.3.2">跨组件通信场景</a></li>
          <li>2.3.3 <a href="#2.3.3">跨组件通信方案</a></li>
          <li>2.3.4 <a href="#2.3.4">跨组件通信方案分析</a></li>
          <li>2.3.5 <a href="#2.3.5">跨组件传递复杂数据格式</a></li>
        </ul>
      </li>
      <li>2.4 <a href="#2.4">组件的生命周期</a>
        <ul>
          <li>2.4.1 <a href="#2.4.1">问题分析</a></li>
          <li>2.4.2 <a href="#2.4.2">可行方案分析</a></li>
          <li>2.4.3 <a href="#2.4.3">最终执行方案</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li>3 <a href="#3">项目讲解</a>
    <ul>
      <li>3.1 <a href="#3.1">如何让组件独立运行?</a></li>
      <li>3.2 <a href="#3.2">配置 AndroidManifest</a></li>
      <li>3.3 <a href="#3.3">配置 ConfigModule</a></li>
      <li>3.4 <a href="#3.4">RouterHub</a></li>
      <li>3.5 <a href="#3.5">EventBusHub</a></li>
      <li>3.6 <a href="#3.6">在项目中使用多个不同的域名</a></li>
    </ul>
  </li>
</ul>

<p><a name="0"></a></p>
<h1 id="0-前言">0 前言</h1>
<p><a href="https://github.com/JessYanCoding/MVPArms"><strong>MVPArms</strong></a> 从两年前开源至今, 已经累积了 <strong>5k star</strong>, 获得了上千个商业项目的信赖和认可</p>

<p>回顾两年前, 那时 <strong>MVPArms</strong> 还没诞生, <strong>MVP</strong>、<strong>Dagger2</strong>、<strong>Rxjava</strong>、<strong>Retrofit</strong> 这些技术在国内才刚刚开始流行, 在网上能搜索到的中文学习资料远没有现在这么丰富, 特别是 <strong>Dagger</strong>, 在网上能搜索到的文章甚至有很多讲的是 <strong>Square</strong> 的 <strong>Dagger1</strong>, 学习资料的匮乏加上 <strong>Dagger2</strong> 本身就是块硬骨头, 让本人在学习的道路上不知道多走了多少弯路</p>

<p>从那时开始, 让初学者都能够快速搭建一个 <strong>MVP</strong> + <strong>Dagger2</strong> + <strong>Retrofit</strong> + <strong>Rxjava</strong> 项目的种子就已经深埋在心中, 后面经过不懈的努力, <strong>MVPArms</strong> 终于诞生并开源了, 开源以后只是一直坚持将 <strong>代码</strong>, <strong>注释</strong> 和 <strong>文档</strong> 做到极致, 没想到的是, <strong>MVPArms</strong> 能发展到如今的体量, 感谢开源!</p>

<p><a name="0.1"></a></p>
<h2 id="01-起源">0.1 起源</h2>
<p>这是 <strong>MVPArms</strong> 的起源, <strong>ArmsComponent</strong> 的起源同样相似, 从去年开始, 组件化逐渐火热起来, 本人也在去年年初开始在公司项目中进行组件化, 一切还算顺利</p>

<p>那时同样的种子继续埋在了心中, 我想让刚刚接触组件化的初学者也能快速搭建一个中小型的组件化项目, 经过一年的不断优化, 终于决定将其开源(<strong>MVPArms</strong> 官方组件化方案 <strong>ArmsComponent</strong>)</p>

<blockquote>
  <p>Github : <a href="https://github.com/JessYanCoding/ArmsComponent">您的 Star 是我坚持的动力 ✊</a></p>
</blockquote>

<p><a name="0.2"></a></p>
<h2 id="02-组件化方案分析">0.2 组件化方案分析</h2>
<p>看了很多组件化方案, 所以总结了在组件化中很重要的三个大点:</p>

<ol>
  <li>基础库(网络请求、图片加载等)的封装</li>
  <li>路由框架(页面跳转, 服务提供)</li>
  <li>业务组件的划分和代码隔离</li>
</ol>

<p><a name="0.2.1"></a></p>
<h3 id="021-业务组件的划分和代码隔离">0.2.1 业务组件的划分和代码隔离</h3>
<p>先说第三点 <strong>业务组件的划分和代码隔离</strong>, 现在大部分的文章都围绕着这点, 我这里发表下个人的观点, 第三点确实是很重要的一点, 不管是大厂的方案还是小厂的方案都有借鉴之处, 但是这点也是最不可能讨论出最终结果和统一解决方案的一点</p>

<p>每个公司、每个项目的情况都不一样, 大厂的方案真的适合您吗? 不一定, 大厂几十个业务群, 几百号开发人员, 他们的组织结构和项目规模都不是普通公司能比拟的, 如果伸拉硬套他们的方案, 进行更严格更细粒度的代码隔离, 可能产出的价值还不及您先前付出的代价, 带来效率的降低, 所以根据项目的实际情况做出灵活的调整才是项目负责人最应该干的事, 这点我在后面会有详细的介绍</p>

<p><a name="0.2.2"></a></p>
<h3 id="022-路由框架">0.2.2 路由框架</h3>
<p>陆续也有很多组件化方案开源自己的 <strong>路由框架</strong>, 是个很不错的开始, 我觉得大家写的都不错, 各有各的优势, 本方案也决定用别人的 <strong>路由框架</strong>, 自己写的原理也差不多, 还不一定比别人考虑的完善, 还要自己维护, 为什么不选择一个成熟稳定的呢？</p>

<p><a name="0.2.3"></a></p>
<h3 id="023-基础库">0.2.3 基础库</h3>
<p>很多组件化文章只是在讲如何拆分以及封装基础库, 但是至今没看到有哪个组件化方案开源过完整的基础库的, 我猜测原因可能是, 组件化方案都是从商业项目不断的业务迭代中逐渐完善的, 基础库也属于公司的核心机密, 所以不可能开源</p>

<p>但是基础库尤其重要, 特别是兼容组件化的基础库, 这关乎到组件化方案的根基, 根基都没有, 何谈其他更高级的功能? 但是从封装到完善一个兼容组件化的基础库是需要很长时间的, 大多数中小公司是不愿意投入这个成本的</p>

<p><a name="0.3"></a></p>
<h2 id="03-armscomponent-的优势">0.3 ArmsComponent 的优势</h2>
<p><strong>ArmsComponent</strong> 拥有一键自动生成组件功能, <a href="https://github.com/JessYanCoding/ArmsComponent-Template">可以一键搭建整体组件架构, 让您体验纯傻瓜式的组件化开发</a>, 虽然一键搭建功能让新手也可以一秒开始组件化项目, 但本方案还是提供有上万字的详细文档让您可以更深入的了解本方案, 并且 <strong>JessYan</strong> 郑重承诺, <strong>ArmsComponent</strong> 将和 <strong>MVPArms</strong> 一起持续维护下去, <strong>ArmsComponent</strong> 绝对是一个真正用心对待的组件化方案</p>

<p><strong>MVPArms</strong> 是一个开源两年, 成熟稳定, 涵盖大量主流技术且兼容组件化的基础库,  <strong>MVPArms</strong> 使得 <strong>ArmsComponent</strong> 成为了唯一提供完整基础库的组件化方案, 这就是 <strong>ArmsComponent</strong> 相对于其他组件化方案最大的优势</p>

<p>因为有了 <strong>基础库</strong> 的存在, 再加上已有的 <strong>路由框架</strong>, 组件化中的三个大点就已经占有两个(<strong>业务组件的划分和代码隔离</strong> 在后面会有介绍), 因此使用 <strong>ArmsComponent</strong> 启动一个新项目, 即可快速进行组件化, 将 <strong>Demo</strong> (组件化项目雏形) 克隆下来后, 稍作修改, 马上就可以投入到业务的开发之中</p>

<p><strong>ArmsComponent</strong> 对于新项目以及已经开始使用 <strong>MVPArms</strong> 的项目将会更加便捷, 有着优于其他组件化方案的体验, 对于那些网络请求, 图片加载等基础功能乱七八糟散落到项目各处, 没有统一抽离出来的旧项目, 建议直接使用 <strong>MVPArms</strong>, 开始组件化</p>

<p>如果您不想使用 <strong>MVPArms</strong>, 觉得接入成本太大, 没关系, 借鉴下 <a href="https://github.com/JessYanCoding/MVPArms"><strong>MVPArms</strong></a> 和 <a href="https://github.com/JessYanCoding/ArmsComponent"><strong>ArmsComponent</strong></a> 的代码, 尝试改造自己的项目, 也是个不错的选择</p>

<p><a name="1"></a></p>
<h1 id="1-简介">1 简介</h1>
<p>好了, 进入正题!</p>

<p><a name="1.1"></a></p>
<h2 id="11-什么是组件化">1.1 什么是组件化?</h2>
<p>组件化简单概括就是把一个功能完整的 <strong>App</strong> 或模块拆分成多个子模块, 每个子模块可以独立编译和运行, 也可以任意组合成另一个新的 <strong>App</strong> 或模块, 每个模块即不相互依赖但又可以相互交互, 遇到某些特殊情况甚至可以升级或者降级</p>

<p><a name="1.2"></a></p>
<h2 id="12-为什么要组件化">1.2 为什么要组件化?</h2>
<p>现在的项目随着需求的增加规模变得越来越大, 规模的增大带来了很多烦恼, 各种业务错中复杂的交织在一起, 每个业务模块之间, 代码没有约束, 带来了代码边界的模糊, 代码冲突时有发生, 更改一个小问题可能引起一些新的问题, 牵一发而动全身, 增加一个新需求, 瞻前顾后的熟悉了大量前辈们写的代码后才敢动手, 编译时间也不在断增加, 开发效率极度的下降, 在这种情况下组件化的出现就是为了解决以上的烦恼</p>

<p><a name="1.3"></a></p>
<h2 id="13-分析现有的组件化方案">1.3 分析现有的组件化方案</h2>
<p>很多大厂的组件化方案是以 <strong>多工程</strong> + <strong>多 Module</strong> 的结构(微信, 美团等超级 <strong>App</strong> 更是以 <strong>多工程</strong> + <strong>多 Module</strong> + <strong>多 P 工程(以页面为单元的代码隔离方式)</strong> 的三级工程结构), 使用 <strong>Git Submodule</strong> 创建多个子仓库管理各个模块的代码, 并将各个模块的代码打包成 <strong>AAR</strong> 上传至私有 <strong>Maven</strong> 仓库使用远程版本号依赖的方式进行模块间代码的隔离</p>

<p><a name="1.4"></a></p>
<h2 id="14-如何选择组件化方案">1.4 如何选择组件化方案?</h2>
<p>按照康威定律, 系统架构的设计需要根据组织间的沟通结构, 因为现在大部分项目的规模和开发人员的数量以及结构还不足以需要某些大厂发布的组件化方案支撑(大厂的组织结构和项目规模都非常庞大, 他们的方案不一定完全适合所有公司的项目), 进行更严格更细粒度的代码间以及模块间的隔离, 盲目的使用某些组件化方案, 可能会带来开发效率降低, 开发成本远大于收益等情况, 性价比变低, 作为项目负责人, 应该根据项目目前的规模以及开发人员的组织结构去选择目前最适合的组件化方案, 做到以项目实际情况去制定技术方案, 而不是盲目跟随某些大厂的技术方案让项目和开发人员花费大量时间去调整和适应</p>

<p><a name="2"></a></p>
<h1 id="2-组件化方案描述">2 组件化方案描述</h1>
<p><strong>ArmsComponent</strong> 目前采用的是 <strong>单工程</strong> + <strong>多 Module</strong> 的结构, 由于 <strong>Demo</strong> 较小仅仅为了展示基本规范, 所以也只是采用源码依赖并没有做到远程版本号依赖组件, 代码管理也只是采用 <strong>单仓库</strong> + <strong>多分支</strong> 的方式, 这样也是对于开发初期, 项目规模还较小, 开发人员也较少时, 开发效率较高的方案, 如果您的项目规模较大, 开发人员众多, 就可以采用上面提到的 <strong>多工程</strong> + <strong>多 Module</strong>, 并使用私有 <strong>Maven</strong> 仓库管理组件版本</p>

<p><strong>世界上没有一个方案可以完美到兼顾所有情况, 并且还满足所有人, 所有项目的需求, 所以项目负责人必须按照项目实际情况做出灵活的调整, 才能做出最适合自家项目的方案</strong></p>

<p><a name="2.1"></a></p>
<h2 id="21-架构图一览">2.1 架构图一览</h2>

<p><img src="https://raw.githubusercontent.com/JessYanCoding/ArmsComponent/master/arts/ArmsComponentArchitecture.jpeg" alt="ArmsComponentArchitecture" /></p>
<p align="center"><font color="gray">ArmsComponent 组件化架构图</font></p>

<p><a name="2.2"></a></p>
<h2 id="22-架构图详解">2.2 架构图详解</h2>
<p>目前架构一共分为三层, 从低到高依次是基础层, 业务层和宿主层, 由于目前项目较小人员较少所以三层都集中在一个工程中, 但您可以根据项目的规模和开发人员的数量拆分成多个工程协同开发</p>

<p><a name="2.2.1"></a></p>
<h3 id="221-宿主层">2.2.1 宿主层</h3>
<p>宿主层位于最上层, 主要作用是作为一个 <strong>App</strong> 壳, 将需要的模块组装成一个完整的 <strong>App</strong>, 这一层可以管理整个 <strong>App</strong> 的生命周期(比如 <strong>Application</strong> 的初始化和各种组件以及三方库的初始化)</p>

<p><a name="2.2.2"></a></p>
<h3 id="222-业务层">2.2.2 业务层</h3>
<p>业务层位于中层, 里面主要是根据业务需求和应用场景拆分过后的业务模块, 每个模块之间互不依赖, 但又可以相互交互, 比如一个商城 <strong>App</strong> 由 <strong>搜索</strong>, <strong>订单</strong>, <strong>购物车</strong>, <strong>支付</strong> 等业务模块组成</p>

<blockquote>
  <p><strong>Tips: 每个业务模块都可以拥有自己独有的 SDK 依赖和自己独有的 UI 资源 (如果是其他业务模块都可以通用的 SDK 依赖 和 UI 资源 就可以将它们抽离到 <a href="#2.2.3.3">基础 SDK(CommonSDK 2.2.3.3)</a> 和 <a href="#2.2.3.3.2">UI 组件(CommonRes 2.2.3.3.2)</a> 中)</strong></p>
</blockquote>

<p><a name="2.2.2.1"></a></p>
<h4 id="2221-业务模块的拆分">2.2.2.1 业务模块的拆分</h4>
<p>写业务之前先不要急着动手敲码, 应该先根据初期的产品需求到后期的运营规划结合起来清晰的梳理一下业务在未来可能会发生的发展, 确定业务之间的边界, 以及可能会发生的变化, 最后再确定下来真正需要拆分出来的业务模块再进行拆分</p>

<p><a name="2.2.3"></a></p>
<h3 id="223-基础层">2.2.3 基础层</h3>
<p>基础层位于最底层, 里面又包括 <strong>核心基础业务模块</strong>、<strong>公共服务模块</strong>、 <strong>基础 SDK 模块</strong>, <strong>核心基础业务模块</strong> 和 <strong>公共服务模块</strong> 主要为业务层的每个模块服务, <strong>基础 SDK 模块</strong> 含有各种功能强大的团队自行封装的 <strong>SDK</strong> 以及第三方 <strong>SDK</strong>, 为整个平台的基础设施建设提供动力</p>

<p><a name="2.2.3.1"></a></p>
<h4 id="2231-核心基础业务">2.2.3.1 核心基础业务</h4>
<p><strong>核心基础业务</strong> 为 <strong>业务层</strong> 的每个业务模块提供一些与业务有关的基础服务, 比如在项目中以用户角色分为 2 个端口, 用户可以扮演多个角色, 但是在线上只能同时操作一个端口的业务, 这时每个端口都必须提供一个角色切换的功能, 以供用户随时在多个角色中切换,
这时在项目中就需要提供一个用于用户自由切换角色的管理类作为 <strong>核心基础业务</strong> 被这 2 个端口所依赖(类似 拉勾, Boss 直聘等 <strong>App</strong> 可以在招聘者和应聘者之间切换)</p>

<p><strong>核心基础业务</strong> 的划分应该遵循是否为业务层大部分模块都需要的基础业务, 以及一些需要在各个业务模块之间交互的业务, 都可以划分为 <strong>核心基础业务</strong></p>

<p><a name="2.2.3.2"></a></p>
<h4 id="2232-公共服务">2.2.3.2 公共服务</h4>
<p><strong>公共服务</strong> 是一个名为 <strong>CommonService</strong> 的 <strong>Module</strong>, 主要的作用是用于 <strong>业务层</strong> 各个模块之间的交互(自定义方法和类的调用), 包含自定义 <strong>Service</strong> 接口, 和可用于跨模块传递的自定义类</p>

<p>主要流程是:</p>

<p>提供服务的业务模块:</p>
<blockquote>
  <p>在公共服务(<strong>CommonService</strong>) 中声明 <strong>Service</strong> 接口 (含有需要被调用的自定义方法), 然后在自己的模块中实现这个 <strong>Service</strong> 接口, 再通过 <strong>ARouter API</strong> 暴露实现类</p>
</blockquote>

<p>使用服务的业务模块:</p>
<blockquote>
  <p>通过 <strong>ARouter</strong> 的 <strong>API</strong> 拿到这个 <strong>Service</strong> 接口(多态持有, 实际持有实现类), 即可调用 <strong>Service</strong> 接口中声明的自定义方法, 这样就可以达到模块之间的交互</p>
</blockquote>

<p>跨模块传递的自定义类:</p>

<p>在 <strong>公共服务</strong> 中定义需要跨模块传递的自定义类后 (<strong>Service</strong> 中的自定义方法和 <strong>EventBus</strong> 中的事件实体类都可能需要用到自定义类), 就可以通过 <strong>ARouter API</strong>, 在各个模块的页面之间跨模块传递这个自定义对象 (<strong>ARouter</strong> 要求在 <strong>URL</strong> 中使用 <strong>Json</strong> 参数传递自定义对象必须实现 <strong>SerializationService</strong> 接口)</p>

<blockquote>
  <p><strong>Tips: 建议在 CommonService 中给每个需要提供服务的业务模块都建立一个单独的包, 然后在这个包下放 Service 接口 和 需要跨模块传递的自定义类, 这样更好管理</strong></p>
</blockquote>

<p><strong>掌握公共服务层的用法最好要了解 ARouter 的 API</strong></p>

<blockquote>
  <p><a href="https://github.com/alibaba/ARouter"><strong>点击查阅 ARouter 文档</strong></a></p>
</blockquote>

<p><a name="2.2.3.3"></a></p>
<h4 id="2233-基础-sdk">2.2.3.3 基础 SDK</h4>
<p><strong>基础 SDK</strong> 是一个名为 <strong>CommonSDK</strong> 的 <strong>Module</strong>, 其中包含了大量功能强大的 <strong>SDK</strong>, 提供给整个架构中的所有模块</p>

<p><a name="2.2.3.3.1"></a>
<strong>2.2.3.3.1 MVPArms</strong></p>

<p><strong>MVPArms</strong> 是整个基础层中最重要的模块, 可谓是整个组件化架构中的心脏, 里面提供了开发一个完整项目所必须的一整套 <strong>API</strong> 和 <strong>SDK</strong>, 是整个项目的脚手架, 我用它来统一整个组件化方案的基础设施, 使每一个模块更加健壮, 因为有了 <strong>MVPArms</strong>, 使得 <strong>ArmsComponent</strong> 成为了唯一提供完整基础框架的组件化方案, 所以学习 <strong>ArmsComponent</strong> 之前必须先学会 <strong>MVPArms</strong></p>

<blockquote>
  <p>学习 <strong>MVPArms</strong> 时请按以下排列顺序依次学习:</p>
</blockquote>

<p>1.<a href="https://github.com/JessYanCoding/MVPArms"><strong>点击学习 Demo</strong></a></p>

<p>2.<a href="https://github.com/JessYanCoding/MVPArms/wiki"><strong>点击查阅 详细文档</strong></a></p>

<p>3.<a href="https://github.com/JessYanCoding/MVPArmsTemplate"><strong>点击下载 一键生成代码插件</strong></a></p>

<p><a name="2.2.3.3.2"></a>
<strong>2.2.3.3.2 UI 组件</strong></p>

<p><strong>基础 SDK</strong> 中的 <strong>UI 组件</strong> 是一个名为 <strong>CommonRes</strong> 的 <strong>Module</strong>, 主要放置一些业务层可以通用的与 <strong>UI</strong> 有关的资源供所有业务层模块使用, 便于重用、管理和规范已有的资源</p>

<blockquote>
  <p><strong>Tips: 值得注意的是, 业务层的某些模块如果出现有资源名命名相同的情况 (如两个图片命名相同), 当在宿主层集成所有模块时就会出现资源冲突的问题, 这时注意在每个模块的 build.gradle 中使用 resourcePrefix 标签给每个模块下的资源名统一加上不同的前缀即可解决此类问题</strong></p>
</blockquote>

<div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">android</span> <span class="o">{</span>
    <span class="n">defaultConfig</span> <span class="o">{</span>
        <span class="n">minSdkVersion</span> <span class="n">rootProject</span><span class="o">.</span><span class="na">ext</span><span class="o">.</span><span class="na">android</span><span class="o">[</span><span class="s2">"minSdkVersion"</span><span class="o">]</span>
        <span class="o">...</span>
    <span class="o">}</span>
    <span class="n">resourcePrefix</span> <span class="s2">"public_"</span>
<span class="o">}</span>
</code></pre></div></div>

<p>可以放置的资源类型有:</p>

<ul>
  <li>通用的 <strong>Style</strong>, <strong>Theme</strong></li>
  <li>通用的 <strong>Layout</strong></li>
  <li>通用的 <strong>Color</strong>, <strong>Dimen</strong>, <strong>String</strong></li>
  <li>通用的 <strong>Shape</strong>, <strong>Selector</strong>, <strong>Interpolator</strong></li>
  <li>通用的 <strong>图片资源</strong></li>
  <li>通用的 <strong>动画资源</strong></li>
  <li>通用的 <strong>自定义 View</strong></li>
  <li>通用的第三方 <strong>自定义 View</strong></li>
</ul>

<p><a name="2.2.3.3.3"></a>
<strong>2.2.3.3.3 其他 SDK</strong></p>

<p><strong>其他 SDK</strong> 主要是 <strong>基础 SDK</strong> 依赖的一些业务层可以通用的 <strong>第三方库</strong> 和 <strong>第三方 SDK</strong> (比如 <strong>ARouter</strong>, <strong>腾讯 X5 内核</strong>), 便于重用、管理和规范已有的 <strong>SDK</strong> 依赖</p>

<p><a name="2.3"></a></p>
<h2 id="23-跨组件通信">2.3 跨组件通信</h2>

<p><a name="2.3.1"></a></p>
<h3 id="231-为什么需要跨组件通信">2.3.1 为什么需要跨组件通信?</h3>
<p>因为各个业务模块之间是各自独立的, 并不会存在相互依赖的关系, 所以一个业务模块是访问不了其他业务模块的代码的, 如果想从 <strong>A</strong> 业务模块的 <strong>A</strong> 页面跳转到 <strong>B</strong> 业务模块的 <strong>B</strong> 页面, 光靠模块自身是不能实现的, 所以这时必须依靠外界的其他媒介提供这个跨组件通信的服务</p>

<p><a name="2.3.2"></a></p>
<h3 id="232-跨组件通信场景">2.3.2 跨组件通信场景</h3>
<p>跨组件通信主要有以下两种场景:</p>

<ul>
  <li>
    <p>第一种是组件之间的页面跳转 (<strong>Activity</strong> 到 <strong>Activity</strong>,  <strong>Fragment</strong> 到 <strong>Fragment</strong>, <strong>Activity</strong> 到 <strong>Fragment</strong>, <strong>Fragment</strong> 到 <strong>Activity</strong>) 以及跳转时的数据传递 (基础数据类型和可序列化的自定义类类型)</p>
  </li>
  <li>
    <p>第二种是组件之间的自定义类和自定义方法的调用(组件向外提供服务)</p>
  </li>
</ul>

<p><a name="2.3.3"></a></p>
<h3 id="233-跨组件通信方案">2.3.3 跨组件通信方案</h3>
<p>其实以上两种通信场景甚至其他更高阶的功能在 <strong>ARouter</strong> 中都已经被实现, <strong>ARouter</strong> 是 <strong>Alibaba</strong> 开源的一个 <strong>Android</strong> 路由中间件, 可以满足很多组件化的需求, 也是作为本方案中比较重要的一环, 需要认真看下文档, 了解下基本使用</p>

<p>关于跨组件通信框架, 本方案不做封装, 专业的事交给专业的人做, 此类优秀框架为数众多, 各有特点, 可以根据您的需求选择您喜欢的框架, 不一定非得是 <strong>ARouter</strong>, 选择 <strong>ARouter</strong> 也是因为 <strong>Alibaba</strong> 出品, 开源时间较长, 使用者众多, 相对稳定, 出现问题比较好沟通</p>

<p><a name="2.3.4"></a></p>
<h3 id="234-跨组件通信方案分析">2.3.4 跨组件通信方案分析</h3>
<p>第一种组件之间的页面跳转不需要过多描述了, 算是 <strong>ARouter</strong> 中最基础的功能, <strong>API</strong> 也比较简单, 跳转时想传递不同类型的数据也提供有相应的 <strong>API</strong> (如果想通过在 <strong>URL</strong> 中使用 <strong>Json</strong> 参数传递自定义对象, 需要实现  <strong>SerializationService</strong>, 详情请查阅 <a href="https://github.com/alibaba/ARouter">ARouter 文档</a>);</p>

<p>第二种组件之间的自定义类和自定义方法的调用要稍微复杂点, 需要 <strong>ARouter</strong> 配合架构中的 公共服务(<strong>CommonService</strong>) 实现, 主要流程在 <a href="#2.2.3.2">公共服务(<strong>2.2.3.2</strong>)</a> 中已有介绍, 这里我画了个示意图, 以便大家更好理解</p>

<p><img src="https://raw.githubusercontent.com/JessYanCoding/ArmsComponent/master/arts/Router.jpeg" alt="Router" /></p>
<p align="center"><font color="gray">跨组件通信示意图</font></p>

<p>此种服务提供方式叫作 <strong>接口下沉</strong>, 看图的同时请配合阅读 <a href="#2.2.3.2">公共服务(<strong>2.2.3.2</strong>)</a> 中的主要流程便于理解</p>

<p>本方案中还提供有 <strong>EventBus</strong> 来作为服务提供的另一种方式, 大家知道 <strong>EventBus</strong> 因为其解耦的特性, 如果被滥用的话会使项目调用层次结构混乱, 不便于维护和调试, 所以本方案使用 <a href="https://github.com/hehonghui/AndroidEventBus"><strong>AndroidEventBus</strong></a> 其独有的 <strong>Tag</strong>, 可以在开发时更容易定位发送事件和接受事件的代码, 如果以组件名来作为 <strong>Tag</strong> 的前缀进行分组, 也可以更好的统一管理和查看每个组件的事件, 当然也不建议大家过多使用 <strong>EventBus</strong></p>

<blockquote>
  <p><strong>Tips: 每个跨组件通信框架提供服务的方式都不同, 您也可以选择其他框架的服务提供方式</strong></p>
</blockquote>

<p><a name="2.3.5"></a></p>
<h3 id="235-跨组件传递复杂数据格式">2.3.5 跨组件传递复杂数据格式</h3>
<p>在一般情况下基本数据类型就可以满足大多数跨组件传递数据的需求, 但是在某些情况下也会需要传递复杂的自定义数据类型, 传递自定义类型在方案中也提供有两种方式:</p>

<p>第一种在 <a href="#2.2.3.2">公共服务(<strong>2.2.3.2</strong>)</a> 中已提及, 就是在 <strong>公共服务 (CommonService)</strong> 中定义这个自定义类</p>

<p>第二种方式也比较简单, 直接通过解析 <strong>Json</strong> 字符串就可以传递</p>

<p><a name="2.4"></a></p>
<h2 id="24-组件的生命周期">2.4 组件的生命周期</h2>
<p>每个组件 (模块) 在测试阶段都可以独立运行, 在独立运行时每个组件都可以指定自己的 <strong>Application</strong>, 这时组件自己管理生命周期就轻而易举, 比如想在 <strong>onCreate</strong> 中初始化一些代码都可以轻松做到, 但是当进入集成调试阶段, 组件自己的 <strong>Application</strong> 已不可用, 每个组件都只能依赖于宿主的生命周期, 这时每个组件如果需要初始化自己独有的代码, 该怎么办?</p>

<p><a name="2.4.1"></a></p>
<h3 id="241-问题分析">2.4.1 问题分析</h3>
<p>在集成调试阶段, 宿主依赖所有组件, 但是每个组件却不能依赖宿主, 意思是每个组件根本不知道自己的宿主是谁, 当然也就不能通过访问代码的方式直接调用宿主的方法, 从而在宿主的生命周期里加入自己的逻辑代码</p>

<p>如果直接将每个模块的初始化代码直接复制进宿主的生命周期里, 这样未免过于暴力, 不仅代码耦合不易扩展, 而且代码还极易冲突, 所以修改宿主源码的方式也不可行</p>

<p>所以有没有什么方法可以让每个组件在集成调试阶段都可以独自管理自己的生命周期呢?</p>

<p>其实解决思路很简单, 无非就是在开发时让每个组件可以独立管理自己的生命周期, 在运行时又可以让每个组件的生命周期与宿主的生命周期进行合并 (在不修改或增加宿主代码的情况下完成)</p>

<p><a name="2.4.2"></a></p>
<h3 id="242-可行方案分析">2.4.2 可行方案分析</h3>
<p>想在不更改宿主代码的情况下在宿主的生命周期中动态插入每个组件的代码, 这倒有点像 <strong>AOP</strong> 的意思</p>

<p>现有的解决方案大概有三种:</p>

<ol>
  <li>
    <p>在基础层中提供一个用于管理组件生命周期的管理类, 每个组件都手动将自己的生命周期实现类注册进这个管理类, 在集成调试时, 宿主在自己的 <strong>Application</strong> 对应生命周期方法中通过管理类去遍历调用注册的所有生命周期实现类即可</p>
  </li>
  <li>使用 <strong>AnnotationProcessor</strong> 解析注解在编译期间生成源代码自动注册所有组件的生命周期实现类, 然后宿主再在对应的生命周期方法中去调用</li>
  <li>使用 <strong>Javassist</strong> 在编译时动态修改 <strong>class</strong> 文件, 直接在宿主的对应生命周期方法中插入每个组件的生命周期逻辑</li>
</ol>

<p>我最后还是选择了第一种方法, 因为后面两种方法虽然使用简单, 还可以自动化的完成所有操作, 非常炫酷, 但是这两种方法技术实现复杂, 在不同的 <strong>Gradle</strong> 版本中还会出现兼容性问题影响整个项目的开发进度, 较难维护, 还会增加编译时间</p>

<p>选择第一种方法虽然增加了几步操作, 但是简单明了, 便与理解和维护, 后续人员加入也可以很快上手, 不受 <strong>Gradle</strong> 版本的影响, 也不会增加编译时间</p>

<p><a name="2.4.3"></a></p>
<h3 id="243-最终执行方案">2.4.3 最终执行方案</h3>
<p>第一种方案具体原理也没什么好说的, 比较简单, 大概就是在基础层中定义有生命周期方法 (<strong>attachBaseContext(), onCreate()</strong> …) 的接口, 每个组件实现这个接口, 然后将实现类注册进基础层的管理器, 宿主通过管理器在对应的生命周期方法中调用所有的接口实现类, 典型的观察者模式, 类似注册点击事件</p>

<p>在 <strong>MVPArms</strong> 中这种方案的实现类叫作 <strong>ConfigModule</strong>, 每个组件都可以声明一个或多个 <strong>ConfigModule</strong> 实现类, 内部实现较为复杂, 实现原理是 <strong>反射</strong> + <strong>代理</strong> + <strong>观察者</strong>, 这个类也是整个 <strong>MVPArms</strong> 框架提供给开发者最重要的类</p>

<p>它可以给 <strong>MVPArms</strong> 框架配置大量的自定义参数, 包括项目中所有生命周期的管理 (<strong>Application</strong>, <strong>Activity</strong>, <strong>Fragment</strong>), 项目中所有网络请求的管理 (<strong>Retrofit</strong>, <strong>Okhttp</strong>, <strong>Glide</strong>),为框架提供了极大的扩展性, 使框架更加灵活</p>

<p><a name="3"></a></p>
<h1 id="3-项目讲解">3 项目讲解</h1>

<p><img src="https://raw.githubusercontent.com/JessYanCoding/ArmsComponent/master/arts/ArmsComponent.gif" alt="ArmsComponent" /></p>

<blockquote>
  <p>项目地址 : <a href="https://github.com/JessYanCoding/ArmsComponent">ArmsComponent</a></p>
</blockquote>

<p><a name="3.1"></a></p>
<h2 id="31-如何让组件独立运行">3.1 如何让组件独立运行?</h2>
<p>在项目根目录的 <strong>gradle.properties</strong> 中, 改变 <strong>isBuildModule</strong> 的值即可</p>

<div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">#</span><span class="n">isBuildModule</span> <span class="err">为</span> <span class="kc">true</span> <span class="err">时可以使每个组件独立运行</span><span class="o">,</span> <span class="kc">false</span> <span class="err">则可以将所有组件集成到宿主</span> <span class="n">App</span> <span class="err">中</span>
<span class="n">isBuildModule</span><span class="o">=</span><span class="kc">true</span>
</code></pre></div></div>

<p><a name="3.2"></a></p>
<h2 id="32-配置-androidmanifest">3.2 配置 AndroidManifest</h2>
<p>由于组件在独立运行时和集成到宿主时可能需要 <strong>AndroidManifest</strong> 配置不一样的参数, 比如组件在独立运行时需要其中的一个 <strong>Activity</strong> 配置了 <code class="language-plaintext highlighter-rouge">&lt;action android:name="android.intent.action.MAIN"/&gt;</code> 作为入口, 而当组件集成到宿主中时, 则依赖于宿主的入口, 所以不需要配置 <code class="language-plaintext highlighter-rouge">&lt;action android:name="android.intent.action.MAIN"/&gt;</code>, 这时我们就需要两个不同的 <strong>AndroidManifest</strong> 应对不同的情况</p>

<p>在组件的 <strong>build.gradle</strong> 中加入以下代码, 即可指定不同的 <strong>AndroidManifest</strong>, 具体请看项目代码</p>

<div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">android</span> <span class="o">{</span>
	<span class="k">sourceSets</span> <span class="o">{</span>
        <span class="n">main</span> <span class="o">{</span>
            <span class="n">jniLibs</span><span class="o">.</span><span class="na">srcDirs</span> <span class="o">=</span> <span class="o">[</span><span class="s1">'libs'</span><span class="o">]</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">isBuildModule</span><span class="o">.</span><span class="na">toBoolean</span><span class="o">())</span> <span class="o">{</span>
                <span class="n">manifest</span><span class="o">.</span><span class="na">srcFile</span> <span class="s1">'src/main/debug/AndroidManifest.xml'</span>
            <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
                <span class="n">manifest</span><span class="o">.</span><span class="na">srcFile</span> <span class="s1">'src/main/release/AndroidManifest.xml'</span>
            <span class="o">}</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><a name="3.3"></a></p>
<h2 id="33-配置-configmoduleglobalconfiguration">3.3 配置 ConfigModule(GlobalConfiguration)</h2>
<p><strong>ConfigModule</strong> 在 <a href="#2.4.3">最终执行方案(2.4.3)</a> 中提到过, <strong>GlobalConfiguration</strong> 是实现类, 他可以给框架配置大量的自定义参数</p>

<p>项目 <strong>CommonSDK</strong> 中提供有一个 <strong>GlobalConfiguration</strong> 用于配置每个组件都会用到的公用配置信息, 但是每个组件可能都需要有一些私有配置, 比如初始化一些特有属性, 所以在每个组件中也需要实现 <strong>ConfigModule</strong>, 具体请看项目代码</p>

<p>需要注意的是, 在 <a href="#3.2">配置 AndroidManifest(3.2)</a> 中提到过组件在独立运行时和集成到宿主时所需要的配置是不一样的, 当组件在独立运行时需要在 <strong>AndroidManifest</strong> 中声明自己私有的 <strong>GlobalConfiguration</strong> 和 <strong>CommonSDK</strong> 公有的  <strong>GlobalConfiguration</strong>, 但在集成到宿主时, 由于宿主已经声明了 <strong>CommonSDK</strong> 的公有 <strong>GlobalConfiguration</strong>, 所以在 <strong>AndroidManifest</strong> 只需要声明自己私有的 <strong>GlobalConfiguration</strong>, 这里也说明了 <strong>AndroidManifest</strong> 在不同的情况需要做出不同的配置</p>

<p><a name="3.4"></a></p>
<h2 id="34-routerhub">3.4 RouterHub</h2>
<p><strong>RouterHub</strong> 用来定义路由器的路由地址, 以组件名作为前缀, 对每个组件的路由地址进行分组, 可以统一查看和管理所有分组的路由地址</p>

<p><strong>RouterHub</strong> 存在于基础库, 可以被看作是所有组件都需要遵守的通讯协议, 里面不仅可以放路由地址常量, 还可以放跨组件传递数据时命名的各种 <strong>Key</strong> 值, 再配以适当注释, 任何组件开发人员不需要事先沟通只要依赖了这个协议, 就知道了各自该怎样协同工作, 既提高了效率又降低了出错风险, 约定的东西自然要比口头上说强</p>

<blockquote>
  <p><strong>Tips: 如果您觉得把每个路由地址都写在基础库的 RouterHub 中, 太麻烦了, 也可以在每个组件内部建立一个私有 RouterHub, 将不需要跨组件的路由地址放入私有 RouterHub 中管理, 只将需要跨组件的路由地址放入基础库的公有 RouterHub 中管理, 如果您不需要集中管理所有路由地址的话, 这也是比较推荐的一种方式</strong></p>
</blockquote>

<p>路由地址的命名规则为 <strong>组件名 + 页面名</strong>, 如订单组件的订单详情页的路由地址可以命名为 <strong>“/order/OrderDetailActivity”</strong></p>

<p><strong>ARouter</strong> 将路由地址中第一个 <strong>’/’</strong> 后面的字符叫作 <strong>Group</strong>, 比如上面的示例路由地址中 <strong>order</strong> 就是 <strong>Group</strong>, 以 <strong>order</strong> 开头的地址都被分配该  <strong>Group</strong> 下</p>

<blockquote>
  <p><strong>Tips: 切记不同的组件中不能出现名称一样的 Group, 否则会发生该 Group 下的部分路由地址找不到的情况!!!</strong></p>
</blockquote>

<p>所以每个组件使用自己的组件名作为 <strong>Group</strong> 是比较好的选择, 毕竟组件不会重名</p>

<p><a name="3.5"></a></p>
<h2 id="35-eventbushub">3.5 EventBusHub</h2>
<p><strong>AndroidEventBus</strong> 作为本方案提供的另一种跨组件通信方式 (第一种跨组件通信方式是 <a href="#2.2.3.2">公共服务(<strong>2.2.3.2</strong>)</a>), <strong>AndroidEventBus</strong> 比 <strong>greenrobot</strong> 的 <strong>EventBus</strong> 多了一个 <strong>Tag</strong>, 在组件化中更容定位和管理事件</p>

<p><strong>EventBusHub</strong> 用来定义 <strong>AndroidEventBus</strong> 的 <strong>Tag</strong> 字符串, 以组件名作为 <strong>Tag</strong> 前缀, 对每个组件的事件进行分组</p>

<p><strong>Tag</strong> 的命名规则为 <strong>组件名</strong> + <strong>页面名</strong> + <strong>动作</strong>, 
比如需要使用 <strong>AndroidEventBus</strong> 通知订单组件的订单详情页进行刷新, 可以将这个刷新方法的 <strong>Tag</strong> 命名为 <strong>“order/OrderDetailActivity/refresh”</strong></p>

<blockquote>
  <p><strong>Tips: 基础库中的 EventBusHub 仅用来存放需要跨组件通信的事件的 Tag, 如果某个事件只想在组件内使用 AndroidEventBus 进行通信, 那就让组件自行管理这个事件的 Tag</strong></p>
</blockquote>

<p><a name="3.6"></a></p>
<h2 id="36-在项目中使用多个不同的域名">3.6 在项目中使用多个不同的域名</h2>
<p>在项目中, 有 <strong>知乎</strong> 、<strong>干货集中营</strong>、<strong>稀土掘金</strong> 三个模块, 这三个模块网络接口的域名都不一样, 但是在项目中却可以统一使用框架提供的同一个 <strong>Retrofit</strong> 进行网络请求, 这是怎么做到的呢? 这是采用本人的另一个库 <a href="https://github.com/JessYanCoding/RetrofitUrlManager">RetrofitUrlManager</a>, 它可以使 <strong>Retrofit</strong> 同时支持多个 <strong>BaseUrl</strong> 以及动态改变 <strong>BaseUrl</strong></p>]]></content><author><name></name></author><category term="Architecture" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" /><media:content medium="image" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">改造 Android 官方架构组件 ViewModel</title><link href="https://jessyan.me/modify-viewmodel/" rel="alternate" type="text/html" title="改造 Android 官方架构组件 ViewModel" /><published>2017-12-14T00:00:00+08:00</published><updated>2017-12-14T00:00:00+08:00</updated><id>https://jessyan.me/modify-viewmodel</id><content type="html" xml:base="https://jessyan.me/modify-viewmodel/"><![CDATA[<h1 id="前言">前言</h1>

<p>Android 官方架构组件在今年 5 月份 Google I/O 大会上被公布, 直到 11 月份一直都是测试版, 由于工作比较繁忙, 期间我只是看过类似的文章, 但没有在实际项目中使用过, 更没有看过源码, 所以对这几个组件的使用很是生疏, 同时也觉得这几个组件非常高大上, 非常神秘!</p>

<p>直到 11 月份 Android 官方架构组件正式版发布, 并且 Google 也在 <strong>Support Library v26.1.0</strong> 以后的版本中内嵌了 Android 官方架构组件中的生命周期组件, 我想, 这是趋势, 既然 Google 这么推崇, 那我也是时候学习一波并将它们引入 <a href="https://github.com/JessYanCoding/MVPArms"><strong>MVPArms</strong></a> 框架了</p>

<blockquote>
  <p>Github : <a href="https://github.com/JessYanCoding/LifecycleModel">你的 Star 是我坚持的动力 ✊</a></p>
</blockquote>

<h1 id="简单介绍">简单介绍</h1>

<p>因为想将 Android 官方架构组件引入 <a href="https://github.com/JessYanCoding/MVPArms"><strong>MVPArms</strong></a> 框架之中, 所以我认真学习了 Android 官方架构组件中除了 <strong>Room</strong> 之外的所有源码, 以考察是否整个组件都适合引入 <a href="https://github.com/JessYanCoding/MVPArms"><strong>MVPArms</strong></a> 框架</p>

<p>在学习完源码过后, 发现 Android 官方架构组件其实并没有想象的那么高深, 原理反而是我们在日常开发中都会用到的知识点, 那我就在文章的开头先简单的介绍下 Android 官方架构组件中的这几个组件</p>

<h2 id="lifecycles">Lifecycles</h2>

<p>生命周期组件是 Android 官方架构组件中的核心组件, 它可以使各种实例作为观察者与 <strong>Activity</strong> 和 <strong>Fragment</strong> 等具有生命周期特性的组件绑定在一起, <strong>LiveData</strong> 和 <strong>ViewModel</strong> 都是基于此组件, 简而言之就是, 你将需要绑定生命周期的实例注册给该组件, 该组件就会在你指定的某个生命周期方法执行时通知这个实例</p>

<p>应用场景很多, 比如之前在 <strong>MVP</strong> 架构中, 你需要在 <strong>Activity</strong> 执行 <strong>onCreate</strong> 时, 让 <strong>Presenter</strong> 初始化一些操作, 这时就不用在 <strong>Activity</strong> 的 <strong>onCreate</strong> 中再调用 <strong>Presenter</strong> 的某个初始化方法了, 直接使用官方的生命周期组件即可完成, 在 <strong>Activity</strong> 执行 <strong>onDestroy</strong> 时需要释放一些对象的资源, 也可以使用到生命周期组件</p>

<h2 id="livedata">LiveData</h2>

<p><strong>LiveData</strong> 具有两个功能, 第一个功能是观察者模式, 在 <strong>Value</strong> 发生变化时通知之前注册的所有观察者, 第二功能是基于生命周期组件与 <strong>Activity</strong> 和 <strong>Fragment</strong> 等具有生命周期特性的组件绑定在一起, 在生命周期发生改变时停止或恢复之前的事件</p>

<p>简而言之就是, 当某个页面请求网络数据成功后需要同步 <strong>UI</strong>, 但这个页面已经不可见, 这时就会停止同步 <strong>UI</strong> 的操作</p>

<h2 id="viewmodel">ViewModel</h2>

<p><strong>ViewModel</strong> 有两个功能, 第一个功能可以使 <strong>ViewModel</strong> 以及 <strong>ViewModel</strong> 中的数据在屏幕旋转或配置更改引起的 <strong>Activity</strong> 重建时存活下来, 重建后数据可继续使用, 第二个功能可以帮助开发者轻易实现 <strong>Fragment</strong> 与 <strong>Fragment</strong> 之间, <strong>Activity</strong> 与 <strong>Fragment</strong> 之间的通讯以及共享数据</p>

<h1 id="浅析官方架构组件">浅析官方架构组件</h1>

<p>用法就不多说了, 此类文章和 <strong>Demo</strong> 太多了, 明白了它们的功能和应用场景后, 我们才知道它们是否真的适合自己的需求, 而不是盲目跟风, 下面我就来分析下我是如何考察新技术, 以及如何判断这些新技术是否有必要应用到自己的项目中</p>

<h2 id="lifecycles-1">Lifecycles</h2>

<p>上面介绍了生命周期组件的功能, 这里就来分析一下生命周期组件是否有必要引入我的框架 <a href="https://github.com/JessYanCoding/MVPArms"><strong>MVPArms</strong></a></p>

<p>说到生命周期我就想到了我之前在 <a href="https://www.jianshu.com/p/ac51c9b88af3">传统MVP用在项目中是真的方便还是累赘?</a> 中讨论的一个内容</p>

<p>现在市面上流行的 <strong>MVP</strong> 架构有两种, <strong>第一种是将 Activity 或 Fragment 作为 View, 抽象一个 Presenter 层出来</strong>, <strong>第二种是将 Activity 或 Fragment 作为 Presenter, 抽象一个 View 层出来</strong></p>

<p>第一种类型代表的框架有 <a href="https://github.com/JessYanCoding/MVPArms"><strong>MVPArms</strong></a>, 第二种类型代表的框架有 <a href="https://github.com/kymjs/TheMVP"><strong>TheMVP</strong></a>, 当然第一种类型的 <strong>MVP</strong> 架构在市面上用的是最多的, 那么第二种类型的优点是什么呢?</p>

<p>我在上面这篇文章也说过, 主要优势有两个, <strong>方便重用View</strong>, 以及 <strong>可直接与 Activity 或 Fragment 的生命周期做绑定</strong>, 这样就可以直接使用 Activity 或 Fragment 的生命周期, 不用再去做多余的回调, 当然也有缺点, 我在文章中也有介绍, 有兴趣的可以去看看</p>

<p>第一种类型的 <strong>MVP</strong> 架构是不具有可以和 <strong>Activity</strong> 或 <strong>Fragment</strong> 的生命周期直接做绑定的优势的, 所以很是嫉妒第二种类型的 <strong>MVP</strong> 架构, 这也是两种类型的 <strong>MVP</strong> 架构最大的区别, 但你想的没错, 现在使用生命周期组件就可以使第一种类型的 <strong>MVP</strong> 架构很轻易的具有绑定生命周期的优势, 现在第一种类型的 <strong>MVP</strong> 架构将如虎添翼</p>

<p>经过以上的分析, 我认为生命周期组件对于我的框架来说是很有必要的, 这将使日常开发更加便捷</p>

<h2 id="livedata-1">LiveData</h2>

<p><strong>LiveData</strong> 与 <strong>RxJava</strong> 都是基于观察者模式, 功能上也有重合, Google 在官方文档上也明确表示, 如果你正在使用 <strong>RxJava</strong>, <strong>Agera</strong> 等类似功能的库, 只要你能正确的处理数据流的生命周期, 就完全可以继续使用它们来替代 <strong>LiveData</strong></p>

<blockquote>
  <p>Note: If you are already using a library like RxJava or Agera, you can continue using them instead of LiveData. But when you use them or other approaches, make sure you are handling the lifecycle properly such that your data streams pause when the related LifecycleOwner is stopped and the streams are destroyed when the LifecycleOwner is destroyed. You can also add the android.arch.lifecycle:reactivestreams artifact to use LiveData with another reactive streams library (for example, RxJava2).</p>
</blockquote>

<p>从官方文档可以看出 Google 对此的建议就是 <strong>RxJava</strong>, <strong>Agera</strong>, <strong>LiveData</strong> 等类似功能的库, 你只使用一个即可</p>

<h3 id="选择-rxjava-还是-livedata-">选择 RxJava 还是 LiveData ?</h3>

<p><strong>LiveData</strong> 和 <strong>RxJava</strong> 的功能的确过于重合, 我也十分赞同 Google 官方的建议, 两者之中选择其一就可以了, 没必要两者都引入项目, 而 <a href="https://github.com/JessYanCoding/MVPArms"><strong>MVPArms</strong></a> 框架, 也正好引入了 <strong>RxJava</strong>, 所以我也来分析分析在 <a href="https://github.com/JessYanCoding/MVPArms"><strong>MVPArms</strong></a> 框架中该选择 <strong>LiveData</strong> 还是 <strong>RxJava</strong>?</p>

<p>于是我认真的研究了其源码, <strong>LiveData</strong> 具有两个功能, 通知观察者更新数据和根据生命周期停止和恢复之前的事件, 而 <strong>Rxjava</strong> 加上 <strong>RxLifecycle</strong>, <strong>RxJava</strong> 加上 <strong>AutoDispose</strong>, 或 <strong>Rxjava</strong> 加上生命周期组件, 也可以轻易做到根据生命周期停止和恢复之前的事件, 在配上 <strong>Rxjava</strong> 强大的操作符, <strong>LiveData</strong> 能做的事 <strong>RxJava</strong> 都能做, <strong>LiveData</strong> 不能做的事 <strong>RxJava</strong> 也能做</p>

<p>并且 <strong>RxJava</strong> 不仅仅只是 <strong>RxJava</strong>, 他还是一个庞大的生态链, 他还有 <strong>RxCache</strong>, <strong>RxLifecycle</strong>, <strong>RxAndroid</strong>, <strong>RxPermission</strong>, <strong>Retrofit-Adapter</strong> 等大量并且强大的衍生库, 我们离开它做很多事都非常不便, 刚刚出生, 羽翼未丰的 <strong>LiveData</strong> 相比于 <strong>RxJava</strong> 将没有任何优势, 甚至显得非常简陋</p>

<p>因此 <strong>LiveData</strong> 和 <strong>RxJava</strong> 之间如果只能选择一个的话, 我没有任何理由选择 <strong>LiveData</strong></p>

<h2 id="viewmodel-1">ViewModel</h2>

<p><strong>ViewModel</strong> 中有一个功能让我十分惊艳, 也十分好奇, 它可以使 <strong>ViewModel</strong> 以及 <strong>ViewModel</strong> 中的数据在屏幕旋转或配置更改引起的 <strong>Activity</strong> 重建时存活下来, 重建后数据可继续使用, 这个功能十分实用且十分重要, 因为之前也没有一个官方解决方案, 所以我觉得很有必要将这个功能引入 <a href="https://github.com/JessYanCoding/MVPArms"><strong>MVPArms</strong></a> 框架</p>

<p>同样另外一个功能, 它还可以帮助开发者轻易实现 <strong>Fragment</strong> 与 <strong>Fragment</strong> 之间, <strong>Activity</strong> 与 <strong>Fragment</strong> 之间的通讯以及共享数据, 同样也正是我所需要的官方解决方案</p>

<p>但在我继续深入研究, 准备将它引入到项目中时, 却发现 Google 将这个功能做了高度封装并限制了它的使用范围, 只能用于 <strong>ViewModel</strong></p>

<p>但我想 Google 既然能让 <strong>MVVM</strong> 框架中的 <strong>ViewModel</strong> 具有这些功能, 那我为什么不能将这个功能扩展出来提供给 <strong>MVP</strong> 框架中的 <strong>Presenter</strong>, 乃至其他更多的模块?</p>

<p>于是我认真的研究了其源码, 准备通过修改源码并封装成库的方式, 让更多的开发者在更多的场景下能够使用到这些功能</p>

<h1 id="改造-viewmodel-组件">改造 ViewModel 组件</h1>

<p>要想改造 <strong>ViewModel 组件</strong> 自然要对它的整个源码分析一遍, 知道其原理, 才知道如何下手</p>

<h2 id="分析源码">分析源码</h2>

<p>篇幅有限, 就来简单的分析下源码把, 源码其实也就几个类, 经过了层层封装, 核心代码就在一个叫做 <strong>HolderFragment</strong> 的 <strong>Fragment</strong> 中,</p>

<p>在我看来 <strong>ViewModel 组件</strong> 的核心原理也就是 <strong>HolderFragment</strong> 中的一行代码实现的:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">setRetainInstance</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
</code></pre></div></div>
<p><strong>setRetainInstance(boolean)</strong> 是 <strong>Fragment</strong> 中的一个方法, 我想很多人应该都知道这个方法的意义</p>

<p>简单来说将这个方法设置为 true 就可以使当前 <strong>Fragment</strong> 在 <strong>Activity</strong> 重建时存活下来, 如果不设置或者设置为 false, 当前 <strong>Fragment</strong> 会在 <strong>Activity</strong> 重建时同样发生重建, 以至于被新建的对象所替代</p>

<p>意思是只要将这个方法设置为 true, <strong>Fragment</strong> 以及 <strong>Fragment</strong> 之中的所有数据都会在 <strong>Activity</strong> 重建时存活下来</p>

<p>这时我们在 <strong>setRetainInstance(boolean)</strong> 为 true 的 <strong>Fragment</strong> 中放一个专门用于存储 <strong>ViewModel</strong> 的 <strong>Map</strong>, 自然 <strong>Map</strong> 中所有的 <strong>ViewModel</strong> 都会幸免于 <strong>Activity</strong> 重建</p>

<p>于是我们让 <strong>Activity</strong>, <strong>Fragment</strong> 都绑定一个这样的 <strong>Fragment</strong>, 将 <strong>ViewModel</strong> 存放到这个 <strong>Fragment</strong> 的 <strong>Map</strong> 中, <strong>ViewModel 组件</strong> 就这样实现了</p>

<h2 id="如何改造">如何改造</h2>

<p>想要知道如何改造, 那我们就要明确这次改造的最终目的是什么, 我们的目的就是要让 <strong>ViewModel 组件</strong> 能用于 <strong>Presenter</strong>, 乃至其他更多的模块, 不止是用于 <strong>ViewModel</strong></p>

<p>那为什么 Google 官方的 <strong>ViewModel 组件</strong> 不能用于其他模块呢, 通过阅读源码可以知道, 是因为 <strong>Google</strong> 把上文提到的 <strong>Map</strong>, 封装了起来, 并没有提供出去, 并且限制了 <strong>ViewModel</strong> 的构建方式</p>

<p><strong>ViewModel 组件</strong> 让一个新的 <strong>ViewModel</strong> 必须继承于它的基类, 并且让开发者必须提供一个 <strong>Factory</strong> 指明当前 <strong>ViewModel</strong> 的构建方式, <strong>ViewModel 组件</strong> 会在合适的时机, 主动去根据 <strong>Factory</strong> 构建 <strong>ViewModel</strong> 实例, 并放入 <strong>Map</strong> 中</p>

<p>这时整个构建过程都被 <strong>ViewModel 组件</strong> 掌控并被限制于 <strong>ViewModel</strong>, 所以我需要做的就是将 <strong>Map</strong> 和 <strong>ViewModel</strong> 的构建方式扩展出来, 将更多的控制权交给外部的开发者</p>

<h2 id="实践">实践</h2>

<p>经过上面的分析, 思路和方案都有了, 接下来就剩下如何把思路和方案实现了</p>

<p>于是我结合上文分析的思路和方案对官方源码进行了改造并做了适当的优化, <strong>LifecycleModel</strong> 就这样诞生了</p>

<p>这篇文章主要还是讲在完成一个目标前, 在从 0 到 1 期间进行的思路和分析的过程, 至于细节你如果感兴趣的话还是去看我的源码把, 哈哈, 注释很详细哦!</p>

<blockquote>
  <p>Github : <a href="https://github.com/JessYanCoding/LifecycleModel">你的 Star 是我坚持的动力 ✊</a></p>
</blockquote>

<h1 id="总结">总结</h1>

<p>一个新技术是否真的适合自己还是需要自己去考察, 不应该盲目跟风, 如果你只知道这个技术很火然后去用它, 不知道为什么用它, 用它的好处, 那你就会一直陷入被动学习的窘境, 一直在学习, 但是总觉得自己跟不上时代的进步, 担惊受怕, 这是现代技术人大部分都存在的处境</p>

<p>至于最近闹的沸沸扬扬的简书 <strong>饱醉豚</strong> 事件, 自从简书 <strong>CEO</strong> 站台后, 已经不再是当事人一个人的事, 而是关乎到简书整个平台, 既然这个 <strong>CEO</strong> 这么傲气, 这个平台都不在乎我们这个群体, 我们也不再去关注这个平台就是了, 流量是跟着原创作者走还是跟着平台, 自己心里没点逼数吗?</p>

<p>简书以及简书 <strong>CEO</strong> 最好做出深刻的道歉, 否则我也会离开简书 (好像我更文频率也不是很高把? 咳咳… 我主打的是质量! 质量! 不是数量, 逃～)</p>

<h1 id="踩坑">踩坑</h1>

<p>在实际项目中使用 <strong>ViewModel 组件</strong> 时我也遇到了一些问题, 浪费了我很多时间, 所以有必要分享出来让大家少走弯路</p>

<h3 id="通过-activity-获取-viewmodel-时遇到的坑">通过 Activity 获取 ViewModel 时遇到的坑:</h3>
<ul>
  <li>
    <p>在 Application.ActivityLifecycleCallbacks 中的 onActivityCreated 方法中获取 ViewModel 时, Activity 每重建一次, 获取的 ViewModel 都是重新构建后的新实例, 并不能让 ViewModel 以及 ViewModel 中的数据幸免于 Activity 重建, 所以不要此方法中获取 ViewModel</p>
  </li>
  <li>
    <p>在 Activity 的 onDestroy 方法中不能获取 ViewModel, 会报错</p>
  </li>
</ul>

<h3 id="通过-fragment-获取-viewmodel-时遇到的坑">通过 Fragment 获取 ViewModel 时遇到的坑:</h3>
<ul>
  <li>
    <p>在 FragmentManager.FragmentLifecycleCallbacks 中的 onFragmentAttached 方法中获取 ViewModel 时也会出现和 Activity 一样的情况, 获取的 ViewModel 是重新构建后的新实例, ViewModel 以及 ViewModel 中的数据不能幸免于 Activity 重建, 所以也不要此方法中获取 ViewModel</p>
  </li>
  <li>
    <p>在 FragmentManager.FragmentLifecycleCallbacks 中的 onFragmentDestroyed 方法中也不能获取 ViewModel, 会报错</p>
  </li>
  <li>
    <p>在 Fragment 的 onDestroy 方法中不能获取 ViewModel, 会报错</p>
  </li>
</ul>

<hr />
<p><strong>Hello 我叫Jessyan,如果您喜欢我的文章,可以在以下平台关注我</strong></p>

<ul>
  <li>GitHub:  <a href="https://github.com/JessYanCoding">https://github.com/JessYanCoding</a></li>
  <li>掘金: <a href="https://gold.xitu.io/user/57a9dbd9165abd0061714613">https://gold.xitu.io/user/57a9dbd9165abd0061714613</a></li>
  <li>简书: <a href="https://www.jianshu.com/u/1d0c0bc634db">https://www.jianshu.com/u/1d0c0bc634db</a></li>
  <li>微博: <a href="https://weibo.com/u/1786262517">https://weibo.com/u/1786262517</a></li>
</ul>

<p>– The end</p>]]></content><author><name></name></author><category term="Activity" /><category term="Fragment" /><summary type="html"><![CDATA[前言]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" /><media:content medium="image" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">解决Retrofit多BaseUrl及运行时动态改变BaseUrl?</title><link href="https://jessyan.me/BaseUrl-Solution/" rel="alternate" type="text/html" title="解决Retrofit多BaseUrl及运行时动态改变BaseUrl?" /><published>2017-07-26T00:00:00+08:00</published><updated>2017-07-26T00:00:00+08:00</updated><id>https://jessyan.me/BaseUrl-Solution</id><content type="html" xml:base="https://jessyan.me/BaseUrl-Solution/"><![CDATA[<h1 id="前言">前言</h1>
<p>Hello,我是 <strong>JessYan</strong>,作为一个喜欢探索新颖解决方案的我,在 <a href="https://www.jianshu.com/p/5832c776621f">上篇文章</a> 中,向大家介绍了怎样通过一行代码即可实现上传下载以及 <strong>Glide</strong> 进度监听,现在又给大家带来了另一项大家都很期待的问题的解决方案,这个问题起源于 <a href="https://github.com/JessYanCoding/MVPArms">MVPArms</a> 的一个 <a href="https://github.com/JessYanCoding/MVPArms/issues/27">Issues</a> ,当然使用 <strong>Retrofit</strong> 时,多个 BaseUrl 以及动态切换 BaseUrl 这两个需求,在其他地方也经常被讨论,那么下面就来讲讲我的思路和解决方案</p>

<blockquote>
  <p>Github : <a href="https://github.com/JessYanCoding/RetrofitUrlManager">你的 Star 是我坚持的动力 ✊</a></p>
</blockquote>

<p><img src="https://raw.githubusercontent.com/JessYanCoding/RetrofitUrlManager/master/art/overview.gif" alt="gif" /></p>

<h1 id="需求出现的场景">需求出现的场景</h1>
<p>也许在日常开发中有些人已经遇到了这两个需求的场景,但为了让一些之前没遇到这些场景的朋友,也能看懂这篇文章,所以先在前面提一提</p>

<h2 id="多个-baseurl-的需求场景">多个 BaseUrl 的需求场景</h2>
<p>如果项目是聚合型 <strong>App</strong> ,比如像一些新闻资讯类客户端,可能数据源来自于多个平台,比如说知乎啊,豆瓣啊,今日头条啊,所以这样就会涉及到多个 <strong>BaseUrl</strong></p>

<p>如果项目使用到多个三方服务提供商,比如图片的读取使用到一个服务商,文件的存储又使用到另一个服务商,这个也会存在一个 <strong>App</strong> 出现多个 <strong>BaseUrl</strong></p>

<h2 id="动态改变-baseurl-的需求场景">动态改变 BaseUrl 的需求场景</h2>
<p>如果项目的 <strong>BaseUrl</strong> 会在 <strong>App</strong> 启动时,请求服务器,根据服务器的返回结果,来确定项目最终的 <strong>BaseUrl</strong>,就会涉及到运行时动态切换 <strong>BaseUrl</strong></p>

<p>如果项目的某个三方服务提供商,并不是固定的,也许会出现变更的情况,比如存储服务从七牛迁移至其他云存储,那我们为了避免更改代码导致重新打包以及发版,就会从服务器获取三方服务提供商的 <strong>BaseUrl</strong> ,然后在运行时动态改变这个 <strong>BaseUrl</strong></p>

<h1 id="解决方案">解决方案</h1>
<p>其实官方 <strong>Api</strong> 早已经提供了解决方案来支持多个 <strong>BaseUrl</strong> 以及运行时动态改变 BaseUrl ,民间也同样有很多解决方案</p>

<p><a name="1"></a></p>
<h2 id="官方静态解决方案">官方静态解决方案</h2>
<p>熟悉 <strong>Retrofit</strong> 的开发者应该知道 <strong>@Get</strong> , <strong>@Post</strong> 这些标注到每个接口方法上的注解不仅可以传相对路径,还可以传全路径,这样我们就可以做到不同的接口使用不同的 <strong>BaseUrl</strong> ,从而达到使用多个 <strong>BaseUrl</strong> 的需求,但是注解上的值只能是 <strong>Final</strong> 的常量,不能动态改变,所以我称这个解决方案为静态解决方案</p>

<p><a name="2"></a></p>
<h2 id="官方动态解决方案">官方动态解决方案</h2>
<p>熟悉 <strong>Retrofit</strong> 的开发者也同样知道 <strong>@Url</strong> 这个标注到每个接口方法参数上的注解,它可以将全路径作为参数传进接口作为每次请求的 <strong>Url</strong> 地址,每次请求接口都可以将不同的全路径作为参数,从而达到支持多个 <strong>BaseUrl</strong> 以及在运行时动态改变 <strong>BaseUrl</strong> ,所以很多请求图片等资源的接口都是使用这个方案(咦,看样子这个官方解决方案不是同时解决我提到的这两个问题吗,别急,先往后面看!)</p>

<p><a name="3"></a></p>
<h2 id="民间常用解决方案">民间常用解决方案</h2>
<p>之前也看过很多开源的聚合类 <strong>App</strong> 源码,像一些整合 <strong>知乎</strong> , <strong>豆瓣</strong> , <strong>Gank</strong> 等多个平台数据的 <strong>App</strong> ,因为各自平台的域名不同,所以大多数这类 <strong>App</strong> 会给每个平台都各自创建一个 <strong>Retrofit</strong> 对象,即不同的 <strong>BaseUrl</strong> 使用不同的 <strong>Retrofit</strong> 对象来创建 <strong>ApiService</strong> 进行请求,这样只要新增一个不同的 <strong>BaseUrl</strong> ,那就需要重新创建一个新的 <strong>Retrofit</strong> 对象</p>

<p>这样也可以同时实现,支持多个 <strong>BaseUrl</strong> 以及运行时动态改变 <strong>BaseUrl</strong> 这两个需求,但是以个人的观点,创建多个其他配置属性一模一样,只是 <strong>BaseUrl</strong> 不一样的 <strong>Retrofit</strong> 对象,太过于浪费资源</p>

<p><a name="4"></a></p>
<h2 id="民间大牛解决方案">民间大牛解决方案</h2>
<p>之前偶然看到了一个 <strong>Retrofit</strong> 维护者, <strong>Square</strong> 公司的大牛的 <a href="https://gist.github.com/swankjesse/8571a8207a5815cca1fb">解决方案</a>,用来解决运行时动态改变 <strong>BaseUrl</strong> ,其实也算半官方的解决方案</p>

<p>提到这个解决方案时,不得不讲一个趣事,其实之前 <strong>Retrofit</strong> 默认是支持运行时动态改变 <strong>BaseUrl</strong> 的,以前是有一个名为 <strong>BaseUrl</strong> 的接口,而 <strong>Retrofit.Builder#baseUrl(BaseUrl)</strong>  方法当时传的参数就是这个 <strong>BaseUrl</strong> ,而不是现在的 <strong>HttpUrl</strong> ,这个接口内部就有一个方法返回 <strong>HttpUrl</strong> ,那时候只要实现 <strong>BaseUrl</strong> 后,动态改变这个方法的返回值,就可以实现动态改变 <strong>BaseUrl</strong></p>

<p>但是这位大牛认为这样的做法不安全,所以提了一个 <a href="https://github.com/square/retrofit/pull/1652">Pull Requests</a> ,删掉了这个 <strong>BaseUrl</strong> 接口,并用上面的解决方案替代之,而亲爱的 <strong>JakeWharton</strong> 同意了他的观点,并合并了这个 <strong>PR</strong> 于是才有了现在的 <strong>Retrofit.Builder#baseUrl(HttpUrl)</strong> 这个不能动态改变 <strong>BaseUrl</strong> 的 <strong>Api</strong></p>

<p>用 <strong>Retrofit</strong> 比较早的老鸟,应该知道以前有一个这个 <strong>Api</strong>,我是说后来的版本怎么没了,原来毁在了这位兄台手上</p>

<p>这个方案也就是利用 <strong>Interceptor</strong> 拦截器,动态改变每个 <strong>Request</strong> 的 <strong>Url</strong> 从而实现动态改变 <strong>BaseUrl</strong>,但他这个解决方案不能支持多 <strong>BaseUrl</strong> ,只要 <strong>host</strong> 一设置,直到下一次改变 <strong>Host</strong> 之前,后面的所有 <strong>Request</strong> 都必须使用同一个 <strong>Host</strong> ,还有一些弊端后面一起分析</p>

<h1 id="几个方案的对比与分析">几个方案的对比与分析</h1>

<h2 id="淘汰含有明显缺陷的方案">淘汰含有明显缺陷的方案</h2>
<p>4个方案中,我首先淘汰的就是 <a href="#3"><strong>民间常用解决方案</strong></a> ,在前面已经明确了我的观点,因为我个人认为创建多个其他配置属性一模一样,只是 <strong>BaseUrl</strong> 不一样的 <strong>Retrofit</strong> 对象,太过于浪费资源,所以就算他能满足我的所有需求,除非真的没有更好的解决方案,否则我是不会选择它的</p>

<p>剩下的三个方案中, <a href="#1"><strong>官方静态解决方案</strong></a> 只能解决,2个需求中的支持多个 <strong>BaseUrl</strong> ,而对于动态改变 <strong>BaseUrl</strong> ,由于注解的 <strong>Value</strong> 只能为常量,所以对这个需求也是无能为力的(两个需求都满足,才表示可行)</p>

<h2 id="谁是最优方案">谁是最优方案?</h2>
<p>其实在前面已经说了 <a href="#2"><strong>官方动态解决方案</strong></a> 就已经可以同时实现多 <strong>BaseUrl</strong> 和运行时动态改变 <strong>BaseUrl</strong> ,那为什么我不直接选择这个方案,还要继续分析呢?</p>

<p>答案也很简单,我认为这个方案,虽然灵活,但是灵活却给它带来了使用上的繁琐,每个接口每次调用都必须传入全路径作为参数,不仅繁琐而且接口一多还不好管理</p>

<p>那 <a href="#4"><strong>民间大牛解决方案</strong></a> 可行? 但是我在前面已经说了这个不可行啊?</p>

<p>这个方案虽然可以支持运行时动态切换 <strong>BaseUrl</strong> 但是它是全局处理,一经使用改变的是所有请求的 <strong>Url</strong> ,所以它并不支持多 <strong>BaseUrl</strong></p>

<p>并且更可怕的是,这个方案不仅不支持多 <strong>BaseUrl</strong> ,还会影响 <a href="#1"><strong>官方静态解决方案</strong></a> 和 <a href="#2"><strong>官方动态解决方案</strong></a> 这两个支持多 <strong>BaseUrl</strong> 的方案,因为不管你注解里面声明的是什么全路径,它的 <strong>Interceptor</strong> 拦截器,都会强行将这个请求的 <strong>Url</strong> 改成它的 <strong>BaseUrl</strong> ,所以这个方案注定只适合只有一个 <strong>BaseUrl</strong> 但需要动态改变的项目</p>

<p>那岂不是 4 个解决方案都不可行?说这么久说个毛线啊?</p>

<h1 id="方案全部淘汰散会">方案全部淘汰?散会？</h1>
<p>等等别急啊,虽然我站在我的角度, <strong>Pass</strong> 了文中提到的所有已存在的解决方案</p>

<p>但是大家仔细想想,如果网上已经存在完美的解决方案,那我还写这篇文章有什么意义?必定是没有我满意的解决方案,我才会自己动手去解决并分享啊,毕竟我是一个不愿意写重复内容的有为青年,只要是我写的内容肯定是会让大家学到不一样的知识三 ✊,不然不是砸自己招牌</p>

<p>好了,不逗大家了,开整!</p>

<h2 id="别急还有大招">别急,还有大招!</h2>
<p>虽然在已有的解决方案当中没有找到让我满意的,但是在遇到问题时,冷静分析现有解决方案是很有必要的,理解前人的思路后才会对整个问题理解得更透彻,我的很多文章也都是以分析和解决思路为主,授人以鱼不如授人以渔,所以我不会直接告诉你答案,先分析一波,理清思路</p>

<p>这不,在分析 <a href="#4">民间大牛解决方案</a> 时,虽然最后发现这不是自己想要的解决方案,但是作为有发散思维的我,又是灵机一动,借助原有解决方案在上面这样一改不是就可行了?</p>

<h2 id="如何改善原有方案">如何改善原有方案？</h2>
<p>上面的分析已经说了 <a href="#4">民间大牛解决方案</a> ,可以在 <strong>Interceptor</strong> 拦截器中设置一个全局的 <strong>Host(Host 可以理解为 BaseUrl)</strong> ,拦截器会强行将这个 <strong>Host</strong> 应用到所有的请求上,改变该请求原有的 <strong>Url</strong>,这样导致了只会同时存在一个 <strong>Host</strong></p>

<p>所以我在想,将这个唯一的 <strong>Host</strong> 变量改为集合,以存储多个 <strong>Host</strong> ,在将不同的 <strong>Host</strong> 应用到不同的请求上,不就可以支持多 <strong>BaseUrl</strong>？</p>

<h2 id="实践想法">实践想法</h2>
<p>说干就干,于是我自己建了一个全局的容器来存储多个 <strong>Host</strong>,这样我就可以在 <strong>App</strong> 运行时的任何时间,任何地点随意新增,修改,删除 <strong>Host</strong></p>

<h2 id="遇到问题">遇到问题</h2>
<p>但是问题来了,我想要将不同的 <strong>Host</strong> 应用到不同的请求上,但我怎么知道什么请求需要什么样的 <strong>Host</strong> ,每个请求总要有个标记,让我知道他需要什么样的 <strong>Host</strong> 吧</p>

<p>于是我就在想 <strong>Retrofit</strong> 有什么方法,可以在请求之前给每个请求加上不同的字符串标记,于是我很自然的想到了 <strong>Header</strong> ,<strong>Retrofit</strong> 正好有 <strong>@Headers</strong> 这个注解,可以给每个接口方法上加入自定义 <strong>Header</strong></p>

<h2 id="再次解决难点">再次解决难点</h2>
<p>我给需要不同 <strong>BaseUrl</strong> 的接口方法上加入了自定义的 <strong>Header</strong> ,以标明每个接口需要的 <strong>Host</strong> 的 <strong>Name</strong> ,而这个 <strong>Name</strong> 对应的值就是 <strong>Host</strong>,但这个值不是在 <strong>@Headers</strong> 中被指定的,它是可以动态改变的</p>

<p>存储 <strong>Host</strong> 的容器是一个 <strong>Map</strong>, key 就是这个 <strong>Name</strong> ,value 才是 <strong>Host</strong> ,拦截器每次拦截到请求时,会判断这个请求是否有这个自定义 <strong>Header</strong>,
有的话,拿到这个 <strong>Header</strong> 中标注的 <strong>Name</strong>,然后用这个 <strong>Name</strong> ,去那个存储 <strong>Host</strong> 的全局 <strong>Map</strong> 中 <strong>get(name)</strong>,拿到对应的 <strong>Host</strong> 再应用到请求上不是就达到支持多个 <strong>BaseUrl</strong> 了?</p>

<p>如果想动态改变某个 <strong>Host</strong> 也简单,将新的 <strong>Host</strong> 以同样的 <strong>Name</strong> <strong>put(name)</strong>  进这个全局 <strong>Map</strong> ,到时候拦截器,使用这个 <strong>Name</strong> <strong>get(name)</strong> 出来的值,就已经是改变后最新的 <strong>Host</strong> ,在将这个 <strong>Host</strong> 应用到请求上不是就达到动态改变 <strong>BaseUrl</strong> 了？</p>

<p>这不,两个需求同时满足!</p>

<h1 id="优化方案">优化方案</h1>
<p>这个方案就两步,给需要不同 <strong>BaseUrl</strong> 的请求设置 <strong>Header</strong> (想用 <strong>Retrofit</strong> 默认 <strong>BaseUrl</strong> 的接口,或者使用 <a href="#1"><strong>官方静态解决方案</strong></a>, <a href="#2"><strong>官方动态解决方案</strong></a> 就不需要设置),在通过全局容器来管理 <strong>BaseUrl</strong></p>

<p>针对于那种只有一个 <strong>BaseUrl</strong>  但需要动态改变的项目,本框架提供了一个 <strong>GlobalDomain</strong> 来优化这个场景,不需要给接口加 <strong>Header</strong> ,只需要一步,向全局容器 <strong>put(GlobalDomain)</strong> 你想要改变的 <strong>BaseUrl</strong> 就可以了</p>

<p>比 <a href="#2"><strong>官方动态解决方案</strong></a> 给每个接口传全路径作为参数,要简单的多, <a href="#2"><strong>官方动态解决方案</strong></a> 注定只适合那种只有一两个需要动态改变 <strong>BaseUrl</strong> 的接口</p>

<h1 id="总结">总结</h1>
<p>以上提到的解决方案,已经优化并封装成了三方库并上传至 <strong>Jcenter</strong>,方便大家使用</p>

<p>本解决方案主要适合,需要同时具备多 <strong>BaseUrl</strong> 以及动态改变 <strong>BaseUrl</strong> 的项目,或者只有一个 <strong>BaseUrl</strong> ,但需要动态改变 <strong>BaseUrl</strong> 的项目</p>

<p>如果对于只需要多 <strong>BaseUrl</strong> 不需要动态改变 <strong>BaseUrl</strong> 的项目,其实用 <a href="#1"><strong>官方静态解决方案</strong></a> 就已经足够了,但我还是推荐用我的这个解决方案,因为需求都是会变的,如果一旦要加入动态改变 <strong>BaseUrl</strong> 的需求,如需要动态切换 生产环境 和 开发环境 ,那这时怎么办,一个个改掉每个接口注解里面的全路径?</p>

<blockquote>
  <p>Github : <a href="https://github.com/JessYanCoding/RetrofitUrlManager">具体使用看 Demo ,记得 Star !</a></p>
</blockquote>

<hr />
<p><strong>Hello 我叫Jessyan,如果您喜欢我的文章,可以在以下平台关注我</strong></p>

<ul>
  <li>GitHub:  <a href="https://github.com/JessYanCoding">https://github.com/JessYanCoding</a></li>
  <li>掘金: <a href="https://gold.xitu.io/user/57a9dbd9165abd0061714613">https://gold.xitu.io/user/57a9dbd9165abd0061714613</a></li>
  <li>简书: <a href="https://www.jianshu.com/u/1d0c0bc634db">https://www.jianshu.com/u/1d0c0bc634db</a></li>
  <li>微博: <a href="https://weibo.com/u/1786262517">https://weibo.com/u/1786262517</a></li>
</ul>

<p>– The end</p>]]></content><author><name></name></author><category term="Retrofit" /><summary type="html"><![CDATA[前言 Hello,我是 JessYan,作为一个喜欢探索新颖解决方案的我,在 上篇文章 中,向大家介绍了怎样通过一行代码即可实现上传下载以及 Glide 进度监听,现在又给大家带来了另一项大家都很期待的问题的解决方案,这个问题起源于 MVPArms 的一个 Issues ,当然使用 Retrofit 时,多个 BaseUrl 以及动态切换 BaseUrl 这两个需求,在其他地方也经常被讨论,那么下面就来讲讲我的思路和解决方案]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" /><media:content medium="image" url="https://jessyan.me/%7B%22feature%22=%3E%22abstract-1.jpg%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>