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(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 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

  1. LocalDisplayAdapter是针对本地已经存在的物理显示屏设备。
  2. WifiDisplayAdapter针对WiFi Display
  3. OverlayDisplayAdapter 这个还没有来得及看
  4. 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& display,        Vector* configs) {    ..................    ..................    configs->clear();    const Vector& 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上实现双屏幕的思路。

更多相关文章

  1. Android系统设计中存在设计模式分析
  2. android图形系统详解二:Drawables
  3. 基于Android(安卓)5.1系统的nfc读卡驱动和上层的调试记录,nfc移植
  4. Android(安卓)View视图系统分析和Scroller和OverScroller分析
  5. Android系统布局——android.R.layout详解
  6. windows系统上安装与使用Android(安卓)NDK r5
  7. Android系统架构基本模式解析
  8. 深入浅出 - Android系统移植与平台开发(九)- JNI介绍
  9. Android(安卓)Debug Bridge

随机推荐

  1. Android(安卓)EditText获取焦点后只显示
  2. Android(安卓)DialogFragment(2)
  3. Android(安卓)中的 ViewPager+ Fragment
  4. 配置dialog无标题的几种方法
  5. Android程序如何实现换肤?
  6. android定位服务
  7. 第一个开源的Android项目
  8. Android(安卓)Studio导入Android系统源码
  9. Android入门教程之ListView的应用示例
  10. 自定义按钮样式