Android Display 系统分析
大概两年前做过一个项目,大致是在Android 系统上实现双显的支持,其中有个需求是需要手动配置每个显示器的旋转角度,当时对Android 的 Display系统有关简单了解,但是不够深入。一直觉得是留下了一个遗憾,现在趁有时间来把这一块再好好了解下。闲话少说,开始吧。本文将按照以下方式来组织:
- Android Display 框架
- Android SurfaceFlinger中Display部分
- Android Framework 中Display 部分
DisplayManagerService对display的管理
WindowManagerService对Display的管理 - Android系统转屏问题
Android Display 框架
Android中Display 框架如下:
![Android Display](https://img-blog.csdn.net/20161130214140525)
如上图所示,Android App除使用Android Presentation 外不需要特别了解Display的相关信息()。而在linux kernel当中的MIPI/HDMI等相关显示设备的驱动也不在本文的讨论范围之列。所以本文讨论的重点在于图中的Android FW中的DisplayManagerService 部分与SurfaceFlinger部分。
Android SurfaceFlinger中的Display部分
从Android 启动开始,我们知道在Android的启动过程中,SurfaceFlinger会作为一个系统进程被Init进程启动,具体的相关信息可以研究Android启动的相关流程。
在SurfaceFlinger中其init函数会在SurfaceFlinger被初始化后被调用。
void SurfaceFlinger::init() {
ALOGI( "SurfaceFlinger's main thread ready to run. "
"Initializing graphics H/W...");
Mutex::Autolock _l(mStateLock);
// initialize EGL for the default display
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(mEGLDisplay, NULL, NULL);
// Initialize the H/W composer object. There may or may not be an
// actual hardware composer underneath.
mHwc = new HWComposer(this,
*static_cast<HWComposer::EventHandler *>(this));
..........
..........
我们可以看到在init函数中会创建一个HWComposer的对象。
HWComposer::HWComposer(
const sp<SurfaceFlinger>& flinger,
EventHandler& handler)
: mFlinger(flinger),
mFbDev(0), mHwc(0), mNumDisplays(1),
mCBContext(new cb_context),
mEventHandler(handler),
mDebugForceFakeVSync(false)
{
............
.............
// Note: some devices may insist that the FB HAL be opened before HWC.
int fberr = loadFbHalModule();
loadHwcModule();
if (mFbDev && mHwc && hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) {
// close FB HAL if we don't needed it.
// FIXME: this is temporary until we're not forced to open FB HAL
// before HWC.
framebuffer_close(mFbDev);
mFbDev = NULL;
}
// If we have no HWC, or a pre-1.1 HWC, an FB dev is mandatory.
if ((!mHwc || !hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1))
&& !mFbDev) {
ALOGE("ERROR: failed to open framebuffer (%s), aborting",
strerror(-fberr));
abort();
}
// these display IDs are always reserved
for (size_t i=0 ; i<NUM_BUILTIN_DISPLAYS ; i++) {
mAllocatedDisplayIDs.markBit(i);
}
if (mHwc) {
ALOGI("Using %s version %u.%u", HWC_HARDWARE_COMPOSER,
(hwcApiVersion(mHwc) >> 24) & 0xff,
(hwcApiVersion(mHwc) >> 16) & 0xff);
if (mHwc->registerProcs) {
mCBContext->hwc = this;
mCBContext->procs.invalidate = &hook_invalidate;
mCBContext->procs.vsync = &hook_vsync;
if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1))
mCBContext->procs.hotplug = &hook_hotplug;
else
mCBContext->procs.hotplug = NULL;
memset(mCBContext->procs.zero, 0, sizeof(mCBContext->procs.zero));
mHwc->registerProcs(mHwc, &mCBContext->procs);
}
// don't need a vsync thread if we have a hardware composer
needVSyncThread = false;
// always turn vsync off when we start
eventControl(HWC_DISPLAY_PRIMARY, HWC_EVENT_VSYNC, 0);
// the number of displays we actually have depends on the
// hw composer version
if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_3)) {
// 1.3 adds support for virtual displays
mNumDisplays = MAX_HWC_DISPLAYS;
} else if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) {
// 1.1 adds support for multiple displays
mNumDisplays = NUM_BUILTIN_DISPLAYS;
} else {
mNumDisplays = 1;
}
}
if (mFbDev) {
//默认使用HWC设备,所以不会走FB分支
...............
...............
} else if (mHwc) {
// here we're guaranteed to have at least HWC 1.1
// 查询系统相关显示设备。
for (size_t i =0 ; i<NUM_BUILTIN_DISPLAYS ; i++) {
queryDisplayProperties(i);
}
}
}
上面代码的主要意思是打开HWC设备,然后根据HWC的相关版本定义最多支持的显示设备数量。HWC是Android新版本引入的新模块,我个人的理解是替换掉早期的OverLayer机制,提供出全新的使用硬件合成的功能。而在我们这个范畴里只考虑了其对Display设备的管理。
status_t HWComposer::queryDisplayProperties(int disp) {
LOG_ALWAYS_FATAL_IF(!mHwc || !hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1));
// use zero as default value for unspecified attributes
int32_t values[NUM_DISPLAY_ATTRIBUTES - 1];
memset(values, 0, sizeof(values));
const size_t MAX_NUM_CONFIGS = 128;
uint32_t configs[MAX_NUM_CONFIGS] = {0};
size_t numConfigs = MAX_NUM_CONFIGS;
status_t err = mHwc->getDisplayConfigs(mHwc, disp, configs, &numConfigs);
if (err != NO_ERROR) {
// this can happen if an unpluggable display is not connected
mDisplayData[disp].connected = false;
return err;
}
mDisplayData[disp].currentConfig = 0;
for (size_t c = 0; c < numConfigs; ++c) {
err = mHwc->getDisplayAttributes(mHwc, disp, configs[c],
DISPLAY_ATTRIBUTES, values);
if (err != NO_ERROR) {
// we can't get this display's info. turn it off.
mDisplayData[disp].connected = false;
return err;
}
DisplayConfig config = DisplayConfig();
for (size_t i = 0; i < NUM_DISPLAY_ATTRIBUTES - 1; i++) {
switch (DISPLAY_ATTRIBUTES[i]) {
case HWC_DISPLAY_VSYNC_PERIOD:
config.refresh = nsecs_t(values[i]);
break;
case HWC_DISPLAY_WIDTH:
config.width = values[i];
break;
case HWC_DISPLAY_HEIGHT:
config.height = values[i];
break;
case HWC_DISPLAY_DPI_X:
config.xdpi = values[i] / 1000.0f;
break;
case HWC_DISPLAY_DPI_Y:
config.ydpi = values[i] / 1000.0f;
break;
#ifdef MTK_AOSP_ENHANCEMENT
case HWC_DISPLAY_SUBTYPE:
mDisplayData[disp].subtype = values[i];
break;
#endif
default:
ALOG_ASSERT(false, "unknown display attribute[%zu] %#x",
i, DISPLAY_ATTRIBUTES[i]);
break;
}
}
if (config.xdpi == 0.0f || config.ydpi == 0.0f) {
float dpi = getDefaultDensity(config.width, config.height);
config.xdpi = dpi;
config.ydpi = dpi;
}
mDisplayData[disp].configs.push_back(config);
}
// FIXME: what should we set the format to?
mDisplayData[disp].format = HAL_PIXEL_FORMAT_RGBA_8888;
mDisplayData[disp].connected = true;
return NO_ERROR;
}
从上面代码中可以看出HWC是怎么查询到显示屏的相关的参数,如显示屏宽度高度刷新率等等,注意下,HWC中可以查询出很多组的显示屏的相关参数。
uint32_t configs[MAX_NUM_CONFIGS] = {0};
size_t numConfigs = MAX_NUM_CONFIGS;
status_t err = mHwc->getDisplayConfigs(mHwc, disp, configs, &numConfigs);
- 1
- 2
- 3
大胆的猜测下,android中是否会开始支持分辨率的动态调整了呢?从以为的经验来说,一个手机在出厂的时候就固定好了分辨率,后续是不是能像windows 系统一样能动态调整呢?
我们记一下,显示屏的相关参数被保留在mDisplayData[disp]中。SurfaceFlinger中的display相关先到这里。之后再回来看看。
Android Framework 中Display 部分
DisplayManagerService对display的管理
从最上面的Android Display 框架图中可以看到,在Android 的JAVA的系统服务中会有一个DisplayManagerService的系统服务与我们常见的ActivityManagerService/WindowsManagerService 并列。从名字中也能看出来它实现的就是对Android Display的管理,这一节开始研究下这个系统服务。
DisplayManagerService的启动在于Android系统流程中由systemserver启动,与AMS/WMS 等JAVA层系统服务的启动方式一致。在这里就不再赘述了。接下来我们先来看看DMS(DisplayManagerService)怎么拿到已经存在的显示屏相关信息,注意,怎么获取显示屏相关信息已经在上一节中有介绍过了。
DisplayManagerService.java中DMS服务启动之时onStart函数会被调用,这个函数中会外发一个MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER的消息。
@Override
public void onStart() { mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER);
.............
.............
}
而这个消息会被registerDefaultDisplayAdapter函数处理。
private void registerDefaultDisplayAdapter() {
// Register default display adapter.
synchronized (mSyncRoot) {
registerDisplayAdapterLocked(new LocalDisplayAdapter(
mSyncRoot, mContext, mHandler, mDisplayAdapterListener));
}
}
啥都没干,只是创建了一个LocalDisplayAdapter对象。
private void registerDisplayAdapterLocked(DisplayAdapter adapter) {
mDisplayAdapters.add(adapter);
adapter.registerLocked();
}
在这里插一句,DMS中有很多类型的的DisplayAdapter
- LocalDisplayAdapter是针对本地已经存在的物理显示屏设备。
- WifiDisplayAdapter针对WiFi Display
- OverlayDisplayAdapter 这个还没有来得及看
- VirtualDisplayAdapter 显示一个虚拟屏幕,该功能可以在开发者选项中开启,可以去研究下这个,可以把android 怎么composer然后display流程理的比较清楚,而且可以不用去关心kernel中的一些问题,比如display 驱动,HWC/Grelloc等等。
好了,在这里我们先只关心LocalDisplayAdapter.
@Override
public void registerLocked() {
super.registerLocked();
.........................
for (int builtInDisplayId : BUILT_IN_DISPLAY_IDS_TO_SCAN) {
tryConnectDisplayLocked(builtInDisplayId);
}
}
在这里,系统会去尝试连接两种显示屏幕,built in跟HDMI,builtin可以理解成默认的显示屏,比如手机中默认的MIPI屏,而HDMI则是扩展屏,目前在手机上集成HDMI接口的貌似不多,但是usb type c流行后,通过type c来扩展屏幕可能不少,这可能会是一个新的手机定制需求。
private void tryConnectDisplayLocked(int builtInDisplayId) {
IBinder displayToken = SurfaceControl.getBuiltInDisplay(builtInDisplayId);
if (displayToken != null) {
SurfaceControl.PhysicalDisplayInfo[] configs =
SurfaceControl.getDisplayConfigs(displayToken);
if (configs == null) {
// There are no valid configs for this device, so we can't use it
Slog.w(TAG, "No valid configs found for display device " +
builtInDisplayId);
return;
}
int activeConfig = SurfaceControl.getActiveConfig(displayToken);
if (activeConfig < 0) {
// There is no active config, and for now we don't have the
// policy to set one.
Slog.w(TAG, "No active config found for display device " +
builtInDisplayId);
return;
}
LocalDisplayDevice device = mDevices.get(builtInDisplayId);
if (device == null) {
// Display was added.
device = new LocalDisplayDevice(displayToken, builtInDisplayId,
configs, activeConfig);
mDevices.put(builtInDisplayId, device);
sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
} else if (device.updatePhysicalDisplayInfoLocked(configs, activeConfig)) {
// Display properties changed.
sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
}
} else {
// The display is no longer available. Ignore the attempt to add it.
// If it was connected but has already been disconnected, we'll get a
// disconnect event that will remove it from mDevices.
}
}
这个函数里主要干了这几件事:
1,从SurfaceFlinger 中获取到显示屏的所有支持的配置参数。以及正在使用的参数。
status_t SurfaceFlinger::getDisplayConfigs(const sp<IBinder>& display,
Vector<DisplayInfo>* configs) {
..................
..................
configs->clear();
const Vector<HWComposer::DisplayConfig>& hwConfigs =
getHwComposer().getConfigs(type);
for (size_t c = 0; c < hwConfigs.size(); ++c) {
const HWComposer::DisplayConfig& hwConfig = hwConfigs[c];
DisplayInfo info = DisplayInfo();
float xdpi = hwConfig.xdpi;
float ydpi = hwConfig.ydpi;
if (type == DisplayDevice::DISPLAY_PRIMARY) {
// The density of the device is provided by a build property
float density = Density::getBuildDensity() / 160.0f;
if (density == 0) {
// the build doesn't provide a density -- this is wrong!
// use xdpi instead
ALOGE("ro.sf.lcd_density must be defined as a build property");
density = xdpi / 160.0f;
}
if (Density::getEmuDensity()) {
// if "qemu.sf.lcd_density" is specified, it overrides everything
xdpi = ydpi = density = Density::getEmuDensity();
density /= 160.0f;
}
info.density = density;
// TODO: this needs to go away (currently needed only by webkit)
sp<const DisplayDevice> hw(getDefaultDisplayDevice());
info.orientation = hw->getOrientation();
#ifdef MTK_AOSP_ENHANCEMENT
} else if (HWC_DISPLAY_SMARTBOOK == hwc.getSubType(type)) {
static const int SMB_DENSITY = 160;
info.density = SMB_DENSITY / 160.0f;
info.orientation = 0;
#endif
} else {
// TODO: where should this value come from?
static const int TV_DENSITY = 213;
info.density = TV_DENSITY / 160.0f;
info.orientation = 0;
}
info.w = hwConfig.width;
info.h = hwConfig.height;
info.xdpi = xdpi;
info.ydpi = ydpi;
info.fps = float(1e9 / hwConfig.refresh);
info.appVsyncOffset = VSYNC_EVENT_PHASE_OFFSET_NS;
info.presentationDeadline =
hwConfig.refresh - SF_VSYNC_EVENT_PHASE_OFFSET_NS + 1000000;
// All non-virtual displays are currently considered secure.
info.secure = true;
#ifdef MTK_AOSP_ENHANCEMENT
// correct for primary display to normalize graphic plane
if (DisplayDevice::DISPLAY_PRIMARY == type) {
getDefaultDisplayDevice()->correctSizeByHwOrientation(info.w, info.h);
}
#endif
configs->push_back(info);
}
return NO_ERROR;
}
看到了吧,取到的就是之前提到的在SurfaceFlinger怎么获取display信息的。
2,创建新的LocalDisplayDevice对象,并且根据正在使用的参数配置LocalDisplayDevice对象。
LocalDisplayDevice会保留所有的显示屏所支持的配置信息。
特别注意:
mBaseDisplayInfo.rotation = Surface.ROTATION_0;
Surface.ROTATION_0的意思是不转屏,也就是说,如果显示屏配置成了横屏设备,那么Surface.ROTATION_90 就意味着需要转屏90度成为竖屏了。
3,通过DISPLAY_DEVICE_EVENT_ADDED消息告知DMS有新的显示设备添加。
DMS会去处理DISPLAY_DEVICE_EVENT_ADDED消息,并且会去创建一个新的LogicalDisplay
// Adds a new logical display based on the given display device.
// Sends notifications if needed.
private void addLogicalDisplayLocked(DisplayDevice device) {
DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked();
boolean isDefault = (deviceInfo.flags
& DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0;
if (isDefault && mLogicalDisplays.get(Display.DEFAULT_DISPLAY) != null) {
Slog.w(TAG, "Ignoring attempt to add a second default display: " + deviceInfo);
isDefault = false;
}
if (!isDefault && mSingleDisplayDemoMode) {
Slog.i(TAG, "Not creating a logical display for a secondary display "
+ " because single display demo mode is enabled: " + deviceInfo);
return;
}
final int displayId = assignDisplayIdLocked(isDefault);
final int layerStack = assignLayerStackLocked(displayId);
LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
display.updateLocked(mDisplayDevices);
if (!display.isValidLocked()) {
// This should never happen currently.
Slog.w(TAG, "Ignoring display device because the logical display "
+ "created from it was not considered valid: " + deviceInfo);
return;
}
mLogicalDisplays.put(displayId, display);
// Wake up waitForDefaultDisplay.
if (isDefault) {
mSyncRoot.notifyAll();
}
sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
}
在最开始,LogicalDisplay会使用与LocalDisplayDevice同样的显示屏配置信息。同时会为这个LogicalDisplay设备分配displayId 与layerStack,displayId很好理解,每个显示设备就有自己的display id嘛,layerStack是用来干嘛的呢?研究下SurfaceFlinger的源码就能理解,sf会把相同layerStack值的图层composer在一起,丢给display去显示。
在这里可能需要思考下为什么Android需要使用LogicalDisplay呢,这个跟LocalDisplayDevice究竟是什么区别呢?在这里我的理解是LocalDisplayDevice是真实存在,是本质,是一块实实在在的显示设备,不可改变。有具体的宽度,高度等信息。而LogicalDisplay是表象,是能够依托与LocalDisplayDevice,并且能更改的。比如LocalDisplayDevice描述了一个宽度是720,高度是1280的竖屏设备,如果这个设备被默认当做横屏设备使用,那么就应该创建一个高度是720,宽度是1280的横屏LogicalDisplay设备。接下来,我们就要开始深入研究这个了。
WindowManagerService对display的管理
除此之外,Android 在framework中还包装有一个Display 的类作为对DisplayManagerService中display设备的封装。其中Display 类中最重要的成员变量
private DisplayInfo mDisplayInfo;
来自于LogicalDisplay对象中,通过display ID让两者指向同一个显示屏.至于具体这两个对象怎么联系在一起的在这里我不做多介绍,有兴趣的自己去翻源码。
LogicalDisplay类中的getDisplayInfoLocked函数:
public DisplayInfo getDisplayInfoLocked() {
if (mInfo == null) {
mInfo = new DisplayInfo();
mInfo.copyFrom(mBaseDisplayInfo);
if (mOverrideDisplayInfo != null) {
mInfo.appWidth = mOverrideDisplayInfo.appWidth;
mInfo.appHeight = mOverrideDisplayInfo.appHeight;
mInfo.smallestNominalAppWidth = mOverrideDisplayInfo.smallestNominalAppWidth;
mInfo.smallestNominalAppHeight = mOverrideDisplayInfo.smallestNominalAppHeight;
mInfo.largestNominalAppWidth = mOverrideDisplayInfo.largestNominalAppWidth;
mInfo.largestNominalAppHeight = mOverrideDisplayInfo.largestNominalAppHeight;
mInfo.logicalWidth = mOverrideDisplayInfo.logicalWidth;
mInfo.logicalHeight = mOverrideDisplayInfo.logicalHeight;
mInfo.overscanLeft = mOverrideDisplayInfo.overscanLeft;
mInfo.overscanTop = mOverrideDisplayInfo.overscanTop;
mInfo.overscanRight = mOverrideDisplayInfo.overscanRight;
mInfo.overscanBottom = mOverrideDisplayInfo.overscanBottom;
mInfo.rotation = mOverrideDisplayInfo.rotation;
mInfo.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi;
mInfo.physicalXDpi = mOverrideDisplayInfo.physicalXDpi;
mInfo.physicalYDpi = mOverrideDisplayInfo.physicalYDpi;
}
}
return mInfo;
}
注意到mOverrideDisplayInfo,这个比较重要,先标记下,后面会有介绍到。
而在WindowManagerService当中则使用了DisplayContent类间接操作Display类
class DisplayContent {
...................
private final Display mDisplay;
..................
/** * @param display May not be null. * @param service You know. */
DisplayContent(Display display, WindowManagerService service) {
mDisplay = display;
mDisplayId = display.getDisplayId();
display.getDisplayInfo(mDisplayInfo);
isDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY;
mService = service;
}
就这样DisplayContent中的mDisplayInfo将等同与Display中的等同与LogicalDisplay中的。而且DisplayContent中的相关屏幕宽高参数会默认使用LogicalDisplay对象mDisplayInfo中的宽高:
private void displayReady(int displayId) {
synchronized(mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
if (displayContent != null) {
mAnimator.addDisplayLocked(displayId);
synchronized(displayContent.mDisplaySizeLock) {
// Bootstrap the default logical display from the display manager.
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
DisplayInfo newDisplayInfo = mDisplayManagerInternal.getDisplayInfo(displayId);
if (newDisplayInfo != null) {
displayInfo.copyFrom(newDisplayInfo);
}
**displayContent.mInitialDisplayWidth = displayInfo.logicalWidth;
displayContent.mInitialDisplayHeight = displayInfo.logicalHeight;
displayContent.mInitialDisplayDensity = displayInfo.logicalDensityDpi;
displayContent.mBaseDisplayWidth = displayContent.mInitialDisplayWidth;
displayContent.mBaseDisplayHeight = displayContent.mInitialDisplayHeight;**
displayContent.mBaseDisplayDensity = displayContent.mInitialDisplayDensity;
displayContent.mBaseDisplayRect.set(0, 0,
displayContent.mBaseDisplayWidth, displayContent.mBaseDisplayHeight);
}
}
}
}
记得之前在DisplayManagerService中对LogicalDisplay的分析么?其屏幕相关配置参数的初始值等同于物理屏幕的参数。displayContent.mBaseDisplayHeigh与displayContent.mBaseDisplayWidth将会影响到系统对横竖屏参数的初始化:
mPolicy.setInitialDisplaySize(displayContent.getDisplay(),
displayContent.mBaseDisplayWidth,
displayContent.mBaseDisplayHeight,
displayContent.mBaseDisplayDensity);
PhoneWindowsManager是整个Android系统中对显示窗口的策略类,在这里会决定屏幕的旋转与大小.
@Override
public void setInitialDisplaySize(Display display, int width, int height, int density) {
// This method might be called before the policy has been fully initialized
// or for other displays we don't care about.
if (mContext == null || display.getDisplayId() != Display.DEFAULT_DISPLAY) {
return;
}
mDisplay = display;
final Resources res = mContext.getResources();
int shortSize, longSize;
if (width > height) {
shortSize = height;
longSize = width;
mLandscapeRotation = Surface.ROTATION_0;
mSeascapeRotation = Surface.ROTATION_180;
if (res.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)) {
mPortraitRotation = Surface.ROTATION_90;
mUpsideDownRotation = Surface.ROTATION_270;
} else {
mPortraitRotation = Surface.ROTATION_270;
mUpsideDownRotation = Surface.ROTATION_90;
}
} else {
shortSize = width;
longSize = height;
mPortraitRotation = Surface.ROTATION_0;
mUpsideDownRotation = Surface.ROTATION_180;
if (res.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)) {
mLandscapeRotation = Surface.ROTATION_270;
mSeascapeRotation = Surface.ROTATION_90;
} else {
mLandscapeRotation = Surface.ROTATION_90;
mSeascapeRotation = Surface.ROTATION_270;
}
}
这部分的逻辑就是检查宽高值之间的大小,如果宽大于高,则硬件是配置成横屏,那么mLandscapeRotation配置成Surface.ROTATION_0,意思是如果应用强行配置成Landscape模式显示则不需要转屏,mPortraitRotation配置成Surface.ROTATION_270或者Surface.ROTATION_90,意思是应用如果需要竖屏显示,则需要相应的转屏操作。反之如果高大于宽亦然。
接下来我们简单分析下Android下的转屏问题。
Android系统转屏问题
我们开始探讨这个问题之前,我们先假设下我们现在手上拥有一台设备,这台设备的物理尺寸是宽度720像素,高度1280像素, 那么很显然这是一部竖屏设备。那么我们假设现在需要启动一个强制横屏应用的应用程序,那么:
WindowManagerService当中的updateRotationUncheckedLocked最终会被调用:
public boolean updateRotationUncheckedLocked(boolean inTransaction) {
...............................
int rotation = (mIsUpdateIpoRotation || mIsUpdateAlarmBootRotation)
? Surface.ROTATION_0
: mPolicy.rotationForOrientationLw(mForcedAppOrientation, mRotation);
boolean altOrientation = !mPolicy.rotationHasCompatibleMetricsLw(
mForcedAppOrientation, rotation);
...............................
updateDisplayAndOrientationLocked();
}
mForcedAppOrientation 在这里会被置为ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
在PhoneWindowManagerService.java中:
public int rotationForOrientationLw(int orientation, int lastRotation) {
....................
case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
// Return landscape unless overridden.
if (isLandscapeOrSeascape(preferredRotation)) {
return preferredRotation;
}
return mLandscapeRotation;
根据我们之前的分析,由于这原本是一个竖屏设备,那么mLandscapeRotation将等于Surface.ROTATION_90,即等于1.
回到WindowManagerService中来:
DisplayInfo updateDisplayAndOrientationLocked() {
// TODO(multidisplay): For now, apply Configuration to main screen only.
final DisplayContent displayContent = getDefaultDisplayContentLocked();
// Use the effective "visual" dimensions based on current rotation
final boolean rotated = (mRotation == Surface.ROTATION_90
|| mRotation == Surface.ROTATION_270);
final int realdw = rotated ?
displayContent.mBaseDisplayHeight : displayContent.mBaseDisplayWidth;
final int realdh = rotated ?
displayContent.mBaseDisplayWidth : displayContent.mBaseDisplayHeight;
int dw = realdw;
int dh = realdh;
if (mAltOrientation) {
if (realdw > realdh) {
// Turn landscape into portrait.
int maxw = (int)(realdh/1.3f);
if (maxw < realdw) {
dw = maxw;
}
} else {
// Turn portrait into landscape.
int maxh = (int)(realdw/1.3f);
if (maxh < realdh) {
dh = maxh;
}
}
}
// Update application display metrics.
final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation);
final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation);
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
synchronized(displayContent.mDisplaySizeLock) {
displayInfo.rotation = mRotation;
displayInfo.logicalWidth = dw;
displayInfo.logicalHeight = dh;
displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity;
displayInfo.appWidth = appWidth;
displayInfo.appHeight = appHeight;
displayInfo.getLogicalMetrics(mRealDisplayMetrics,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
displayInfo.getAppMetrics(mDisplayMetrics);
if (displayContent.mDisplayScalingDisabled) {
displayInfo.flags |= Display.FLAG_SCALING_DISABLED;
} else {
displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
}
mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
displayContent.getDisplayId(), displayInfo);
displayContent.mBaseDisplayRect.set(0, 0, dw, dh);
}
if (false) {
Slog.i(TAG, "Set app display size: " + appWidth + " x " + appHeight);
}
mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics,
mCompatDisplayMetrics);
return displayInfo;
}
我们会看到mRotation会等于Surface.ROTATION_90,所以有转屏动作,这时会变换屏幕的宽度与高度,并且将最新的宽高信息设置到LogicalDisplay对象中。
final int realdw = rotated ?
displayContent.mBaseDisplayHeight : displayContent.mBaseDisplayWidth;
final int realdh = rotated ?
displayContent.mBaseDisplayWidth : displayContent.mBaseDisplayHeight;
int dw = realdw;
int dh = realdh;
...................
displayInfo.rotation = mRotation;
displayInfo.logicalWidth = dw;
displayInfo.logicalHeight = dh;
displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity;
displayInfo.appWidth = appWidth;
displayInfo.appHeight = appHeight;
........................
mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
displayContent.getDisplayId(), displayInfo);
displayInfo.rotation 会被置为1。这个时候LogicalDisplay中的rotation信息,宽度与高度信息会与LocalDisplayDevice中不一致了。
LogicalDisplay 中的setDisplayInfoOverrideFromWindowManagerLocked函数,设置了mOverrideDisplayInfo,回头想想上面所提到的getDisplayInfoLocked函数。
public boolean setDisplayInfoOverrideFromWindowManagerLocked(DisplayInfo info) {
if (info != null) {
if (mOverrideDisplayInfo == null) {
mOverrideDisplayInfo = new DisplayInfo(info);
mInfo = null;
return true;
}
if (!mOverrideDisplayInfo.equals(info)) {
mOverrideDisplayInfo.copyFrom(info);
mInfo = null;
return true;
}
} else if (mOverrideDisplayInfo != null) {
mOverrideDisplayInfo = null;
mInfo = null;
return true;
}
return false;
}
与此同时,WindowManagerService会更新最新的Configure配置信息:
void computeScreenConfigurationLocked(Configuration config) {
final DisplayInfo displayInfo = updateDisplayAndOrientationLocked();
...............
final DisplayInfo displayInfo = updateDisplayAndOrientationLocked();
final int dw = displayInfo.logicalWidth;
final int dh = displayInfo.logicalHeight;
config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT :
Configuration.ORIENTATION_LANDSCAPE;
.........................
mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
这段代码里会调用上面有提到的updateDisplayAndOrientationLocked函数更新displayInfo信息,进而生成新的Configuration,之后会将Configuration发生出去,而这时一般情况下应用程序会收到转屏消息,应用会重新获取屏幕的宽高再重新绘制一遍。这里的屏幕的宽高指的是LogicalDisplay的。
而每次刷新屏幕的时候LogicalDisplay的configureDisplayInTransactionLocked会被调用:
public void configureDisplayInTransactionLocked(DisplayDevice device,
boolean isBlanked) {
..................
// Only grab the display info now as it may have been changed based on the requests above.
//获取LogicalDisplay的最新屏幕信息,见上面分析
final DisplayInfo displayInfo = getDisplayInfoLocked();
//获取LocalDisplayDevice的物理屏幕信息
final DisplayDeviceInfo displayDeviceInfo = device.getDisplayDeviceInfoLocked();
............................
int orientation = Surface.ROTATION_0;
if ((displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0) {
//设置与LogicalDisplay的转屏信息,本例子里肯定为1.
orientation = displayInfo.rotation;
}
// Apply the physical rotation of the display device itself.
//求余计算,结果依然为1嘛。。。
orientation = (orientation + displayDeviceInfo.rotation) % 4;
boolean rotated = (orientation == Surface.ROTATION_90
|| orientation == Surface.ROTATION_270);
// 物理屏宽高参数修改,这是为啥。
int physWidth = rotated ? displayDeviceInfo.height : displayDeviceInfo.width;
int physHeight = rotated ? displayDeviceInfo.width : displayDeviceInfo.height;
// 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) {
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;
}
/// M: Enable anti-overscan capability on wifi display @{
if (displayDeviceInfo.type == Display.TYPE_WIFI) {
displayRectWidth = (int) (displayRectWidth * ANTI_OVERSCAN_RATIO);
displayRectHeight = (int) (displayRectHeight * ANTI_OVERSCAN_RATIO);
}
/// @}
int displayRectTop = (physHeight - displayRectHeight) / 2;
int displayRectLeft = (physWidth - displayRectWidth) / 2;
mTempDisplayRect.set(displayRectLeft, displayRectTop,
displayRectLeft + displayRectWidth, displayRectTop + displayRectHeight);
mTempDisplayRect.left += mDisplayOffsetX;
mTempDisplayRect.right += mDisplayOffsetX;
mTempDisplayRect.top += mDisplayOffsetY;
mTempDisplayRect.bottom += mDisplayOffsetY;
//将转屏信息,显示范围最终设置到SurfaceFlinger当中
device.setProjectionInTransactionLocked(orientation, mTempLayerStackRect, mTempDisplayRect);
}
好了,这个就先到这里了,后面可以再写下两年前在Android 4.4上实现双屏幕的思路。