转: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 EditText设置不可写
  2. Android屏幕分辨率正确获取及PX,DPI,DP,SP等的对应关系
  3. Android系统配置数据库注释(settings.db)
  4. android“设置”里的版本号
  5. 在Fragment中设置控件点击方法,执行失败。
  6. Android(安卓)闹钟管理类的使用
  7. Android设置通知栏/状态栏透明改变通知栏颜色和app最上部分颜色
  8. android 设置中划线 下划线等
  9. Andorid Dialog 示例【慢慢更新】

随机推荐

  1. Mssql根据表名获取字段
  2. 这被认为是正常形式的失败吗?
  3. 建议一种有效的查询方式
  4. Code First for Mysql 错误:未为提供程序
  5. sql语句,order by后加参数问题
  6. mybatis中 insert into select 批量生成u
  7. ORA-19909:当进行不完全恢复之后使用open
  8. sql 2005判断某个表或某个表中的列是否存
  9. MySQL中的类Decode用法
  10. 如何优化用于从表复制数据的oracle过程?