Window,PhoneWindow,DecorView之间的关系

2,701 阅读3分钟

推荐思考

  1. 设置布局时调用的setContentView(@LayoutRes int layoutResID)方法,最终是谁在调用?
  2. View的事件分发都是按照Activity》ViewGroup》View顺序分发下去,以dispatchTouchEvent为例,讲述Activity到ViewGroup这个过程是如何完成的?
  3. 一般开发中会在布局再设置一层背景,所以在UI优化时,可以将最顶层的背景去掉,代码为 getWindow().setBackgroundDrawable(null) ,这里去除的是哪里的背景颜色?

以上三个问题,通过查看源码即可得到答案,感兴趣的朋友可以去探个究竟。

正文

我们先来看一张描述三者之间关系的图

从这张图上看,Window,PhoneWindow,DecorView三者是包含的关系,我们再来看官方文档的介绍:

Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc.

The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.

提取文档最重要的一点:window是一个抽象类,它唯一的一个实现类是PhoneWindow。

DecorView是PhoneView的一个内部类,继承自FramLayout,所以也是ViewGroup,它是顶层View(用HierarchyViewer工具查看布局的层级时,就能看到DecorView)。

现在总结一下三者的关系:

  1. 每一个Activity都有一个Window
  2. Window的唯一实现类是PhoneWindow
  3. 而PhoneWindow是一个Window,不具备多少View的相关能力,但是PhoneWindow持有DecorView实例,Activity中多数对于View的相关操作都是通过DecorView完成的。 很多源码中可以体现这一点,在关于View的操作上,Activity以及相关联的Window,PhoneView,接受到成员变量,一般都是外抛,交给DecorView去处理。

它们的关系并不复杂,在很多源码中都有体现出来,比如开篇的3个问题,如果去寻找答案,就能在源码里体会到Window,PhoneWindow,DecorView之间的关系。

源码里看三者的关系

我们一起通过开篇的三个问题所涉及的源码,从中去意会三者之间的关系。因源码很多,只贴出了关键的部分。可以自行去看完整源码,看完后一定会更加理解Window,PhoneWindow,DecorView三者之间的关系。

  1. 设置布局时调用的setContentView(@LayoutRes int layoutResID)方法,最终是由DecorView调用
//Activity的setContentView方法,调用的是getWindow里的setContentView方法
public void setContentView(@LayoutRes int layoutResID) {(
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
//getWindow()方法返回的是mWindow
private Window mWindow;
public Window getWindow() {
    return mWindow;
}
//mWindow其实就是PhoneWindow
final void attach(一堆参数) {
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
  ...
    }
 

layoutResource是垂直的线性布局,被加载到了DecorView里面,layoutResource有一个FramLyout布局,id为content,Activity的布局就是被设置在了id为Content布局里面,也就是DecorView里面。(这部分的代码很绕,建议自己去查看,这里只贴出关键的代码。)

// PhoneWindow.class
protected ViewGroup generateLayout(DecorView decor) {
	mDecor.startChanging();
	mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);// R.layout.screen_simple为layoutResource
	ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
	...
	return contentParent;
}
  1. 以dispatchTouchEvent为例, Activity到ViewGroup这个过程的传递,主要源码如下

首先调用getWindow()的superDispatchTouchEvent方法

 public boolean dispatchTouchEvent(MotionEvent ev) {
  if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    //手机屏保的方法,空实现
    onUserInteraction();
  }
  //事件开始传递
  if (getWindow().superDispatchTouchEvent(ev)) {
    return true;
  }
  return onTouchEvent(ev);
}

然后看getWindow()里的superDispatchTouchEvent方法,调用的是mDecor的superDispatchTouchEvent方法,mDecor就是DecorView。

@Override public boolean superDispatchTouchEvent(MotionEvent event) {
  return mDecor.superDispatchTouchEvent(event);
}

mDecor的superDispatchTouchEvent方法,是调用父类的DispatchTouchEvent方法。DecorView继承FramLayout,说明它也是ViewGroup,这也就能说明事件是从Activity传递到了ViewGroup。

DecorView的superDispatchTouchEvent方法
public boolean superDispatchTouchEvent(MotionEvent event) {
  return super.dispatchTouchEvent(event);//调用的是父类的dispatchTouchEvent
}
  1. 一般开发中会在布局再设置一层背景,所以在UI优化时,可以将最顶层的背景去掉,代码为getWindow().setBackgroundDrawable(null) ,这里去除是DecorView的背景颜色。

Window,PhoneWindow,DecorView,或许在实际的开发运用中比较少用到,但是搞懂三者的关系,对于我们学习其它知识点,查看源码,有很大的帮助。