关于android:听说你还在用dp做屏幕适配

69次阅读

共计 11883 个字符,预计需要花费 30 分钟才能阅读完成。

大家在 Android 开发时,必定会感觉屏幕适配是个尤其苦楚的事,各种屏幕尺寸适配起来巨烦无比。如果咱们换个角度咱们看下这个问题,不晓得大家有没有理解过 web 前端开发,或者说大家对于网页都不生疏吧,其实适配的问题在 web 页面的设计中实践上也存在,为什么这么说呢?电脑的显示器的分辨率、包含手机分辨率,我敢说分辨率的品种远超过 Android 设施的分辨率,那么有一个很奇怪的景象:

为什么 Web 页面设计人员素来没有说过,屏幕适配好麻烦?

那么,到底是什么起因,让网页的设计能够在千差万别的分辨率的分辨率中仍旧能给用户一个优质的体验呢?带着这个纳闷,我问了下一个前端敌人,敌人睁大眼睛问我:适配是什么??前端仿佛看来确实没有这类问题,起初在我认真的诘问后,她通知我,噢 这个尺寸呀,咱们个别都加个 viewport,我都是设置为 20% 缩放的~~ 追根到底,其实就是一个起因,网页提供了缩放比计算大小。

同样的,大家拿到 UI 给的设计图当前,是不是埋怨过 UI 妹妹标识的都是 px,而我我的项目外面用 dp,这都什么玩意😂,和 UI 解释她也不了解,开发同样也是一脸无奈。所以能不能有一套完满的解决方案来解决 Android 工程师和 UI 妹妹间的矛盾,实现 UI 给出一个固定尺寸的设计稿,而后你在编写布局的时候不必思考,无脑照抄下面标识的像素值,就能达到完满适配。现实够饱满,但事实够残暴:

因为 Android 零碎的开放性,任何用户、开发者、OEM 厂商、运营商都能够对 Android 进行定制,于是导致:

  • Android 零碎碎片化:小米定制的 MIUI、魅族定制的 flyme、华为定制的 EMUI 等等, 当然其都是基于 Google 原生零碎定制的
  • Android 机型屏幕尺寸碎片化:5 寸、5.5 寸、6 寸等等
  • Android 屏幕分辨率碎片化:320×480、480×800、720×1280、1080×1920

    据友盟指数显示,统计至 2015 年 12 月,反对 Android 的设施共有 27796 种

当 Android 零碎、屏幕尺寸、屏幕密度呈现碎片化的时候,就很容易呈现同一元素在不同手机上显示不同的问题。

试想一下这么一个场景:

为 4.3 寸屏幕筹备的 UI 设计图,运行在 5.0 寸的屏幕上,很可能在右侧和下侧存在大量的空白;而 5.0 寸的 UI 设计图运行到 4.3 寸的设施上,很可能显示不下。

屏幕品种这么多,那么就须要一套完满的计划去解决适配问题,介绍屏幕适配计划之前,先简略介绍下 Android 屏幕中用到的一些相干重要概念::**

屏幕尺寸

· 含意:手机对角线的物理尺寸

· 单位:英寸(inch),1 英寸 =2.54cm

Android 手机常见的尺寸有 5 寸、5.5 寸、6 寸等等

屏幕分辨率

· 含意:手机在横向、纵向上的像素点数总和

  1. 个别形容成屏幕的 ” 宽 x 高”=AxB
  2. 含意:屏幕在横向方向(宽度)上有 A 个像素点,在纵向方向(高)有 B 个像素点

    例子:1080×1920,即宽度方向上有 1080 个像素点,在高度方向上有 1920 个像素点

  • 单位:px(pixel),1px= 1 个像素点

UI 设计师的设计图会以 px 作为对立的计量单位

  • Android 手机常见的分辨率:320×480、480×800、720×1280、1080×1920、1080×2340

屏幕像素密度

  • 含意:每英寸的像素点数
  • 单位:dpi(dots per ich)

假如设施内每英寸有 160 个像素,那么该设施的屏幕像素密度 =160dpi

  • 安卓手机对于每类手机屏幕大小都有一个相应的屏幕像素密度:
密度类型 代表的分辨率(px) 屏幕密度(dpi)
低密度(ldpi) 240×320 120
中密度(mdpi) 320×480 160
高密度(hdpi) 480×800 240
超高密度(xhdpi) 720×1280 320
超超高密度(xxhdpi) 1080×1920 480

