骚年你的屏幕适配方式该升级了!-今日头条适配方案

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

前言

这个月在 Android 技术圈中 屏幕适配 这个词曝光率挺高的,为什么这么说呢?因为这个月陆续有多个大佬发布了屏幕适配相关的文章,公布了自己认可的屏幕适配方案

上上个星期 Blankj 老师发表了一篇力挺今日头条屏幕适配方案的 文章,提出了很多优化的方案,并开源了相关源码

上个星期 拉丁吴 老师在 鸿神 的公众号上发布了一篇 文章,详细描述了市面上主流的几种屏幕适配方案,并发布了他的 smallestWidth 限定符适配方案和相关源码 (其实早就发布了),文章写的很好,建议大家去看看

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

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

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

Github : 您的 Star 是我坚持的动力 ✊

浅谈适配方案

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

第一种就是宽高限定符适配,什么是宽高限定符适配呢

├── src/main
│   ├── res
│   ├── ├──values
│   ├── ├──values-800x480
│   ├── ├──values-860x540
│   ├── ├──values-1024x600
│   ├── ├──values-1024x768
│   ├── ├──...
│   ├── ├──values-2560x1440

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

第二种就是 鸿神AndroidAutoLayout

这两种方案都已经逐渐退出了历史的舞台,为什么想必大家都知道,不知道的建议看看 拉丁吴 老师的文章,所以这两种方案我在文章中就不在阐述了,主要讲讲现在最主流的两种屏幕适配方案,今日头条适配方案smallestWidth 限定符适配方案

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

今日头条屏幕适配方案

原理

上面已经告知,不了解这两个方案的先看看上面的两篇文章,所以这里我就假设大家已经看了上面的文章或者之前就了解过这两个方案,所以在本文中我就不再阐述 DPIDensity 以及一些比较基础的知识点,上面的文章已经阐述的够清楚了

今日头条屏幕适配方案的核心原理在于,根据以下公式算出 density

当前设备屏幕总宽度(单位为像素)/ 设计图总宽度(单位为 dp) = density

density 的意思就是 1 dp 占当前设备多少像素

为什么要算出 density,这和屏幕适配有什么关系呢?

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;
    }

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

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

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

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

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

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

  • 设备 1,屏幕宽度为 1080px480DPI,屏幕总 dp 宽度为 1080 / (480 / 160) = 360dp

  • 设备 2,屏幕宽度为 1440560DPI,屏幕总 dp 宽度为 1440 / (560 / 160) = 411dp

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

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

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

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

这时我们该怎么做呢?改变每个 Viewdp 值?不现实,在每个设备上都要通过代码动态计算 Viewdp 值,工作量太大

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

屏幕的总 px 宽度 / density = 屏幕的总 dp 宽度

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

当前设备屏幕总宽度(单位为像素)/ 设计图总宽度(单位为 dp) = density

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

验证方案可行性

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

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

验证设备 1

屏幕总宽度为 1080 px,根据今日头条的的公式求出 density1080 / 375 = 2.88 (density)

这个 50dp * 50dpView,系统最后会将高宽都换算成 px50dp * 2.88 = 144 px (根据公式 dp * density = px)

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

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

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

验证设备 2

屏幕总宽度为 1440 px,根据今日头条的的公式求出 density1440 / 375 = 3.84 (density)

这个 50dp * 50dpView,系统最后会将高宽都换算成 px50dp * 3.84 = 192 px (根据公式 dp * density = px)

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

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

优点

  1. 使用成本非常低,操作非常简单,使用该方案后在页面布局时不需要额外的代码和操作,这点可以说完虐其他屏幕适配方案

  2. 侵入性非常低,该方案和项目完全解耦,在项目布局时不会依赖哪怕一行该方案的代码,而且使用的还是 Android 官方的 API,意味着当你遇到什么问题无法解决,想切换为其他屏幕适配方案时,基本不需要更改之前的代码,整个切换过程几乎在瞬间完成,会少很多麻烦,节约很多时间,试错成本接近于 0

  3. 可适配三方库的控件和系统的控件(不止是是 ActivityFragmentDialogToast 等所有系统控件都可以适配),由于修改的 density 在整个项目中是全局的,所以只要一次修改,项目中的所有地方都会受益

  4. 不会有任何性能的损耗

缺点

暂时没发现其他什么很明显的缺点,已知的缺点有一个,那就是第三个优点,它既是这个方案的优点也同样是缺点,但是就这一个缺点也是非常致命的

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

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

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

举个栗子

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

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

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

解决方案

方案 1

调整设计图尺寸,因为三方库可能是远程依赖的,无法修改源码,也就无法让三方库来适应我们项目的设计图尺寸,所以只有我们自身作出修改,去适应三方库的设计图尺寸,我们将项目自身的设计图尺寸修改为这个三方库的设计图尺寸,就能完成项目自身和三方库的适配

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

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

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

