我们之前有一款产品,显示屏时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效果问题