屏幕尺寸、分辨率、像素密度三者关系

一部手机的分辨率是宽 * 高,屏幕大小是以寸为单位,那么三者的关系是:

不懂没关系,在这里举个例子

假如一部手机的分辨率是 1080×1920(px),屏幕大小是 5 寸,问密度是多少?

:请间接套公式

密度无关像素

  • 含意:density-independent pixel,叫 dp 或 dip,与终端上的理论物理像素点无关。
  • 单位:dp,能够保障在不同屏幕像素密度的设施上显示雷同的成果

    1. Android 开发时用 dp 而不是 px 单位设置图片大小,是 Android 特有的单位
    2. 场景:如果同样都是画一条长度是屏幕一半的线,如果应用 px 作为计量单位,那么在 480×800 分辨率手机上设置应为 240px;在 320×480 的手机上应设置为 160px,二者设置就不同了;如果应用 dp 为单位,在这两种分辨率下,160dp 都显示为屏幕一半的长度。
  • dp 与 px 的转换
    因为 ui 设计师给你的设计图是以 px 为单位的,Android 开发则是应用 dp 作为单位的,那么咱们须要进行转换:
密度类型 代表的分辨率(px) 屏幕密度(dpi) 换算(px/dp)
低密度(ldpi) 240×320 120 1dp=0.75px
中密度(mdpi) 320×480 160 1dp=1px
高密度(hdpi) 480×800 240 1dp=1.5px
超高密度(xhdpi) 720×1280 320 1dp=2px
超超高密度(xxhdpi) 1080×1920 480 1dp=3px

在 Android 中,规定以 160dpi(即屏幕分辨率为 320×480)为基准:1dp=1px

独立比例像素

  • 含意:scale-independent pixel,叫 sp 或 sip
  • 单位:sp

Android 开发时用此单位设置文字大小,可依据字体大小首选项进行缩放。

举荐应用 12sp、14sp、18sp、22sp 作为字体设置的大小,不举荐应用奇数和小数,容易造成精度的失落问题;小于 12sp 的字体会太小导致用户看不清

请把下面的概念记住,因为上面解说都会用到!

适配计划比拟

1. dp 原生计划

2. dimen 基于 px 和 dp 的适配(宽高限定符和 smallestWidth 适配)

3. 头条屏幕适配计划

4. 头条适配计划改良版本

dp 原生计划

前言:对立以 px 为单位有什么问题?

Android 屏幕适配由来已久,关键在于屏幕尺寸与屏幕分辨率的变动微小,而很多 UI 工程师为了兼容 iOS 和 Android 的适配,这样导致设计进去的 UI 稿是以 px 单位标注的。在成千上百种机型背后,px 单位已难以适应。

1. 同样尺寸,不同分辨率:

1080px 的宽度上显示 100px 比例是 100/1080

720px 的宽度上显示 100px 比例是 100/720

2. 同分辨率,不同尺寸:

1080px 在 4.7 寸上显示 100px

1080px 在 6.1 寸上显示 100px

如果应用多套 px 文件计划来适配,市面上少说上百种寸,须要的文件太多了

不同分辨率的屏幕该如何适配

这时候就须要用到 dp 计划来解决了, 所以dp 到底解决了什么问题?

以下公式示意了,同样尺寸上不同分辨率(不同 density)的设施,每 1dp 所代表的像素数量是不一样的。

480 dpi 上 1dp = 1 * 3 = 3px

320 dpi 上 1dp = 1 * 2 = 2px

240 dpi 上 1dp = 1 * 1.5 = 1.5px

160 dpi 上 1dp = 1 * 1 = 1px

120 dpi 上 1dp = 1 * 0.75 = 0.75px

然而所示意的物理长度(160dp=1in)是一样的。

160 dp 在 density= 3 上示意 480px,物理长度为 1 in

160 dp 在 density= 2 上示意 320px,物理长度为 1 in

160 dp 在 density=1.5 上示意 240px,物理长度为 1 in

160 dp 在 density= 1 上示意 160px,物理长度为 1 in

160 dp 在 density=0.75 上示意 120px,物理长度为 1 in

由上可知,dp 单位的应用就意味着你在这些同样尺寸然而不同分辨率的设施上看到的大小一样,此时各设施上显示的比例也就统一了。

