我们之前有一款产品,显示屏时4.2寸的,但是屏幕模组是4.0寸的,导致显示的时候,Android系统状态栏有很小的一部分被遮住了,显示不全。
就想着能不能修改系统默认显示大小,解决这个问题。
平时大家调试app适配的时候,经常会使用wm工具

wm size可以查看当前屏幕分辨率,也可以设置屏幕分辨率(当然也就一般调试问题wm size)。

eg: wm size 720x1280

这是我们当前屏幕的分辨率。
wm density [reset|DENSITY]
该命令的用法类似于wm size 命令,作用是读取、设置或者重置LCD的density值。density值即LCD的ppi.

Physical density: 320
wm overscan [reset|LEFT,TOP,RIGHT,BOTTOM]

该命令用来设置、重置LCD的显示区域。四个参数分别是显示边缘距离LCD左、上、右、下的像素数。例如,对于分辨率为540x960的屏幕,通过执行 命令wm overscan 0,0,0,420可将显示区域限定在一个540x540的矩形框里。

如下图所示:

android 命令设置 分辨率 安卓修改显示分辨率_android 命令设置 分辨率


通过 wm overscan 微调,发现wm overscan 0,10,0,0时候正好可以解决系统状态栏被遮挡的问题,

这个方法只是解决了临时的显示区域修改。如果想开机启动就修改显示区域,或者永久修改显示区域,就需要修改系统源码。

开始代码追踪

在wm.java文件中

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 if (op.equals("scaling")) {
            runDisplayScaling();
        } else if (op.equals("screen-capture")) {
            runSetScreenCapture();
        } else if (op.equals("dismiss-keyguard")) {
            runDismissKeyguard();
        } else {
            showError("Error: unknown command '" + op + "'");
            return;
        }
    }

函数 runDisplayOverscan()

private void runDisplayOverscan() throws Exception {
        String overscanStr = nextArgRequired();
        Rect rect = new Rect();
        if ("reset".equals(overscanStr)) {
            rect.set(0, 0, 0, 0);
        } else {
            final Pattern FLATTENED_PATTERN = Pattern.compile(
                    "(-?\\d+),(-?\\d+),(-?\\d+),(-?\\d+)");
            Matcher matcher = FLATTENED_PATTERN.matcher(overscanStr);
            if (!matcher.matches()) {
                System.err.println("Error: bad rectangle arg: " + overscanStr);
                return;
            }
            rect.left = Integer.parseInt(matcher.group(1));
            rect.top = Integer.parseInt(matcher.group(2));
            rect.right = Integer.parseInt(matcher.group(3));
            rect.bottom = Integer.parseInt(matcher.group(4));
        }

        try {
            mWm.setOverscan(Display.DEFAULT_DISPLAY, rect.left, rect.top, rect.right, rect.bottom);
        } catch (RemoteException e) {
        }
    }

WindowManagerService

