转:https://www.brobwind.com/archives/514

ANDROID: 设置显示窗口的SIZE和DENSITY

在做Android app开发的时候,为了验证不同屏幕分辨率和dpi下界面的布局情况。你可以使用android emulator来实现,也可以找不同屏幕配置的手机来验证。当然,你可以找台Android原生系统的手机来验证如Nexus 4/Nexus 5系列的手机来验证。Android系统中有一个wm命令,可以设置显示窗口的尺寸(重新设置屏幕的罗辑分辩率)和屏幕的dpi。

  • 设置显示窗口的尺寸
$ adb shell wm size 540x960

显示窗口的尺寸可以比屏幕的物理分辨率大,也可以比它小。重置用如下命令:

$ adb shell wm size reset

设置完之后,你会发现SurfaceFlinger中的Layer的尺寸也发生的变化。

以Nexus4为例,原来Live Wallpaper: PhaseBeam Layer的尺寸为768×1280:

$ adb shell dumpsys SurfaceFlinger...+ Layer 0xb7bbf3b0 (com.android.phasebeam.PhaseBeamWallpaper)  Region transparentRegion (this=0xb7bbf510, count=1)    [  0,   0,   0,   0]  Region visibleRegion (this=0xb7bbf3b8, count=1)    [  0,  50, 768, 1184]      layerStack=   0, z=    21000, pos=(0,0), size=( 768,1280), crop=(   0,  50, 768,1184), isOpaque=1, invalidate=0, alpha=0xff, flags=0x00000002, tr=[1.00, 0.00][0.00, 1.00]      client=0xb7bad728      format= 2, activeBuffer=[ 768x1280: 768,  3], queued-frames=0, mRefreshPending=0            mTexName=10 mCurrentTexture=0            mCurrentCrop=[0,0,0,0] mCurrentTransform=0            mAbandoned=0            -BufferQueue mMaxAcquiredBufferCount=1, mDequeueBufferCannotBlock=0, default-size=[768x1280], default-format=2, transform-hint=00, FIFO(0)={}            >[00:0xb7bbef08] state=ACQUIRED, 0xb7b18260 [ 768x1280: 768,  3]             [01:0xb7baf328] state=FREE    , 0xb7badaa8 [ 768x1280: 768,  3]             [02:0xb7b1a5d0] state=FREE    , 0xb7bad9a0 [ 768x1280: 768,  3]...

将显示窗口的尺寸设为1080×1920之后:

$ adb shell dumpsys SurfaceFlinger...+ Layer 0xb7d596b8 (com.android.phasebeam.PhaseBeamWallpaper)  Region transparentRegion (this=0xb7d59818, count=1)    [  0,   0,   0,   0]  Region visibleRegion (this=0xb7d596c0, count=1)    [  0,  50, 1080, 1824]      layerStack=   0, z=    21000, pos=(0,0), size=(1080,1920), crop=(   0,  50,1080,1824), isOpaque=1, invalidate=0, alpha=0xff, flags=0x00000002, tr=[1.00, 0.00][0.00, 1.00]      client=0xb7dbf270      format= 2, activeBuffer=[1080x1920:1152,  3], queued-frames=0, mRefreshPending=0            mTexName=10 mCurrentTexture=2            mCurrentCrop=[0,0,0,0] mCurrentTransform=0            mAbandoned=0            -BufferQueue mMaxAcquiredBufferCount=1, mDequeueBufferCannotBlock=0, default-size=[1080x1920], default-format=2, transform-hint=00, FIFO(0)={}             [00:0xb7dd1fb0] state=FREE    , 0xb7dd0048 [1080x1920:1152,  3]             [01:0xb7d5c2c8] state=FREE    , 0xb7d39ca8 [1080x1920:1152,  3]            >[02:0xb7d1d578] state=ACQUIRED, 0xb7d5cb18 [1080x1920:1152,  3]...

可以想象得到,系统把这个layer对应的窗口当成了一个1080p屏幕来布局和绘制了。

  • 设置屏幕的dpi
$ adb shell wm density 320

常用的dpi有160(mdpi), 240(hdpi), 320(xhdpi), 480(xxhdpi)。重置可用如下命令:

$ adb shell wm density reset
  • 如何实现 ,以设置显示窗口尺寸为例(@android-5.1.1)

首先看一下wm命令如何通知WindowManagerService更新配置:

代码:frameworks/base/cmds/wm/src/com/android/commands/wm/Wm.java