dp 计划没有解决什么问题

举个例子:

屏幕分辨率为:1920*1080,屏幕尺寸为 5 吋的话,那么 dpi 为 440。假如咱们 UI 设计图是按屏幕宽度为 360dp 来设计的,那这样会存在什么问题呢?

在上述设施上,屏幕宽度其实为 1080/(440/160)=392.7dp,也就是屏幕是比设计图要宽的。这种状况下,即便应用 dp 也是无奈在不同设施上显示为同样成果的。同时还存在局部设施屏幕宽度有余 360dp,这时就会导致按 360dp 宽度来开发理论显示不全的状况。

而且上述屏幕尺寸、分辨率和像素密度的关系,很多设施并没有按此规定来实现,因而 dpi 的值十分乱,没有法则可循,从而导致应用 dp 适配成果差强人意。

dimen 基于 px 和 dp 的适配(宽高限定符和 smallestWidth 适配)

dimen 基于 dp 适配 SmallestWidth 限定符

原理:

这种适配根据的是最小宽度限定符。指的是 Android 会辨认屏幕可用高度和宽度的最小尺寸的 dp 值(其实就是手机的宽度值),而后依据辨认到的后果去资源文件中寻找对应限定符的文件夹下的资源文件。这种机制和上文提到的宽高限定符适配原理上是一样的,都是零碎通过特定的规定来抉择对应的文件。

举个例子,小米 5 的 dpi 是 480, 横向像素是 1080px,依据 px=dp(dpi/160),横向的 dp 值是 1080/(480/160), 也就是 360dp, 零碎就会去寻找是否存在 value-sw360dp 的文件夹以及对应的资源文件。

smallestWidth 限定符适配和宽高限定符适配最大的区别在于,有很好的容错机制,如果没有 value-sw360dp 文件夹,零碎会向下寻找,比方离 360dp 最近的只有 value-sw350dp,那么 Android 就会抉择 value-sw350dp 文件夹上面的资源文件。这个个性就完满的解决了上文提到的宽高限定符的容错问题。

毛病:

  • 侵入性强
  • Android 私人订制的起因,宽度方面参差不齐,不可能适配所有的手机。
  • 我的项目中减少了 N 个文件夹,上拉下拉查看文件十分不不便:想看 string 或者 color 资源文件须要拉很多再能达到。
  • 通过宽度限定符就近查找的原理,能够看进去匹配进去的大小不够精确。
  • 是在 Android 3.2 当前引入的,Google 的本意是用它来适配平板的布局文件(然而实际上显然用于 diemns 适配的成果更好),不过目前 SPX 所有的我的项目应该最低反对版本应该都是 5.1 了,所以这问题其实也不重要了。

dimens 基于 px 的适配 宽高限定符适配

原理:

依据市面上手机分辨率的占比剖析,咱们选定一个占比例值大的(比方 1280*720)设定为一个基准,而后其余分辨率依据这个基准做适配。

基准的意思(比方 320*480 的分辨率为基准)是:

宽为 320,将任何分辨率的宽度分为 320 份,取值为 x1 到 x320

长为 480,将任何分辨率的高度分为 480 份,取值为 y1 到 y480

例如对于 800 * 480 的分辨率设施来讲,须要在我的项目中 values-800×480 目录下的 dimens.xml 文件中的如下设置(当然了,能够通过工具主动生成):

<resources>
<dimen name="x1">1.5px</dimen>
<dimen name="x2">3.0px</dimen>
<dimen name="x3">4.5px</dimen>
<dimen name="x4">6.0px</dimen>
<dimen name="x5">7.5px</dimen></pre>

能够看到 x1 = 480 / 基准 = 480 / 320 = 1.5 ; 它的意思就是同样的 1px,在 320/480 分辨率的手机上是 1px,在 480/800 的分辨率的手机上就是 1 *1.5px,px 会依据咱们指定的不同 values 文件夹主动适配为适合的大小。

验证计划:

简略通过计算验证下这种计划是否能达到适配的成果,例如设计图上有一个宽 187dp 的 View。

分辨率为 480 * 800

  • 设计图占宽比: 187dp / 375dp = 0.498
  • 理论在 480 800 占宽比 = 187 1.28px / 480 = 0.498

