如果使用过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,我们转换一下公式:
如果设置宽度和真实宽度的比例大于设置高度和真实高度的比例,也就是说如果按比例缩放,那么宽度则会超出显示屏的真实宽度,因此需要限制宽度,即缩放后显示的宽度为显示屏宽度,而高度则按比例缩小。
反之,则是高度的比例大于宽度的比例,显示的高度限制为显示屏高度,宽度则按比例缩小。
因此我们所设置的分辨率宽高比与显示屏的宽高比不一样时,就会出现有一个维度(宽度或高度)不是占满全屏的。
当比例一致时,则不会出现此问题。
需求
书接上回,上一篇描述了一个客户需求,当屏幕的某个维度被对称裁剪后,我们完全可以通过wm size的方式来达到我们的需求。但如果是两个维度同时裁剪,设置不是对称裁剪时,我们需要对configureDisplayLocked的逻辑进行修改了。