Android wm size显示缩放问题

本文深入解析了Android系统如何处理分辨率设置,当使用wmsize命令修改分辨率时,即使超过或低于屏幕实际分辨率,系统会通过等比例缩放保持界面比例,避免压缩畸变。在LogicalDisplay.configureDisplayLocked方法中,系统根据设备和设定的分辨率计算显示区域,以适应物理屏幕。当设置的分辨率宽高比与屏幕不一致时,会出现宽度或高度未占满全屏的情况。对于非对称裁剪的需求,可能需要修改此逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        如果使用过wm size命令来修改Android的分辨率的人,应该都会发现wm size分辨无论是超过还是低于屏幕分辨率,但最终显示时,显示屏上仍有一个维度必定是匹配屏幕的。

 

        那么这个东西是在哪导致的呢。实际上通过dumpsys display来看,display的分辨率确实是自己所设置的那个,然而显示的却不是,因为真实的显示屏分辨率是有限的。Android原生框架就考虑了这个,因此在将这个显示的分辨率设置底层SF/hwc时,做了一个等比例的缩放,使界面没有明显的压缩畸变感,也能完整地显示在屏幕上。

分析

        这部分代码是在LogicalDisplay.configureDisplayLocked这个方法中实现的,我们先来看一下这段源码:

    public void configureDisplayLocked(SurfaceControl.Transaction t,
            DisplayDevice device,
            boolean isBlanked) {
        // Set the layer stack.
        device.setLayerStackLocked(t, isBlanked ? BLANK_LAYER_STACK : mLayerStack);

        // Set the color mode and allowed display mode.
        if (device == mPrimaryDisplayDevice) {
            device.setDesiredDisplayModeSpecsLocked(mDesiredDisplayModeSpecs);
            device.setRequestedColorModeLocked(mRequestedColorMode);
        } else {
            // Reset to default for non primary displays
            device.setDesiredDisplayModeSpecsLocked(
                    new DisplayModeDirector.DesiredDisplayModeSpecs());
            device.setRequestedColorModeLocked(0);
        }

        device.setAutoLowLatencyModeLocked(mRequestedMinimalPostProcessing);
        device.setGameContentTypeLocked(mRequestedMinimalPostProcessing);

        // Only grab the display info now as it may have been changed based on the requests above.
        final DisplayInfo displayInfo = getDisplayInfoLocked();
        final DisplayDeviceInfo displayDeviceInfo = device.getDisplayDeviceInfoLocked();

        // Set the viewport.
        // This is the area of the logical display that we intend to show on the
        // display device.  For now, it is always the full size of the logical display.
        mTempLayerStackRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);

        // Set the orientation.
        // The orientation specifies how the physical coordinate system of the display
        // is rotated when the contents of the logical display are rendered.
        int orientation = Surface.ROTATION_0;
        if ((displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0) {
            orientation = displayInfo.rotation;
        }

        // Apply the physical rotation of the display device itself.
        orientation = (orientation + displayDeviceInfo.rotation) % 4;

        // Set the frame.
        // The frame specifies the rotated physical coordinates into which the viewport
        // is mapped.  We need to take care to preserve the aspect ratio of the viewport.
        // Currently we maximize the area to fill the display, but we could try to be
        // more clever and match resolutions.
        boolean rotated = (orientation == Surface.ROTATION_90
                || orientation == Surface.ROTATION_270);
        int physWidth = rotated ? displayDeviceInfo.height : displayDeviceInfo.width;
        int physHeight = rotated ? displayDeviceInfo.width : displayDeviceInfo.height;

        Rect maskingInsets = getMaskingInsets(displayDeviceInfo);
        InsetUtils.rotateInsets(maskingInsets, orientation);
        // Don't consider the masked area as available when calculating the scaling below.
        physWidth -= maskingInsets.left + maskingInsets.right;
        physHeight -= maskingInsets.top + maskingInsets.bottom;

        // Determine whether the width or height is more constrained to be scaled.
        //    physWidth / displayInfo.logicalWidth    => letter box
        // or physHeight / displayInfo.logicalHeight  => pillar box
        //
        // We avoid a division (and possible floating point imprecision) here by
        // multiplying the fractions by the product of their denominators before
        // comparing them.
        int displayRectWidth, displayRectHeight;
        if ((displayInfo.flags & Display.FLAG_SCALING_DISABLED) != 0 || mDisplayScalingDisabled) {
            displayRectWidth = displayInfo.logicalWidth;
            displayRectHeight = displayInfo.logicalHeight;
        } else if (physWidth * displayInfo.logicalHeight
                < physHeight * displayInfo.logicalWidth) {
            // Letter box.
            displayRectWidth = physWidth;
            displayRectHeight = displayInfo.logicalHeight * physWidth / displayInfo.logicalWidth;
        } else {
            // Pillar box.
            displayRectWidth = displayInfo.logicalWidth * physHeight / displayInfo.logicalHeight;
            displayRectHeight = physHeight;
        }
        int displayRectTop = (physHeight - displayRectHeight) / 2;
        int displayRectLeft = (physWidth - displayRectWidth) / 2;
        mTempDisplayRect.set(displayRectLeft, displayRectTop,
                displayRectLeft + displayRectWidth, displayRectTop + displayRectHeight);

        // Now add back the offset for the masked area.
        mTempDisplayRect.offset(maskingInsets.left, maskingInsets.top);

        if (orientation == Surface.ROTATION_0) {
            mTempDisplayRect.offset(mDisplayOffsetX, mDisplayOffsetY);
        } else if (orientation == Surface.ROTATION_90) {
            mTempDisplayRect.offset(mDisplayOffsetY, -mDisplayOffsetX);
        } else if (orientation == Surface.ROTATION_180) {
            mTempDisplayRect.offset(-mDisplayOffsetX, -mDisplayOffsetY);
        } else {  // Surface.ROTATION_270
            mTempDisplayRect.offset(-mDisplayOffsetY, mDisplayOffsetX);
        }

        mDisplayPosition.set(mTempDisplayRect.left, mTempDisplayRect.top);
        device.setProjectionLocked(t, orientation, mTempLayerStackRect, mTempDisplayRect);
    }

        这里面有几个值需要我们去关注:displayInfo.logicalWidth、displayInfo.logicalHeight、displayDeviceInfo.width、displayDeviceInfo.height。displayDeviceInfo是原始display的宽高数据,displayInfo是我们设置的宽高数据。

        接下来我们就可以看到,当display被设置为不可scaling或mDisplayScalingDisabled,那么,真实的显示宽高就是我们设置的宽高。

        否则,就是需要进行显示的缩放。

        当physWidth * displayInfo.logicalHeight  < physHeight * displayInfo.logicalWidth,我们转换一下公式:

