Android屏幕尺寸获取方法区别

20 阅读4分钟

一、故事开篇:小明租房记

小明刚到大城市打工,想租一间房。他有两个选择:

  • 方案A:找中介问“这套房子的实际居住面积是多少?”——中介会告诉他:卧室、客厅、厨房加起来共50平米,但不包括公摊的走廊、电梯间。
  • 方案B:直接找开发商问“这块地皮上房子盖了多大?”——开发商说:整层楼建筑面积80平米,包含公摊、外墙、甚至楼道的消防栓。

在 Android 世界里,getDisplayMetrics()  就是那个“中介”,告诉你应用能用的显示区域(扣除了状态栏、导航栏、刘海等系统装饰)。
getRealSize()  就是那个“开发商”,告诉你屏幕的物理完整尺寸(整个显示面板,哪怕被系统 UI 遮住的地方也算)。

二、代码直观对比

// 在 Activity 或 View 中
DisplayMetrics metrics = getResources().getDisplayMetrics();
int usableWidth = metrics.widthPixels;   // 可用宽度(像素)
int usableHeight = metrics.heightPixels; // 可用高度(像素)

// 另一种方式
DisplayManager displayManager = (DisplayManager) getSystemService(DISPLAY_SERVICE);
Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
Point realSize = new Point();
display.getRealSize(realSize);
int physicalWidth = realSize.x;   // 物理宽度(全屏幕)
int physicalHeight = realSize.y;  // 物理高度(全屏幕)

举个真实例子:一部手机物理分辨率 1080×2340,但底部有虚拟导航栏(高 150 像素),顶部有状态栏(高 80 像素)。
getDisplayMetrics() 返回的 heightPixels ≈ 2340 - 150 - 80 = 2110。
getRealSize() 返回的 physicalHeight = 2340。

三、深入源码:中介 vs 开发商

1. 中介的算盘 —— getDisplayMetrics()

调用链:
Resources.getDisplayMetrics() → ResourcesImpl.getDisplayMetrics() → DisplayMetrics 从哪里填充?

最终追到 Display 类的方法:

// android.view.Display
public void getMetrics(DisplayMetrics outMetrics) {
    // 注意:这里传入的 outMetrics 会被填入“应用区域”尺寸
    synchronized (this) {
        if (mDisplayInfo == null) {
            updateDisplayInfoLocked();
        }
        mDisplayInfo.getAppMetrics(outMetrics, mCompatibilityInfo, mDisplayAdjustments);
    }
}

mDisplayInfo 是 DisplayInfo 对象,由系统服务 DisplayManagerService 定期更新。关键在 getAppMetrics

// android.view.DisplayInfo
public void getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
        DisplayAdjustments displayAdjustments) {
    // 1. 先拿到物理尺寸
    int width = logicalWidth;
    int height = logicalHeight;
    
    // 2. 减去系统窗口(状态栏、导航栏、手势区域)占用的空间
    // 这一步通过 WindowManager 的 Policy(如 PhoneWindowManager)计算
    Rect appRect = new Rect();
    getNonDecorDisplaySize(appRect);  // 计算扣除装饰后的可用矩形
    
    outMetrics.widthPixels = appRect.width();
    outMetrics.heightPixels = appRect.height();
    // ... 密度等属性
}

核心原理
系统在 WindowManagerService 中维护了一个“窗口策略”(WindowPolicy),它会根据当前的系统 UI 可见性、导航模式(手势/三键)、刘海屏缺口等,实时计算出一个 “安全的应用显示矩形”getDisplayMetrics() 拿到就是这个矩形的大小。

2. 开发商的账本 —— getRealSize()

调用链更直接:

// android.view.Display
public void getRealSize(Point outSize) {
    synchronized (this) {
        updateDisplayInfoLocked();
        // 直接返回物理尺寸,不扣任何装饰
        outSize.x = mDisplayInfo.logicalWidth;
        outSize.y = mDisplayInfo.logicalHeight;
    }
}

那 logicalWidth/Height 又是哪来的?它们来自 DisplayInfo 的 getPhysicalSize

// android.view.DisplayInfo
public void getPhysicalSize(Point outSize) {
    outSize.x = (int)(physicalWidth * displayScalingDisabled ? 1 : compatibilityScale);
    outSize.y = (int)(physicalHeight * ...);
}

physicalWidth/Height 是从底层 SurfaceFlinger 读取的原始显示模式分辨率。SurfaceFlinger 通过 HWC(硬件合成器)直接询问屏幕驱动:“兄弟,你这块玻璃到底多少个发光点?”驱动回答:“1080×2340”。然后一路传上来,没有任何窗口策略的干预。

四、时序图:亲眼见证数据的旅程

下面用 mermaid 格式描述一次从应用调用到底层驱动的完整过程。
(注:如果渲染不出,可以想象成一群小人在传递纸条)

1234.png

关键差异

  • getDisplayMetrics() 多了一次到 WMS 的“窗口策略”询问,会实时扣除系统 UI。
  • getRealSize() 直接从 SurfaceFlinger 拿最底层的物理值,干净利落。

五、为什么会有这种差别?—— Android 的设计哲学

Android 早期(2.x ~ 4.x)手机都是有物理按键的,屏幕就是纯粹的显示区,那时候两个方法返回值一样。后来有了虚拟导航栏、手势条、刘海、挖孔……Google 为了让应用开发者不用关心这些花里胡哨的异形屏,设计了  “应用可用区域”  的概念——你只管在你得到的尺寸里画 UI,那些系统区域由系统自动处理。

于是:

  • getDisplayMetrics() :适合布局 UI,保证你的按钮不会被虚拟导航栏挡住。
  • getRealSize() :适合全屏沉浸式场景(游戏、视频),或者你想精确知道“这手机到底有多长”时使用。

六、小彩蛋:还有两个亲戚叫 getSize() 和 getMetrics()

其实 Display 还有 getSize(Point) 方法,它和 getDisplayMetrics() 返回的可用尺寸基本一致,只是单位不同(一个是 Point 像素对,一个是 DisplayMetrics 结构)。而 getRealMetrics(DisplayMetrics) 则类似于 getRealSize,填充分辨率和真实密度。

所以记忆口诀:

  • Real 家族 = 真实物理尺寸(开发商)
  • 非Real 家族 = 扣除系统UI后的可用尺寸(中介)

七、最后:一段让你恍然大悟的代码

// 在 Activity 中,假设全屏且导航栏是虚拟按键
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
// metrics.heightPixels = 2110(可用区域)

Point realSize = new Point();
getWindowManager().getDefaultDisplay().getRealSize(realSize);
// realSize.y = 2340(物理全屏)

// 那你猜导航栏高度是多少?
int navBarHeight = realSize.y - metrics.heightPixels; 
// 结果是 230 像素(状态栏+导航栏?实际要分开算,但道理一样)