public class Wm extends BaseCommand {    ...    public void onRun() throws Exception {        mWm = IWindowManager.Stub.asInterface(ServiceManager.checkService(                        Context.WINDOW_SERVICE));        if (mWm == null) {            System.err.println(NO_SYSTEM_ERROR_CODE);            throw new AndroidException("Can't connect to window manager; is the system running?");        }        String op = nextArgRequired();        if (op.equals("size")) {            runDisplaySize();        } else if (op.equals("density")) {            runDisplayDensity();        } else if (op.equals("overscan")) {            runDisplayOverscan();        } else {            showError("Error: unknown command '" + op + "'");            return;        }    }    ...    private void runDisplaySize() throws Exception {        String size = nextArg();        int w, h;        if (size == null) {            Point initialSize = new Point();            Point baseSize = new Point();            try {                mWm.getInitialDisplaySize(Display.DEFAULT_DISPLAY, initialSize);                mWm.getBaseDisplaySize(Display.DEFAULT_DISPLAY, baseSize);                System.out.println("Physical size: " + initialSize.x + "x" + initialSize.y);                if (!initialSize.equals(baseSize)) {                    System.out.println("Override size: " + baseSize.x + "x" + baseSize.y);                }            } catch (RemoteException e) {            }            return;        } else if ("reset".equals(size)) {            w = h = -1;        } else {            int div = size.indexOf('x');            if (div <= 0 || div >= (size.length()-1)) {                System.err.println("Error: bad size " + size);                return;            }            String wstr = size.substring(0, div);            String hstr = size.substring(div+1);            try {                w = Integer.parseInt(wstr);                h = Integer.parseInt(hstr);            } catch (NumberFormatException e) {                System.err.println("Error: bad number " + e);                return;            }        }        try {            if (w >= 0 && h >= 0) {                // TODO(multidisplay): For now Configuration only applies to main screen.                mWm.setForcedDisplaySize(Display.DEFAULT_DISPLAY, w, h);            } else {                mWm.clearForcedDisplaySize(Display.DEFAULT_DISPLAY);            }        } catch (RemoteException e) {        }    }    ...}

先获取WindowManagerService再调用它的setForcedDisplaySize()方法。

代码:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

public class WindowManagerService extends IWindowManager.Stub        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {    ...    @Override    public void setForcedDisplaySize(int displayId, int width, int height) {        if (mContext.checkCallingOrSelfPermission(                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=                PackageManager.PERMISSION_GRANTED) {            throw new SecurityException("Must hold permission " +                    android.Manifest.permission.WRITE_SECURE_SETTINGS);        }        if (displayId != Display.DEFAULT_DISPLAY) {            throw new IllegalArgumentException("Can only set the default display");        }        final long ident = Binder.clearCallingIdentity();        try {            synchronized(mWindowMap) {                // Set some sort of reasonable bounds on the size of the display that we                // will try to emulate.                final int MIN_WIDTH = 200;                final int MIN_HEIGHT = 200;                final int MAX_SCALE = 2;                final DisplayContent displayContent = getDisplayContentLocked(displayId);                if (displayContent != null) {                    width = Math.min(Math.max(width, MIN_WIDTH),                            displayContent.mInitialDisplayWidth * MAX_SCALE);                    height = Math.min(Math.max(height, MIN_HEIGHT),                            displayContent.mInitialDisplayHeight * MAX_SCALE);                    setForcedDisplaySizeLocked(displayContent, width, height);                    Settings.Global.putString(mContext.getContentResolver(),                            Settings.Global.DISPLAY_SIZE_FORCED, width + "," + height);                }            }        } finally {            Binder.restoreCallingIdentity(ident);        }    }    ...    // displayContent must not be null    private void setForcedDisplaySizeLocked(DisplayContent displayContent, int width, int height) {        Slog.i(TAG, "Using new display size: " + width + "x" + height);        synchronized(displayContent.mDisplaySizeLock) {            displayContent.mBaseDisplayWidth = width;            displayContent.mBaseDisplayHeight = height;        }        reconfigureDisplayLocked(displayContent);    }    ...    // displayContent must not be null    private void reconfigureDisplayLocked(DisplayContent displayContent) {        // TODO: Multidisplay: for now only use with default display.        configureDisplayPolicyLocked(displayContent);        displayContent.layoutNeeded = true;        boolean configChanged = updateOrientationFromAppTokensLocked(false);        mTempConfiguration.setToDefaults();        mTempConfiguration.fontScale = mCurConfiguration.fontScale;        if (computeScreenConfigurationLocked(mTempConfiguration)) {            if (mCurConfiguration.diff(mTempConfiguration) != 0) {                configChanged = true;            }        }        if (configChanged) {            mWaitingForConfig = true;            startFreezingDisplayLocked(false, 0, 0);            mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);        }        performLayoutAndPlaceSurfacesLocked();    }    ...}

setForcedDisplaySize()->setForcedDisplaySizeLocked()->reconfigureDisplayLocked()->SEND_NEW_CONFIGURATION

这里可以看到可以设置的屏幕窗口最小为(200,200),最大小显示屏分辨率的两倍。

而WindowManagerService又是如何通知DisplayManagerService更新配置:

WindowsManagerService$H.handleMessage(SEND_NEW_CONFIGURATION)

->WindowManagerService.sendNewConfiguration()

->ActivityManagerService.updateConfiguration() x2

->WindowManagerService.setNewConfiguration()

->WindowManagerService.performLayoutAndPlaceSurfacesLocked()

->WindowManagerService.performLayoutAndPlaceSurfaceLockedLoop()

->WindowManagerService.performLayoutAndPlaceSurfaceLockedInner()

->DisplayManagerService$LocalService.performTraversalInTransactionFromWindowManager()

->DisplayManagerService.access$4200()

->DisplayManagerService.performTraversalInTransactionFromWindowManagerInternal()

->DisplayManagerService.performTraversalInTransactionLocked()

->DisplayManagerService.configureDisplayInTransactionLocked()

->LogicalDisplay.configureDisplayInTransactionLocked()

到这里就会去设置layer stack的尺寸和在屏幕上的位置:

final class LogicalDisplay {    ...    /**     * Applies the layer stack and transformation to the given display device     * so that it shows the contents of this logical display.     *     * We know that the given display device is only ever showing the contents of     * a single logical display, so this method is expected to blow away all of its     * transformation properties to make it happen regardless of what the     * display device was previously showing.     *     * The caller must have an open Surface transaction.     *     * The display device may not be the primary display device, in the case     * where the display is being mirrored.     *     * @param device The display device to modify.     * @param isBlanked True if the device is being blanked.     */    public void configureDisplayInTransactionLocked(DisplayDevice device,            boolean isBlanked) {        final DisplayInfo displayInfo = getDisplayInfoLocked();        final DisplayDeviceInfo displayDeviceInfo = device.getDisplayDeviceInfoLocked();        // Set the layer stack.        device.setLayerStackInTransactionLocked(isBlanked ? BLANK_LAYER_STACK : mLayerStack);        // Set the refresh rate        device.requestRefreshRateLocked(mRequestedRefreshRate);        // Set the viewport.        // This is the area of the logical display that we intend to show on the        // display device.  For now, it is always the full size of the logical display.        mTempLayerStackRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);        // Set the orientation.        // The orientation specifies how the physical coordinate system of the display        // is rotated when the contents of the logical display are rendered.        int orientation = Surface.ROTATION_0;        if ((displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0) {            orientation = displayInfo.rotation;        }        // Apply the physical rotation of the display device itself.        orientation = (orientation + displayDeviceInfo.rotation) % 4;        // Set the frame.        // The frame specifies the rotated physical coordinates into which the viewport        // is mapped.  We need to take care to preserve the aspect ratio of the viewport.        // Currently we maximize the area to fill the display, but we could try to be        // more clever and match resolutions.        boolean rotated = (orientation == Surface.ROTATION_90                || orientation == Surface.ROTATION_270);        int physWidth = rotated ? displayDeviceInfo.height : displayDeviceInfo.width;        int physHeight = rotated ? displayDeviceInfo.width : displayDeviceInfo.height;        // 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 (physWidth * displayInfo.logicalHeight                < physHeight * displayInfo.logicalWidth) {            // Letter box.            displayRectWidth = physWidth;            displayRectHeight = displayInfo.logicalHeight * physWidth / displayInfo.logicalWidth;        } else {            // Pillar box.            displayRectWidth = displayInfo.logicalWidth * physHeight / displayInfo.logicalHeight;            displayRectHeight = physHeight;        }        int displayRectTop = (physHeight - displayRectHeight) / 2;        int displayRectLeft = (physWidth - displayRectWidth) / 2;        mTempDisplayRect.set(displayRectLeft, displayRectTop,                displayRectLeft + displayRectWidth, displayRectTop + displayRectHeight);        device.setProjectionInTransactionLocked(orientation, mTempLayerStackRect, mTempDisplayRect);    }    ...}

->DisplayDevice.setProjectionInTransactionLocked()

通知SurfaceFlinger:

abstract class DisplayDevice {    ...    /**     * Sets the display projection while in a transaction.     *     * @param orientation defines the display's orientation     * @param layerStackRect defines which area of the window manager coordinate     *            space will be used     * @param displayRect defines where on the display will layerStackRect be     *            mapped to. displayRect is specified post-orientation, that is     *            it uses the orientation seen by the end-user     */    public final void setProjectionInTransactionLocked(int orientation,            Rect layerStackRect, Rect displayRect) {        if (mCurrentOrientation != orientation                || mCurrentLayerStackRect == null                || !mCurrentLayerStackRect.equals(layerStackRect)                || mCurrentDisplayRect == null                || !mCurrentDisplayRect.equals(displayRect)) {            mCurrentOrientation = orientation;            if (mCurrentLayerStackRect == null) {                mCurrentLayerStackRect = new Rect();            }            mCurrentLayerStackRect.set(layerStackRect);            if (mCurrentDisplayRect == null) {                mCurrentDisplayRect = new Rect();            }            mCurrentDisplayRect.set(displayRect);            SurfaceControl.setDisplayProjection(mDisplayToken,                    orientation, layerStackRect, displayRect);        }    }    ...}

最后,SurfaceFlinger就会跟据这些信息对Layer进行合成,显示在屏幕上。

代码:frameworks/native/services/surfaceflinger/DisplayDevice.cpp

void DisplayDevice::setProjection(int orientation,        const Rect& newViewport, const Rect& newFrame) {    Rect viewport(newViewport);    Rect frame(newFrame);    const int w = mDisplayWidth;    const int h = mDisplayHeight;    Transform R;    DisplayDevice::orientationToTransfrom(orientation, w, h, &R);    if (!frame.isValid()) {        // the destination frame can be invalid if it has never been set,        // in that case we assume the whole display frame.        frame = Rect(w, h);    }    if (viewport.isEmpty()) {        // viewport can be invalid if it has never been set, in that case        // we assume the whole display size.        // it's also invalid to have an empty viewport, so we handle that        // case in the same way.        viewport = Rect(w, h);        if (R.getOrientation() & Transform::ROT_90) {            // viewport is always specified in the logical orientation            // of the display (ie: post-rotation).            swap(viewport.right, viewport.bottom);        }    }    dirtyRegion.set(getBounds());    Transform TL, TP, S;    float src_width  = viewport.width();    float src_height = viewport.height();    float dst_width  = frame.width();    float dst_height = frame.height();    if (src_width != dst_width || src_height != dst_height) {        float sx = dst_width  / src_width;        float sy = dst_height / src_height;        S.set(sx, 0, 0, sy);    }    float src_x = viewport.left;    float src_y = viewport.top;    float dst_x = frame.left;    float dst_y = frame.top;    TL.set(-src_x, -src_y);    TP.set(dst_x, dst_y);    // The viewport and frame are both in the logical orientation.    // Apply the logical translation, scale to physical size, apply the    // physical translation and finally rotate to the physical orientation.    mGlobalTransform = R * TP * S * TL;    const uint8_t type = mGlobalTransform.getType();    mNeedsFiltering = (!mGlobalTransform.preserveRects() ||            (type >= Transform::SCALE));    mScissor = mGlobalTransform.transform(viewport);    if (mScissor.isEmpty()) {        mScissor = getBounds();    }    mOrientation = orientation;    mViewport = viewport;    mFrame = frame;}
  • 在三星A9上将屏幕的density由原来的480更改为320,重启后发现显示的效果不太好:
  1. 三星Launcher的主页只在屏幕的上半部显示
  2. camera的界面只显示在了屏幕的右上方
  3. 输入法界面也显示不正常
  4. 常用的应用如计算器、收音机、智能管理器。。。好多应用布局都有问题。

看来还是google原生系统处理得比较好,系在统重启之后,还没有找到有问题的布局(bootanimation会显示不正常)。


更多相关文章

  1. 一种粗暴快速的 Android 全屏幕适配方案
  2. Android屏幕适配 重点盘点
  3. Android中切换屏幕方向时Activity生命周期函数执行情况分析
  4. Android开发:最全面、最易懂的Android屏幕适配解决方案
  5. Android主流屏幕分辨率介绍
  6. android Activity实现从底部弹出或滑出选择菜单或窗口
  7. Android 4.4(KitKat)窗口管理子系统 - 体系框架
  8. Android 屏幕旋转生命周期以及处理方法

随机推荐

  1. Android模块化开发--路由ARouter日常使用
  2. android 入门 001 (界面布局)
  3. YUV420P的格式以及转换为RGB565的代码(And
  4. android CheckBox RadioButton 照片和文
  5. Android(安卓)安全提示 笔记
  6. Android工程Debug证书过期解决方案 -- ec
  7. Windows环境下Android(安卓)Studio系列3
  8. Android(安卓)Studio使用gradle-experime
  9. Android(安卓)数据库框架GreenDao与LiteP
  10. android UiAutomator利用am命令启动和结