赞
踩
大概两年前做过一个项目,大致是在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 启动开始,我们知道在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);
大胆的猜测下,android中是否会开始支持分辨率的动态调整了呢?从以为的经验来说,一个手机在出厂的时候就固定好了分辨率,后续是不是能像windows 系统一样能动态调整呢?
我们记一下,显示屏的相关参数被保留在mDisplayData[disp]中。SurfaceFlinger中的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.
- @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设备。接下来,我们就要开始深入研究这个了。
除此之外,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下的转屏问题。
我们开始探讨这个问题之前,我们先假设下我们现在手上拥有一台设备,这台设备的物理尺寸是宽度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上实现双屏幕的思路。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。