分辨率为 1080 * 1920

  • 设计图占宽比: 187dp / 375dp = 0.498
  • 理论在 1080 1920 占宽比 = 187 2.88px / 1080 = 0.498
  • 计算高同理

毛病:

  • 侵入性强
  • 须要精准命中资源文件能力适配,比方 1920×1080 的手机就肯定要找到 1920×1080 的限定符,否则就只能用对立的默认的 dimens 文件了。而应用默认的尺寸的话,UI 就很可能变形,简略说,就是容错机制很差。
  • Android 不同分辨率的手机切实太多了,可能你说支流就能够,确实小公司支流就能够,淘宝这种 App 必定不能只适配支流手机。控件在设计图上显示的大小以及控件之间的间隙在小分辨率和大分辨率手机上天壤之别,你会发现大屏幕手机上控件超级大。可能你会感觉失常,毕竟分辨率不同。但实际效果大的有些夸大。
  • 占据资源大:好几百 KB,甚至多达 1M 或跟多。

头条屏幕适配计划

梳理需要:

首先来梳理下咱们的需要,个别咱们设计图都是以固定的尺寸来设计的。比方以分辨率 1920px 1080px 来设计,以 density 为 3 来标注,也就是屏幕其实是 640dp 360dp。如果咱们想在所有设施上显示完全一致,其实是不事实的,因为屏幕高宽比不是固定的,16:9、4:3 甚至其余宽高比层出不穷,宽高比不同,显示完全一致就不可能了。然而通常下,咱们只须要以宽或高一个维度去适配,比方咱们 Feed 是高低滑动的,只须要保障在所有设施中宽的维度上显示统一即可,再比方一个不反对高低滑动的页面,那么须要保障在高这个维度上都显示统一,尤其不能存在某些设施上显示不全的状况。同时思考到当初根本都是以 dp 为单位去做的适配,如果新的计划不反对 dp,那么迁徙老本也十分高。

因而,总结下大抵需要如下:

  • 反对以宽或者高一个维度去适配,放弃该维度上和设计图统一;
  • 反对 dp 和 sp 单位,管制迁徙老本到最小。

找计划兼容突破口

从 dp 和 px 的转换公式:

$\color{red}{px = dp * density}$

能够看出,如果设计图宽为 360dp,想要保障在所有设施计算得出的 px 值都正好是屏幕宽度的话,咱们只能批改 density 的值。通过浏览源码,咱们能够得悉,density 是 DisplayMetrics 中的成员变量,而 DisplayMetrics 实例通过 Resources#getDisplayMetrics 能够取得,而 Resouces 通过 Activity 或者 Application 的 Context 取得。

先来相熟下 DisplayMetrics 中和适配相干的几个变量:

  • DisplayMetrics#density 就是上述的 density
  • DisplayMetrics#densityDpi 就是上述的 dpi
  • DisplayMetrics#scaledDensity 字体的缩放因子,失常状况下和 density 相等,然而调节零碎字体大小后会扭转这个值

是不是 Android 中所有的 dp 和 px 的换算都是通过 DisplayMetrics 中相干的值来计算的呢?

  • 首先来看看布局文件中的 dp 转化,最终都是调用 TypedValue#applyDimension 来进行住转化

  • 图片的 decode,BitmapFactory#decodeResourceStream 办法:

当然还有些其余 dp 转换的场景,根本都是通过 DisplayMetrics 来计算的,这里不再详述。因而,想要满足上述需要,咱们只须要批改DisplayMetrics 中和 dp 转换相干的变量即可。

最终计划:

上面假如设计图宽度是 360dp,以宽维度来适配。

那么适配后 自定义 density = 设施实在宽(单位 px) / 360,接下来只须要把咱们计算好的 density 在零碎中批改下即可,代码实现如下:

同时在 Activity#onCreate 办法中调用下。代码比较简单,也没有波及到零碎非公开 api 的调用,因而实践上不会影响 app 稳定性。

毛病:

  • 只能反对以高或宽中的一个作为基准进行适配。
  • 只须要批改一次 density,我的项目中的所有中央都会主动适配,这个看似解放了双手,缩小了很多操作,然而实际上反馈了一个毛病,那就是只能一刀切的将整个我的项目进行适配,但适配范畴是不可控的。我的项目中如果采纳了零碎控件、三方库控件、等不是咱们我的项目本身设计的控件,这时就会呈现和咱们我的项目本身的设计图尺寸差距十分大的问题。

