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效果問題