什么是战略性了解:知其然,无需知其所以然,也就是知道整体结构框架即可,不追根内部具体实现。
注:文章前面会探索API 14的源码,后面会探索API 28的源码
WindowManager直译过来叫做窗口管理者,用了这么多年Window的我们,应该对窗口这个概念并不陌生,打开应用,弹出应用窗口,使用应用。电脑屏幕大,所以我们可以在一个电脑屏幕上看到多个应用,手机屏幕小,只看一个应用的体验比较好。
如果是我们要写一个类叫做WindowManager,我们会写哪些方法呢?每个应用都给他分配一个WindowManager,然后添加界面,删除界面,更新界面,差不多就这些方法吧,等有需求了再拓展嘛,嗯嗯,大概是这个逻辑。看看实际是怎么实现的吧。
ViewManager探索
根据源码,我们很轻易的发现WindowManager其实是一个接口,并且继承ViewManager,ViewManager也是个接口。
public interface WindowManager extends ViewManager
ViewManager听着像是管理View的,感觉添加View删除View什么的都在这里,别猜了,先看看源码:
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
添加View,更新View布局,移除View,方法名字倒是一目了然的,但是为啥没有deleteView,难道Android窗口这里不会删除View吗,感觉移除操作很简单,只需要把View设置为不可见就行了,也不知道内部怎么实现的,根据方法名字猜想一波吧。
WindowManager探索
我们还是来看看继承了ViewManager的WindowManager,看看他又实现了啥,最开始猜想的添加删除view居然是
ViewManager的方法,现在都不知道WindowManager这个类是干嘛的了,看波源码再做打算。
public interface WindowManager extends ViewManager {
public static class BadTokenException extends RuntimeException {
public BadTokenException() {}
public BadTokenException(String name) {
super(name);
}
}
public Display getDefaultDisplay();
public void removeViewImmediate(View view);
public boolean isHardwareAccelerated();
public static class LayoutParams extends ViewGroup.LayoutParams
implements Parcelable{
// 各种属性
}
}
内部居然有个异常类,倒是挺简单;还有个静态公共类,是我们熟悉的LayoutParams,原来是在这里定义的。
接着是三个待实现的接口方法,我们来猜猜是干嘛的:
// 获取默认Display实例
public Display getDefaultDisplay();
// 立刻移除View
public void removeViewImmediate(View view);
// 是否使用硬件加速
public boolean isHardwareAccelerated();
猜测完毕,源码看到这里,下面两个方法倒是还是好理解,第一个方法不知道Display是啥东西,感觉是跟屏幕有点关系,看看源码大部分都是一些get的方法,听着都像是获取屏幕都信息:
// 随便摘要一些方法
public class Display {
...
public void getSize(Point outSize){...}
public int getDisplayId(){...}
public int getWidth(){...}
public int getHeight(){...}
public void getRealSize(Point outSize){...}
...
}
大概了解一些方法是干嘛的,不求甚解。确实,主要的一些方法还是跟屏幕有关系。
WindowManagerImpl探索
既然大概知道了WindowManager的结构,那么来了解一下WindowManager的子类吧。
WindowManager的子类有这么几个,除去Test旗下的类,在剩下的类中,有一个类带着主角脸,那就是WindowManagerImpl,既然这个类自带主角脸,那么我们主要就来探索一下这个类。
进去这个类之后,发现这个类只有500多行的代码,难得源码才500多行,好好研究一下。既然继承了WindowManager和ViewManager接口,不然先看看WindowManagerImpl对这几个方法具体这么实现的。
ViewManager方法的具体实现
首先看看ViewManager的那三个方法:
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
第一个是addView方法是吧。那就来吧:
public void addView(View view) {
addView(view, new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE));
}
public void addView(View view, ViewGroup.LayoutParams params) {
addView(view, params, null, false);
}
public void addView(View view, ViewGroup.LayoutParams params, CompatibilityInfoHolder cih) {
addView(view, params, cih, false);
}
private void addView(View view, ViewGroup.LayoutParams params,
CompatibilityInfoHolder cih, boolean nest) {
...
}
没想到居然有这么多addView方法,而且又是以这种形式调用的,最终还是会调用最下面的这个方法,那么我们就来具体看看最下面的这个方法是怎么实现的吧。
为了便于阅读,我将addView方法的内部划分成了三个部分,我们一个一个的来看,大事化小。(注:这三个部分合起来并非addView所有源码,我只是挑出了主要代码)
private void addView(View view, ViewGroup.LayoutParams params,
CompatibilityInfoHolder cih, boolean nest) {
// 第一部分
// 第二部分
// 第三部分
}
我们首先来看第一部分的源码:
private View[] mViews;
private ViewRootImpl[] mRoots;
private void addView(View view, ViewGroup.LayoutParams params,
CompatibilityInfoHolder cih, boolean nest) {
final WindowManager.LayoutParams wparams
= (WindowManager.LayoutParams) params;
ViewRootImpl root;
View panelParentView = null;
synchronized (this) {
int index = findViewLocked(view, false);
if (index >= 0) {
if (!nest) {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
root = mRoots[index];
root.mAddNesting++;
// Update layout parameters.
view.setLayoutParams(wparams);
root.setLayoutParams(wparams, true);
return;
}
// 第二部分
// 第三部分
}
root.setView(view, wparams, panelParentView);
}
这些代码看着并非那么晦涩难懂,虽然不知道findViewLocked这个方法是干嘛的,但是我们可以继续看下去,毕竟就两个if而已,我们看到第二个if抛了个异常,条件是!nest
,但是根据前面看到的addView方法的一系列的引用,好像此处的nest变量为false,那么这个!nest
就一定为true了,所以这个异常一定会抛出来,我们还是看看第一个if的index>=0
在什么情况下进去吧,再往上看就是findViewLocked
方法了,姑且进去一探究竟。
private View[] mViews;
private int findViewLocked(View view, boolean required) {
synchronized (this) {
final int count = mViews != null ? mViews.length : 0;
for (int i = 0; i < count; i++) {
if (mViews[i] == view) {
return i;
}
}
if (required) {
throw new IllegalArgumentException(
"View not attached to window manager");
}
return -1;
}
}
看来WindowManagerImpl是用View[]来储存view的,这个方法是用来检查WindowManagerImpl是否已经拥有这个view了。
我们再回到addView方法:
private void addView(View view, ViewGroup.LayoutParams params,
CompatibilityInfoHolder cih, boolean nest) {
synchronized (this) {
int index = findViewLocked(view, false);
if (index >= 0) {
if (!nest) {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
}
// 第二部分
// 第三部分
}
}
再看这个if判断好像就很合理了,如果该view已经被添加了,就抛出异常。
我们接着来看第二部分的代码:
private void addView(View view, ViewGroup.LayoutParams params,
CompatibilityInfoHolder cih, boolean nest) {
if (false) Log.v("WindowManager", "addView view=" + view);
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException(
"Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams
= (WindowManager.LayoutParams) params;
ViewRootImpl root;
View panelParentView = null;
synchronized (this) {
// 第一部分
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews != null ? mViews.length : 0;
for (int i = 0; i < count; i++) {
if (mRoots[i].mWindow.asBinder() == wparams.token) {
panelParentView = mViews[i];
}
}
}
root = new ViewRootImpl(view.getContext());
root.mAddNesting = 1;
if (cih == null) {
root.mCompatibilityInfo = new CompatibilityInfoHolder();
} else {
root.mCompatibilityInfo = cih;
}
view.setLayoutParams(wparams);
// 第三部分
}
root.setView(view, wparams, panelParentView);
}
第二部分的代码,第一个判断涉及binder,不在我们的讨论范围,我们就暂且忽视。(一旦讨论会涉及更多其他类的源码,还有AIDL类,涉及源码太多,就此打住)
接下来就涉及ViewRootImpl,所以第二部分的代码就此跳过吧,我们来看看第三部分。
private void addView(View view, ViewGroup.LayoutParams params,
CompatibilityInfoHolder cih, boolean nest) {
if (false) Log.v("WindowManager", "addView view=" + view);
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException(
"Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams
= (WindowManager.LayoutParams) params;
ViewRootImpl root;
View panelParentView = null;
synchronized (this) {
// 第一部分
// 第二部分
if (mViews == null) {
index = 1;
mViews = new View[1];
mRoots = new ViewRootImpl[1];
mParams = new WindowManager.LayoutParams[1];
} else {
index = mViews.length + 1;
Object[] old = mViews;
mViews = new View[index];
System.arraycopy(old, 0, mViews, 0, index - 1);
old = mRoots;
mRoots = new ViewRootImpl[index];
System.arraycopy(old, 0, mRoots, 0, index - 1);
old = mParams;
mParams = new WindowManager.LayoutParams[index];
System.arraycopy(old, 0, mParams, 0, index - 1);
}
index--;
mViews[index] = view;
mRoots[index] = root;
mParams[index] = wparams;
}
root.setView(view, wparams, panelParentView);
}
第三部分的代码显的中规中矩,如果被添加的view是第一个,就新建一个数据放到第一个位置,如果是第N个,就将原来的数组复制一份,并新增一个位置,将要添加的view放到新增的位置上。然后调用ViewRootImpl的setView方法,将View加载进去。(setView方法的内容很多,你甚至可以直接通过追踪源码找到view的事件分发那里去)
到此为止,我们就算了解了addView的方法。从整个结构上来看,addView的结构很清晰,将view添加进去。不过记录了添加的view罢了。
接着我们来看具体首先的第二个方法:updateViewLayout
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
源码很短:
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams
= (WindowManager.LayoutParams) params;
view.setLayoutParams(wparams);
synchronized (this) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots[index];
mParams[index] = wparams;
root.setLayoutParams(wparams, false);
}
}
找到View对应的ViewRootImpl类,然后调用setLayoutParams方法,间接刷新界面,也算对得起这个方法的名字。
接着我们来看removeView方法:
public void removeView(View view) {
synchronized (this) {
int index = findViewLocked(view, true);
View curView = removeViewLocked(index);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
首先查看要被移除的view的位置是哪一个,然后调用removeViewLocked,我们来看看removeViewLocked做了啥:
View removeViewLocked(int index) {
ViewRootImpl root = mRoots[index];
View view = root.getView();
// Don't really remove until we have matched all calls to add().
root.mAddNesting--;
if (root.mAddNesting > 0) {
return view;
}
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance(view.getContext());
if (imm != null) {
imm.windowDismissed(mViews[index].getWindowToken());
}
}
root.die(false);
finishRemoveViewLocked(view, index);
return view;
}
里面的代码涉及了很多位置的方法,不过我们依然可以根据方法名来判断这个方法具体干了什么,不过里面有行注释能够给我们提供不少帮助,翻译过来:在调用add()方法之前,并没有真正移除view。
不过这个方法总体就是移除指定的view了。(不求甚解)
WindowManager方法的具体实现
现在我们来看看关于WindowManager接口的具体实现过程:
public interface WindowManager extends ViewManager {
public Display getDefaultDisplay();
public void removeViewImmediate(View view);
public boolean isHardwareAccelerated();
}
WindowManager接口的具体实现就要简单的多了。
public Display getDefaultDisplay() {
return new Display(Display.DEFAULT_DISPLAY, null);
}
获取默认Display原来就是获取Display的默认实例。
public void removeViewImmediate(View view) {
synchronized (this) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots[index];
View curView = root.getView();
root.mAddNesting = 0;
root.die(true);
finishRemoveViewLocked(curView, index);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
这个方法倒是和removeView方法很类似。
public boolean isHardwareAccelerated() {
return false;
}
这个方法就显的异常简单了。
到目前为止,我们就算是把WindowManager的这6个方法的具体实现给看完了,那我们又能得出什么呢,总不能白看吧。
我们发现,WindowManager确实主要是用来管理view的,主要就是view的那几个操作,增加删除和更新操作,但这里的删除更新,并不是由WindowManager本身来实现的,而是借助ViewRootImpl类来进行真正具体的操作。本身的主要任务是通过数组的方式记录View和对应的ViewRootImpl,不愧是管理者,指挥其他人帮你干事就行了,然后我们指挥管理者。
基于API28的探索
前面的代码都是基于API14的代码,为什么我要用API14的代码讲解之后再用API28的代码呢,因为低级的API往往比高级的API源码要简单很多,但是Android的源码,这些代码的主要架构是不会变的,现在我们已经了解了API14的源码,那么现在我们来了解API28的源码就会快很多,试试?
API28的ViewManager类倒是没有变化,不过WindowManager倒是变了不少,现在来看看这个接口变成什么样子了:
public interface WindowManager extends ViewManager {
// 一堆属性
// 两个异常类
public Display getDefaultDisplay();
public void removeViewImmediate(View view);
public interface KeyboardShortcutsReceiver {
void onKeyboardShortcutsReceived(List<KeyboardShortcutGroup> result);
}
public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
public Region getCurrentImeTouchRegion();
public static class LayoutParams extends ViewGroup.LayoutParams
implements Parcelable{
// 各种属性
}
}
我们可以看到,还是有不少变化,新增了2个方法,去掉了isHardwareAccelerated这个方法。
子类也由多个变成了一个:
WindowManagerImpl的变化更大,从原来的500多行,变成了100来行,难得源码变少。
大概看看里面长什么样:
public final class WindowManagerImpl implements WindowManager{
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
...
}
原来里面的方法都被mGlobal给承包了,以前是通过ViewRootImpl实现的东西,现在由WindowManagerGlobal实现,所以这到底是个啥。
进来WindowManagerGlobal这个类之后,发现这个类,跟API14的WindowManagerImpl特别像,以前WindowManagerImpl有的方法,现在在WindowManagerGlobal这个类里都能找到,而且还做了拓展,并且实现方式也很类似,看来Google将原来WindowManagerImpl的方法都让WindowManagerGlobal去管理了。而且储存view的形式也从数组变成了ArrayList,想必是要管理更多的View了。
总结
通过对WindowManager类的探索,我们大致了解到了,其实这个类主要是对view进行管理,添加移除什么的,但是这只是个管理类,所以具体的实现不可能写在这个类里面,若要了解具体如何实现的,还需进入ViewRootImpl类里面细细探索,这个类才是真正的大头,我们只是站在了一个顶端的位置俯瞰整个结构,还需深入每一个模块才能知其所以然。
我们也知道了老代码和新代码虽然有差异,但是核心的思想还是不会变的,如果要想了解某些模块的具体实现流程,在啃不动源码的时候,不妨啃啃老代码,老代码没有花里胡哨(各种各样)的类去装饰,侧重核心,可以很快了解内部机制。(其实本篇博客的目的是这个?)