头条适配计划改良版本

大抵思路:在头条适配计划的根底上,通过重写 Activity 的 getResources(), 重写冷门单位 pt 作为基准单位,它是 Android 中的一个长度单位:示意一个点,是屏幕的物理尺寸,其大小为 1 英寸的 1 / 72,也就是 72pt 等于 1 英寸

  • AdaptScreenUtils
public final class AdaptScreenUtils {
private static List<Field> sMetricsFields;

private AdaptScreenUtils() {throw new UnsupportedOperationException("u can't instantiate me...");
}

/**
 * Adapt for the horizontal screen, and call it in {@link android.app.Activity#getResources()}.
 */
public static Resources adaptWidth(final Resources resources, final int designWidth) {float newXdpi = (resources.getDisplayMetrics().widthPixels * 72f) / designWidth;
    applyDisplayMetrics(resources, newXdpi);
    return resources;
}

/**
 * Adapt for the vertical screen, and call it in {@link android.app.Activity#getResources()}.
 */
public static Resources adaptHeight(final Resources resources, final int designHeight) {return adaptHeight(resources, designHeight, false);
}

/**
 * Adapt for the vertical screen, and call it in {@link android.app.Activity#getResources()}.
 */
public static Resources adaptHeight(final Resources resources, final int designHeight, final boolean includeNavBar) {float screenHeight = (resources.getDisplayMetrics().heightPixels
            + (includeNavBar ? getNavBarHeight(resources) : 0)) * 72f;
    float newXdpi = screenHeight / designHeight;
    applyDisplayMetrics(resources, newXdpi);
    return resources;
}

private static int getNavBarHeight(final Resources resources) {int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
    if (resourceId != 0) {return resources.getDimensionPixelSize(resourceId);
    } else {return 0;}
}

/**
 * @param resources The resources.
 * @return the resource
 */
public static Resources closeAdapt(final Resources resources) {float newXdpi = Resources.getSystem().getDisplayMetrics().density * 72f;
    applyDisplayMetrics(resources, newXdpi);
    return resources;
}

/**
 * Value of pt to value of px.
 *
 * @param ptValue The value of pt.
 * @return value of px
 */
public static int pt2Px(final float ptValue) {DisplayMetrics metrics = FWAdSDK.sContext.getResources().getDisplayMetrics();
    return (int) (ptValue * metrics.xdpi / 72f + 0.5);
}

/**
 * Value of px to value of pt.
 *
 * @param pxValue The value of px.
 * @return value of pt
 */
public static int px2Pt(final float pxValue) {DisplayMetrics metrics = FWAdSDK.sContext.getResources().getDisplayMetrics();
    return (int) (pxValue * 72 / metrics.xdpi + 0.5);
}

private static void applyDisplayMetrics(final Resources resources, final float newXdpi) {resources.getDisplayMetrics().xdpi = newXdpi;
    FWAdSDK.sContext.getResources().getDisplayMetrics().xdpi = newXdpi;
    applyOtherDisplayMetrics(resources, newXdpi);
}

static void preLoad() {applyDisplayMetrics(Resources.getSystem(), Resources.getSystem().getDisplayMetrics().xdpi);
}

private static void applyOtherDisplayMetrics(final Resources resources, final float newXdpi) {if (sMetricsFields == null) {sMetricsFields = new ArrayList<>();
        Class resCls = resources.getClass();
        Field[] declaredFields = resCls.getDeclaredFields();
        while (declaredFields != null && declaredFields.length > 0) {for (Field field : declaredFields) {if (field.getType().isAssignableFrom(DisplayMetrics.class)) {field.setAccessible(true);
                    DisplayMetrics tmpDm = getMetricsFromField(resources, field);
                    if (tmpDm != null) {sMetricsFields.add(field);
                        tmpDm.xdpi = newXdpi;
                    }
                }
            }
            resCls = resCls.getSuperclass();
            if (resCls != null) {declaredFields = resCls.getDeclaredFields();
            } else {break;}
        }
    } else {applyMetricsFields(resources, newXdpi);
    }
}

private static void applyMetricsFields(final Resources resources, final float newXdpi) {for (Field metricsField : sMetricsFields) {
        try {DisplayMetrics dm = (DisplayMetrics) metricsField.get(resources);
            if (dm != null) dm.xdpi = newXdpi;
        } catch (Exception e) {Log.e("AdaptScreenUtils", "applyMetricsFields:" + e);
        }
    }
}

private static DisplayMetrics getMetricsFromField(final Resources resources, final Field field) {
    try {return (DisplayMetrics) field.get(resources);
    } catch (Exception e) {Log.e("AdaptScreenUtils", "getMetricsFromField:" + e);
        return null;
    }
}
}
  • 应用办法
    以宽度 320 为基准进行适配
