Android Display 系统分析

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);

大胆的猜测下,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<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上实现双屏幕的思路。

    原文作者:影子LEON
    原文地址: https://blog.csdn.net/ljp1205/article/details/53405641
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