方案 2

第二个方案是最简单的,也是按 Activity 为单位,取消当前 Activity 的适配效果,改用其他的适配方案

使用中的问题

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

但是我建议大家千万不要这样做,还是老老实实的以 dp 为单位填写 dp 值,为什么呢?

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

第一个坑

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

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

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

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

举个栗子

使用这个方案时,在屏幕宽度为 1080px 的设备上,将设计图宽度直接填写为 1080,根据今日头条公式

当前设备屏幕总宽度 / 设计图总宽度 = density

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

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

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

这时再来看看你之前在 layout 文件中填写的 dp,换算成 px900 px (300dp * 3 = 900px 根据公式 dp * density = px)

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

第二个坑

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

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

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

总结

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

所以我把这次的屏幕适配文章归位一个系列,一共分为三篇,第一篇详细的讲 今日头条屏幕适配方案,第二篇详细的讲 smallestWidth 限定符适配方案,第三篇详细讲两个方案的深入对比以及如何选择,并发布我根据 今日头条屏幕适配方案 优化的屏幕适配框架 AndroidAutoSize

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

<manifest>
    <application>            
        <meta-data
            android:name="design_width_in_dp"
            android:value="360"/>
        <meta-data
            android:name="design_height_in_dp"
            android:value="640"/>           
     </application>           
</manifest>

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

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


关于大家的评论以及关注的问题,我在这里统一回复一下:

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

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

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

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

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


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


Hello 我叫 JessYan,如果您喜欢我的文章,可以在以下平台关注我

– The end

解决Retrofit多BaseUrl及运行时动态改变BaseUrl(二)

前言

我在之前的文章 《解决Retrofit多BaseUrl及运行时动态改变BaseUrl》 中,介绍了市面上能够解决此类问题的 4 个常见的解决方案,并开源了自己经过优化后的解决方案 RetrofitUrlManager,现在再为大家带来此系列的第二篇文章,这篇文章主要介绍 RetrofitUrlManager 针对 BaseUrl 替换逻辑的重大升级,因为这个升级对于 RetrofitUrlManager 足够重要,将使 RetrofitUrlManager 能够从容应对更多复杂的需求,所以单独写一篇文章让更多的人能够知道

Github : 您的 Star 是我坚持的动力 ✊

为什么不使用多 Retrofit 实例的方案?

在上篇文章 《解决Retrofit多BaseUrl及运行时动态改变BaseUrl》 中,4 种方案的特点和不足我都描述的很清楚,建议没看过这篇文章的可以去看看这篇文章,扩宽知识面,在后面的时间里经常有人问我为什么不使用多 Retrofit 实例的方案,多个 Retrofit 实例看起来并不会占用多少资源啊?

在回答之前为了让看这篇文章的人能了解我在说什么,所以我再粘贴下 上篇文章 中关于这个方案的部分描述

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

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

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

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

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

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

升级之前的 RetrofitUrlManager 的问题

此次升级之前的 RetrofitUrlManager 版本,只是将 上篇文章 的思想完全实现,有了整个框架的基础,但是在动态替换 BaseUrl 方面还不够强大,最被大家吐槽的就是只能替换 BaseUrl 的域名

比如一个需要替换 BaseUrlUrl 地址为 "https:www.google.com/api/v2",其中 "https:www.google.com/api" 是我们传给 RetrofitBaseUrl,这时我们使用 RetrofitUrlManager 框架,想把 BaseUrl 替换成 "https:www.github.com",我们期望的替换后的 URL 地址是 "https:www.github.com/v2",但使用框架替换后的实际 URL 地址是 "https:www.github.com/api/v2", "/api" 作为 BaseUrl 的一部分并没有被新的 BaseUrl 替换掉,只是替换了 BaseUrl 中的域名

RetrofitUrlManager 是如何改善的

改善之前先要先分析为什么会这样?因为 RetrofitUrlManager 框架在拦截器中拦截到的 URL 地址是 Retrofit 已经把 BaseUrl 和接口注解中的相对路径合并后得到的最终路径地址,所以框架并不知道您传给 RetrofitBaseUrl 除了域名外还包含后面的 "/api",框架不知道 BaseUrl 的具体值,所以框架只会默认所有的 BaseUrl 都只含有域名,所以也就只能替换域名

高级模式

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

什么是 pathSegment?

"https://www.github.com/wiki/part?name=jess" 中,其中的 "/wiki""/part" 就是 pathSegment, PathSize 就是 pathSegment 的个数

这个 URL 地址的 PathSize 就是 2, 第一个 pathSegment"/wiki",第二个 pathSegment"/part",可以粗略的理解为域名后面跟了几个 "/" PathSize 就是几