@Override
public Resources getResources() {return AdaptScreenUtils.adaptWidth(super.getResources(),320);
}

假如我当初须要在屏幕核心有个按钮,宽度和高度为咱们屏幕宽度的 1 /2,我能够怎么编写布局文件呢?

<FrameLayout>
<Button
    android:layout_gravity="center"
    android:gravity="center"
    android:text="@string/hello_world"
    android:layout_width="160pt"
    android:layout_height="160pt"/>
</FrameLayout>

成果

480 x 800 – mdpi(160dpi)

720 x 1280 – xhdpi(320dpi)

1080 x 1920 – xxhdpi(480dpi)

能够看到效果图中 WebView 对之后的 View 并没有产生适配生效的问题,这是之前适配所不能解决的问题。

长处

1. 无侵入性
用了这个之后仍然能够应用 dp 包含其余任何单位,对从前应用的布局不会造成任何影响,在老我的项目中开发新性能你能够胆大地退出该适配计划,新我的项目的话更能够毫不犹豫地采纳该适配,并且在敞开该敞开后,pt 成果等同于 dp 哦。

2. 灵活性高
如果你想要对某个 View 做到不同分辨率的设施下,使其尺寸在适配维度上所占比例统一的话,那么对它应用 pt 单位即可,如果你不想要这样的成果,而是想要更大尺寸的设施显示更多的内容,那么能够像从前那样写 dpsp 什么的即可,联合这两点,在界面布局上你就能够熟能生巧地做到你想要的成果。

3. 不会影响零碎 View 和三方 View 的大小
这点其实在无侵入性中曾经体现进去了,因为头条的计划是间接批改 DisplayMetrics#densitydp 适配,这样会导致系统 View 尺寸和原先不统一,比方 DialogToast、尺寸,同样,三方 View 的大小也会和原先成果不统一,这也就是抉择 pt 适配的起因之一。

4. 不会生效
因为不管头条的适配还是其余三方库适配,都会存在 DisplayMetrics#density 被还原的状况,须要本人从新设置回去,最显著的就是界面中存在 WebView 的话,因为其初始化的时候会还原 DisplayMetrics#density 的值导致适配生效,当然这点曾经有解决方案了,但还会有很多其余状况会还原 DisplayMetrics#density 的值导致适配生效。而我这计划就是为了解决这个痛点,不让 DisplayMetrics 中的值被还原导致适配生效。

毛病:

只能适配宽或者高其中一边,但这也是绝大部分适配计划的痛点所在,长和宽只能适配其一,好在大部分公司在采纳这些计划去适配是都采纳优先适配宽,而后在长下面以滑动模式去进行解决;

小结:

尽管 dimen 基于 px 和 dp 的适配 这种计划能涵盖市面上所有机型屏幕的适配,然而冗余的 dimen 文件会让工程师们生不如死,而且这种计划侵入性十分强,一旦应用将使得回退变得十分的艰难;头条适配计划和头条适配优化计划 作为一种侵入性不是很强的形式进行接入,能完满解决代码冗余问题,而且总体方案灵活性很高,但只能抉择宽或者高作为惟一维度去进行适配;

上述计划都能用来解决屏幕适配问题,每种计划都有其独特的优缺点,因而最终选取哪种计划因人而异

参考文章:

Android 屏幕适配和计划【整顿】

Android 屏幕适配:最全面的解决方案

Android 屏幕适配计划

一种极低成本的 Android 屏幕适配形式

Android 据说你还在用 dp 单位做屏幕适配?

❤️ 谢谢反对

以上便是本次分享的全部内容,心愿对你有所帮忙 ^_^

喜爱的话别忘了 分享、点赞、珍藏 三连哦~。

欢送关注公众号 程序员巴士,一辆乏味、有范儿、有温度的程序员巴士,涉猎大厂面经、程序员生存、实战教程、技术前沿等内容,关注我,交个敌人。

正文完
 0