1. 程式人生 > >Android 4.2寸螢幕顯示4.0的效果

Android 4.2寸螢幕顯示4.0的效果

我們之前有一款產品,顯示屏時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的矩形框裡。

如下圖所示:
在這裡插入圖片描述
通過 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效果問題