第二节:wm命令的实现方式分析
wm命令是在Wm.java文件中实现的。我们来分析一下执行的过程:
通过上述的可以看出是在adb shell中调用wm命令,可以知道wm是一个可执行文件,那么对于java类来说入口方法时main()方法。所以来看Wm类的main()方法如下:
public static void main(String[] args) { (new Wm()).run(args); }
使用Wm类的默认构造方法创建了一个Wm的实例,然后调用run()方法继续执行,将命令行传入的参数args一并传入。
由于run()方法是从Wm的父类BaseCommand继承来的,代码如下。
下面来看run()方法的内容:
public void run(String[] args) { …… onRun(); ……. }
经过一些对参数的验证,最后调用Wm实现的onRun()方法,代码如下:
public void onRun() throws Exception { mWm = IWindowManager.Stub.asInterface(ServiceManager.checkService( Context.WINDOW_SERVICE)); ……. String op = nextArgRequired(); if (op.equals("size")) { runDisplaySize(); } else if (op.equals("density")) { runDisplayDensity(); } else if (op.equals("overscan")) { runDisplayOverscan(); } else { ……. } }
从前面章节对wm命令的介绍,都是对屏幕的进行操作的,必然涉及到WmS的操作,所以这里要获得WmS的代理对象,赋值给mWm变量,后续的我们要调用WmS的方法,都是通过mWm来调用的。
接下来通过nextArgRequired()获得命令字符串,然后根据命令字符串,执行不同的命令。
注意:这里使用了多个if语句,其实在JDK7开始,switch的条件语句已经可以使用字符串了。
根据命令的不同分别调用runDisplaySize()、runDisplayDensity()、runDisplayOverscan()方法,见名知义,所以这里不赘述。我们以runDisplayOverscan()方法为例,进行分析,其他的类似,最后都是通过mWm调用WmS的某个方法,对DisplayContent进行设置。
下面我们上该方法的代码:
private void runDisplayOverscan() throws Exception { String overscanStr = nextArgRequired(); Rect rect = new Rect(); int density; 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) { } }
1. 获得命令的第一个参数,如果是resize,就意味着要恢复到原始的overscan的值,看到给rect矩形变量设置了四个0,这意味着设备原始是没有overscan值的。
2. 如果第一个参数不是resize,就意味着要对overscan进行设置,但是从wm overscan的参数模式可以看出是四个使用逗号(,)的整数值,可以是负整数值。这里使用正则表达式,将该参数转换成一个数组放到到matcher中,接着依次取出,分别赋值给rect的left,top,right,bottom的值。
3.当执行以上两步后,我们已经给rect矩形变量设置完值了,接下来就是告诉WmS,可以根据这些值,更新屏幕的overscan的值了。这里调用WmS的setOverscan()方法,把rect的四个坐标值传入。
注意:这里仅仅使用rect来存储left,top,right,bottom的值,并不代表overcan是一个矩形区域,其实overscan代表的是不绘制屏幕内容的区域。如下图中的浅橙色所示:
图2-1 屏幕的绘制参数示意图
4.接下来我们来看WmS中的setOverscan()的实现:
@Override 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); } }
1 首先来检查执行方法的类是否有足够的权限,这里必须有android.Manifest.permission.WRITE_SECURE_SETTINGS的权限才能调用该方法。
2 接着就是获得DisplayContent对象,这里我们传入的displayId就是Display.DEFAULT_DISPLAY,值为0,其实对于手机来说只有一块屏幕,所以就是DEFAULT_DISPLAY。当displayContent不为null的时候,调用WmS的setOverscanLocked()私有方法继续进行设置。
3 我们来看看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.name, left, top, right, bottom); mDisplaySettings.writeSettingsLocked(); reconfigureDisplayLocked(displayContent); }
该方法中做了三件事情:
l 调用displayContent的getDisplayInfo()方法获得DisplayInfo对象,然后根据传入的overscan的值,设置DisplayInfo中的响应的值。
l 调用mDisplaySettings的setOverscanLocked()方法,将overscan的值保存成一条名字为diaplayInfo.name的记录,通过这个名字,我们就可以从mDisplaySettings中读出该DisplayContent的overscan的内容。接着调用那个mDisplaySettings的writeSettingsLocked()方法,将刚才设置的内容写入到display_settings.xml设置文件中。
l 调用reconfigureDisplayLocked(displayContent)方法,进一步更新DisplayContent的内容,然后调用
performLayoutAndPlaceSurfacesLocked()方法对屏幕进行绘制。这个过程不再进行介绍。