高级模式的替换规则

  1. URL 地址为 "https://www.github.com/wiki/part",您在 App 初始化时传入 RetrofitUrlManager#startAdvancedModel(String)BaseUrl"https://www.github.com/wiki",您想替换成的 BaseUrl 地址是 "https://www.google.com/api",经过框架替换后生成的最终 URL 地址为 "http://www.google.com/api/part"

  2. URL 地址为 "https://www.github.com/wiki/part",您在 App 初始化时传入 RetrofitUrlManager#startAdvancedModel(String)BaseUrl"https://www.github.com/wiki",您想替换成的 BaseUrl 地址是 "https://www.google.com",经过框架替换后生成的最终 URL 地址为 "http://www.google.com/part"

  3. URL 地址为 "https://www.github.com/wiki/part", 您在 App 初始化时传入 RetrofitUrlManager#startAdvancedModel(String)BaseUrl"https://www.github.com",您想替换成的 BaseUrl 地址是 "https://www.google.com/api",经过框架替换后生成的最终 URL 地址为 "http://www.google.com/api/wiki/part"

超级模式

超级模式是高级模式的加强版,优先级高于高级模式,按理说高级模式就能满足开发中的大部分需求,那什么又是超级模式呢?那就要先来说说高级模式了

高级模式的原理

在高级模式中您需要在 App 初始化时将您传给 RetrofitBaseUrl 同样传给 RetrofitUrlManager#startAdvancedModel(String) 一份,用以开启高级模式,成功开启高级模式后,这个传给 RetrofitUrlManager#startAdvancedModel(String)BaseUrl 就会作为框架替换 BaseUrl 的基准

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

高级模式的局限

如果传给 RetrofitUrlManager#startAdvancedModel(String)BaseUrl"https://www.github.com/wiki/part" (PathSize = 2),框架就会将项目中所有 URL 中的 域名 以及 域名 后面的前两个 pathSegments 作为可被替换的 BaseUrl 整体,意味着框架只会将 URL 中的 域名 以及前两个 pathSegments 剪切并替换为您期望的 BaseUrl

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

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

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

超级模式的用法

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

超级模式的替换规则

  1. URL 地址为 "https://www.github.com/wiki/part#baseurl_path_size=1""#baseurl_path_size=1" 表示其中 BaseUrl"https://www.github.com/wiki",您想替换成的 BaseUrl 地址是 "https://www.google.com/api",经过框架替换后生成的最终 URL 地址为 "http://www.google.com/api/part"

  2. URL 地址为 "https://www.github.com/wiki/part#baseurl_path_size=1""#baseurl_path_size=1" 表示其中 BaseUrl"https://www.github.com/wiki",您想替换成的 BaseUrl 地址是 "https://www.google.com",经过框架替换后生成的最终 URL 地址为 "http://www.google.com/part"

  3. URL 地址为 "https://www.github.com/wiki/part#baseurl_path_size=0""#baseurl_path_size=0" 表示其中 BaseUrl"https://www.github.com",您想替换成的 BaseUrl 地址是 "https://www.google.com/api",经过框架替换后生成的最终 URL 地址为 "http://www.google.com/api/wiki/part"

  4. URL 地址为 "https://www.github.com/wiki/part/issues/1#baseurl_path_size=3""#baseurl_path_size=3" 表示其中 BaseUrl"https://www.github.com/wiki/part/issues",您想替换成的 BaseUrl 地址是 "https://www.google.com/api",经过框架替换后生成的最终 URL 地址为 "http://www.google.com/api/1"

三种模式比较

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

替换 BaseUrl 的自由程度 (可扩展性)

普通模式 < 高级模式 < 超级模式

  • 普通模式: 只能替换域名

  • 高级模式: 只能替换在 RetrofitUrlManager#startAdvancedModel(String) 中传入的固定 BaseUrl 格式

  • 超级模式: 每个 URL 都可以随意指定可被替换的 BaseUrl 格式

使用上的复杂程度

普通模式 < 高级模式 < 超级模式

  • 普通模式: 无需做过多配置

  • 高级模式: 在 App 初始化时调用一次 RetrofitUrlManager#startAdvancedModel(String) 即可

  • 超级模式: 每个需要开启超级模式的 URL 尾部都需要加入 RetrofitUrlManager#IDENTIFICATION_PATH_SIZE (#baseurl_path_size=) + PathSize

总结

由此可见,自由度越强,使用上也越复杂,所以可以根据自己的需求选择对应的模式,并且也可以根据需求的变化随意升级或降级这三种模式

这次更新让 RetrofitUrlManager 的能力提升了一个档次,足以应对各种复杂的 BaseUrl 替换需求,正因为 RetrofitUrlManager 极强的扩展性,现在甚至可以做到,让服务器可以通过远程动态控制项目中的多个 BaseUrl

如果还有什么问题或者需求可以给我提 Issues,如果 RetrofitUrlManager 能够给您带来实质的帮助,也请不要吝啬您的 Star


Hello 我叫Jessyan,如果您喜欢我的文章,可以在以下平台关注我

– The end