public void setOverscan(int displayId, int left, int top, int right, int bottom) {
        if (mContext.checkCallingOrSelfPermission(
                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
                PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Must hold permission " +
                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
        }
        final long ident = Binder.clearCallingIdentity();
        try {
            synchronized(mWindowMap) {
                DisplayContent displayContent = getDisplayContentLocked(displayId);
                if (displayContent != null) {
                    setOverscanLocked(displayContent, left, top, right, bottom);
                }
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

首先来检查执行方法的类是否有足够的权限,这里必须有android.Manifest.permission.WRITE_SECURE_SETTINGS的权限才能调用该方法。
接着就是获得DisplayContent对象,这里我们传入的displayId就是Display.DEFAULT_DISPLAY,值为0,其实对于手机来说只有一块屏幕,所以就是DEFAULT_DISPLAY。当displayContent不为null的时候,调用WmS的setOverscanLocked()私有方法继续进行设置。

private void setOverscanLocked(DisplayContent displayContent,
            int left, int top, int right, int bottom) {
        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
        synchronized (displayContent.mDisplaySizeLock) {
            displayInfo.overscanLeft = left;
            displayInfo.overscanTop = top;
            displayInfo.overscanRight = right;
            displayInfo.overscanBottom = bottom;
        }

        mDisplaySettings.setOverscanLocked(displayInfo.uniqueId, left, top, right, bottom);
        mDisplaySettings.writeSettingsLocked();

        reconfigureDisplayLocked(displayContent);
    }

该方法中做了三件事情:

调用displayContent的getDisplayInfo()方法获得DisplayInfo对象,然后根据传入的overscan的值,设置DisplayInfo中的响应的值。
调用mDisplaySettings的setOverscanLocked()方法,将overscan的值保存成一条名字为diaplayInfo.name的记录,通过这个名字,我们就可以从mDisplaySettings中读出该DisplayContent的overscan的内容。接着调用那个mDisplaySettings的writeSettingsLocked()方法,将刚才设置的内容写入到display_settings.xml设置文件中。
display_settings.xml 文件位置
/data/system/display_settings.xml

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<display-settings>
<display name="local:0" overscanTop="10" />
</display-settings>

调用reconfigureDisplayLocked(displayContent)方法,进一步更新DisplayContent的内容,然后调用

performLayoutAndPlaceSurfacesLocked()方法对屏幕进行绘制

DisplaySettings.java 文件中的

public void setOverscanLocked(String name, int left, int top, int right, int bottom) {
        if (left == 0 && top == 0 && right == 0 && bottom == 0) {
            // Right now all we are storing is overscan; if there is no overscan,
            // we have no need for the entry.
            mEntries.remove(name);
            return;
        }
        Entry entry = mEntries.get(name);
        if (entry == null) {
            entry = new Entry(name);
            mEntries.put(name, entry);
        }
        entry.overscanLeft = left;
        entry.overscanTop = top;
        entry.overscanRight = right;
        entry.overscanBottom = bottom;
    }

所以修改writeSettingsLocked 方法才能真正的处理固化我们显示区域的代码。
现在如果想让系统第一次开机启动就显示4.0效果,需要在系统启动时候就要把4.0相关属性设置完毕。
Android 系统服务启动
SystemService.java

private void startOtherServices() {
    ...
    // [见2.2]
    WindowManagerService wm = WindowManagerService.main(context, inputManager,
    mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
    !mFirstBoot, mOnlyCore);

    wm.displayReady(); 
    ...
    wm.systemReady(); 
}

WindowManagerService.java

public static WindowManagerService main(final Context context,
            final InputManagerService im,
            final boolean haveInputMethods, final boolean showBootMsgs,
            final boolean onlyCore) {
        final WindowManagerService[] holder = new WindowManagerService[1];
        DisplayThread.getHandler().runWithScissors(new Runnable() {
            @Override
            public void run() {
           
                holder[0] = new WindowManagerService(context, im,
                        haveInputMethods, showBootMsgs, onlyCore);
            }
        }, 0);
        return holder[0];
    }
private WindowManagerService(Context context, InputManagerService inputManager,
        boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
        WindowManagerPolicy policy) {
    mRoot = new RootWindowContainer(this);
    mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
    mWindowPlacerLocked = new WindowSurfacePlacer(this);
    ...
    //获得 DisplayManager
    mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
    //通过DisplayManager去获得Displays
    mDisplays = mDisplayManager.getDisplays();
    for (Display display : mDisplays) {
        createDisplayContentLocked(display);
    }
}
private DisplayContent createDisplayContent(final Display display) {
    //生成一个新的DisplayContent
    final DisplayContent dc = new DisplayContent(display, mService, mLayersController,
            mWallpaperController);
    final int displayId = display.getDisplayId();

    final DisplayInfo displayInfo = dc.getDisplayInfo();
    final Rect rect = new Rect();
    //获得Overscan的区域, 这个是配置的 /data/system/display_settings.xml
    mService.mDisplaySettings.getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect);
    displayInfo.overscanLeft = rect.left;
    displayInfo.overscanTop = rect.top;
    displayInfo.overscanRight = rect.right;
    displayInfo.overscanBottom = rect.bottom;
    if (mService.mDisplayManagerInternal != null) {
         //这里可能会设置 mOverrideDisplayInfo 
        mService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
                displayId, displayInfo);
        mService.configureDisplayPolicyLocked(dc);

        // TODO(multi-display): Create an input channel for each display with touch capability.
        if (displayId == DEFAULT_DISPLAY && mService.canDispatchPointerEvents()) {
            dc.mTapDetector = new TaskTapPointerEventListener(
                    mService, dc);
            mService.registerPointerEventListener(dc.mTapDetector);
            mService.registerPointerEventListener(mService.mMousePositionTracker);
        }
    }

    return dc;
}

而/data/system/display_settings.xml 这个文件只有 在wm size 、wm density 和 wm overscan 改变系统熟悉之后才会存在,

public void getOverscanLocked(String name, String uniqueId, Rect outRect) {
        // Try to get the entry with the unique if possible.
        // Else, fall back on the display name.
        Entry entry;
        if (uniqueId == null || (entry = mEntries.get(uniqueId)) == null) {
            entry = mEntries.get(name);
        }
        if (entry != null) {//如果有改变系统属性会走到这个分支
            outRect.left = entry.overscanLeft;
            outRect.top = 10;
            outRect.right = entry.overscanRight;
            outRect.bottom = entry.overscanBottom;
        } else {//系统第一次启动一定走到目前分支。
            outRect.set(0, 10, 0, 0);
        }
    }

完美解决系统4.2 显示4.0效果问题