\frac{displayInfo.logicalWidth}{physWidth} > \frac{displayInfo.logicalHeight}{physHeight}

        如果设置宽度和真实宽度的比例大于设置高度和真实高度的比例,也就是说如果按比例缩放,那么宽度则会超出显示屏的真实宽度,因此需要限制宽度,即缩放后显示的宽度为显示屏宽度,而高度则按比例缩小。

        反之,则是高度的比例大于宽度的比例,显示的高度限制为显示屏高度,宽度则按比例缩小。

        因此我们所设置的分辨率宽高比与显示屏的宽高比不一样时,就会出现有一个维度(宽度或高度)不是占满全屏的。

        当比例一致时,则不会出现此问题。

需求

        书接上回,上一篇描述了一个客户需求,当屏幕的某个维度被对称裁剪后,我们完全可以通过wm size的方式来达到我们的需求。但如果是两个维度同时裁剪,设置不是对称裁剪时,我们需要对configureDisplayLocked的逻辑进行修改了。

<think>我们正在讨论的是在MTK平台的Android 12系统中,Framework层如何设置显示大小的默认值。根据用户的问题,我们需要关注的是显示大小(Display Size)的默认设置,这通常涉及到屏幕密度(density)或缩放因子的设置。 在Android系统中,显示大小通常与屏幕密度(density)相关,系统会根据设备的屏幕特性(如分辨率、尺寸)计算出默认的密度,然后通过这个密度来调整显示内容的大小。在Framework层,这个设置通常位于DisplayMetrics中。 根据Android源码,我们可以知道系统在启动时会读取设备的配置文件(如ro.sf.lcd_density)来设置默认的密度。但在MTK平台,可能会有一些特定的配置方式。 参考用户提供的引用[2](虽然主要是关于色彩模式的,但其中涉及到了显示设置的处理流程)和引用[3](提到了MTK平台的工程模式),我们可以推测MTK平台可能有自己的配置方式。 在Android系统中,默认的显示密度通常由系统属性`ro.sf.lcd_density`决定,这个属性在系统启动时由init进程读取,并设置到SurfaceFlinger等服务中。在Framework层,这个值会被读取并用于初始化DisplayMetrics。 然而,在MTK平台,可能会有额外的配置步骤。例如,在设备制造商(OEM)的配置文件中覆盖默认值。 具体到Android 12的MTK平台,我们可以按照以下步骤来设置默认显示大小: 1. **系统属性设置**:在设备的`system.prop`或`build.prop`中设置`ro.sf.lcd_density`属性。这个属性定义了默认的屏幕密度(dpi)。例如: ``` ro.sf.lcd_density=480 ``` 2. **在Framework中设置**:如果需要在代码中动态设置,可能需要修改Framework中与显示相关的服务。例如,在`DisplayManagerService`中,系统会读取这个属性并应用到全局的DisplayMetrics中。 3. **MTK特定的配置**:MTK平台可能在其配置文件中设置了默认值。我们可以查找MTK的配置目录(如`device/mediatek`或`vendor/mediatek`)下的相关配置。例如,在`init.mtXXXX.rc`文件中可能会设置这个属性。 4. **通过Overlay资源覆盖**:另一种方式是通过Overlay机制在资源文件中覆盖默认值。例如,在`frameworks/base/core/res/res/values/config.xml`中有一个`config_densityDpi`的配置项,我们可以通过设备覆盖(device overlay)来修改这个值。 5. **在SettingsProvider中设置默认值**:系统设置中的显示大小设置可能会存储在Settings Provider中,我们可以设置其默认值。例如,在`Settings.System`中有一个`DISPLAY_SIZE_FORCED`(已废弃)或`DISPLAY_DENSITY_FORCED`(在较新版本中)等设置项。但默认情况下,系统会使用上述的`ro.sf.lcd_density`。 根据用户的问题,我们关注的是Framework层的默认设置。因此,最直接的方法是通过系统属性`ro.sf.lcd_density`来设置。 在MTK平台,这个属性通常在`init.mtXXXX.rc`文件中设置,或者在`device.mk`中通过`PRODUCT_PROPERTY_OVERRIDES`来设置。 例如,在设备Makefile中: ```makefile PRODUCT_PROPERTY_OVERRIDES += ro.sf.lcd_density=480 ``` 或者在`init`脚本中: ``` on boot setprop ro.sf.lcd_density 480 ``` 但是,请注意,在Android 12中,由于Treble架构,系统属性的设置位置可能有所不同。建议在设备树的`system.prop`文件中设置。 另外,如果用户想要在代码中动态改变默认值(例如根据不同的显示模式),那么可能需要修改Framework中读取该属性的地方。在Android Framework中,默认的显示密度在`DisplayMetrics`的初始化过程中被设置,具体在`DisplayManagerService`中。 在`DisplayManagerService`中,有一个方法`setDisplayProperties`会设置显示属性,其中会读取系统属性`ro.sf.lcd_density`。如果这个属性没有设置,则会使用默认值。 因此,在MTK Android 12上,设置默认显示大小的主要方法就是设置`ro.sf.lcd_density`系统属性。 **注意**:显示大小(Display Size)在用户设置中通常指的是缩放因子,而缩放因子是基于默认密度(density)的。所以设置默认密度就是设置默认显示大小的基础。 如果用户想要设置的是显示区域的大小(比如分辨率),那么可能需要调整显示设备的分辨率,这通常是在显示服务(如SurfaceFlinger)中处理的,但默认情况下,Android使用设备的最大分辨率。 综上所述,对于MTK Android 12 Framework层设置显示大小的默认值,我们推荐通过设置系统属性`ro.sf.lcd_density`来实现。 **相关问题**: 1. 如何在Android系统中动态改变屏幕密度? 2. 在Android 12中,显示大小设置的用户界面是如何实现的? 3. MTK平台在显示设置方面有哪些特定的定制点? 4. 如何通过Overlay机制覆盖默认的显示密度?</think>在MTK平台的Android 12系统中,Framework层设置显示大小默认值主要通过以下步骤实现: ### 核心实现机制 1. **系统属性配置** 默认显示大小由`ro.sf.lcd_density`系统属性控制,该值在设备编译时确定: ```makefile # device.mk 或 system.prop PRODUCT_PROPERTY_OVERRIDES += ro.sf.lcd_density=480 ``` 此值会被写入`/system/build.prop`文件,系统启动时由`SurfaceFlinger`读取[^1]。 2. **DisplayMetrics初始化** Framework层在`DisplayMetrics.java`中加载默认密度: ```java // frameworks/base/core/java/android/util/DisplayMetrics.java public static int DENSITY_DEVICE_STABLE = SystemProperties.getInt("ro.sf.lcd_density", DENSITY_DEFAULT); ``` 3. **窗口管理服务应用** `WindowManagerService`在初始化时通过`updateForcedDisplaySettingsLocked()`方法强制应用默认值: ```java // frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java private void updateForcedDisplaySettingsLocked() { mDisplaySettings.setForcedDensity(Display.DEFAULT_DISPLAY, SystemProperties.getInt("persist.sys.sf_density", SystemProperties.getInt("ro.sf.lcd_density", 320))); } ``` 4. **MTK平台定制点** MTK通常在`mediatek/frameworks/base`中添加扩展: ```java // mediatek/frameworks/base/core/java/com/mediatek/view/ScalableDisplay.java public static void setDefaultScaleFactor(float scale) { sDefaultScale = scale; // 设置默认缩放因子 } ``` ### 关键配置文件位置 | 文件路径 | 作用 | |----------|------| | `device/mediatek/<project>/system.prop` | 定义`ro.sf.lcd_density`默认值 | | `frameworks/base/core/res/res/values/config.xml` | 配置`config_defaultScaledDensity` | | `frameworks/base/services/core/java/com/android/server/wm/DisplaySettings.java` | 持久化显示设置 | ### 调试验证方法 1. 通过ADB检查当前值: ```bash adb shell getprop ro.sf.lcd_density adb shell wm density ``` 2. 在工程模式中查看(MTK专属): ``` *#*#3646633#*#* → Display → Density Settings ``` ### 注意事项 - **动态修改**:用户设置会覆盖默认值,存储在`settings_global.xml`的`display_density_forced`字段 - **兼容性**:需确保`ro.sf.lcd_density`与`ro.sf.real_lcd_density`(物理密度)的比例合理 - **分屏适配**:Android 12新增多窗口密度适配逻辑,需验证`Activity#getDisplay()`返回值[^4] > 提示:修改默认值后需重新编译`framework-res.apk`并验证CTS测试项`android.view.cts.DensityTest`[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值