转载请注明 Android_YangKe,谢谢!
老实说自己做 Android 有一段时间了,但发现 Android 技能提升上有了点小瓶颈,总感觉自己什么都会,又都感觉自己什么都不会,于是就有了此片文章。
下面我们将通过 Window 慢慢引出三者之间的关系,同时适当的源码辅助分析。说到源码相信很多人都是心中都一万个 mmb,劳资这么差的英文,动不动成千上万行的代码,脑袋瞬间短路好不好... 微笑-微笑 。
Activity,View 是什么我相信不用解释,而 Window 在使用频率上相对来说就低了些,那么 Window 是什么?在 Android 体系中扮演着什么样的角色?下面是 Google 工程师对 Window 类的一些介绍,我们来看下。
//Window.java
/**
* 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.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
大致意思:Window 是一个基础类,是顶级视图的承载。提供了标准的 UI 策略,如背景,标题。同时 Window 只有一个实现类PhoneWindow
。官方的解释还是比较给力,很详细的介绍了该对象,现在我们对 Window 有了一个初步认识。下面我们将结合具体案例进行探索。
Activity 中 onCreate 函数相信大家再熟悉不过了,如我们将里面的setContentView
函数注释掉时,会发现原来我们的炫酷的页面只留下一个标题加空白页面(前提我们没有修改默认的主题),这么看来此行代码很关键。
//Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
一般来说我们的 Activity 都是继承自 AppCompatActivity
,但最终的顶层父类是 Activity。通过上面源码我们可以发现 Activity 只是 View 宿主,并没有真正的实现setContentView
而是通过 getWindow 操作此函数,我们来看下 getWindow。
//Activity.java
public Window getWindow() {
return mWindow;
}
final void attach(...) {
attachBaseContext(context);
mWindow = new PhoneWindow(this, window,activityConfigCallback);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
}
很简单 getWindow 函数返回了一个实例PhoneWindow
。由于代码中没有对 getWindow 判空,我们可以推测出 attach 函数一定在 setContentView
之前执行,也就是 onCreate 函数之前,不然一定会报NullPointerException
。
扯得有点多,言归正传。这里我们看到了 Window 的身影,从上文我们了解到 Window 是顶级视图的承载,而PhoneWindow
又是 Window 的唯一子类,我们尝试着去 PhoneWindow
中探索 setContentView
。
//PhoneWindow.java
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
...
}
一大串代码(当然我省略了一部分代码)果不其然,真正的操作的就是在 Window 的 setContentView
函数中完成的,首先。
- mDecor:Window 上最顶层的视图,如果按照从上到下的顺序,分别是状态栏,标题栏,具体 View 控件。由于其继承了 FrameLayout 也就是说 mDecor 也是一个 ViewGroup。
- mContentParent:表示视图区域,里面可以是 mDecor,也可以是 mDecor 中的具体 View,但不代表具体视图。
了解了这两个对象的作用,我们继续看代码。removeAllViews
函数顾名思义,也就是从当前 Window 中移除所有 View,这里就不进入源码进行分析了(代码水很深,随时保留精气神)。现在就剩下installDecor
了,此函数貌似很关键,我们跟进来看下。
//PhoneWindow.java
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
首先映入眼帘的是generateDecor
,同时此函数的返回值赋予了 mDecor。随后系统调用了generateLayout
。generateLayout
从字面意义上来看是初始化布局,莫非跟布局 View 有关,我们来看一下。
//PhoneWindow.java
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
...
}
这里我们抛去 if 的各种判断直接看这几个常量:FLAG_FULLSCREEN
、 FEATURE_NO_TITLE
、 FLAG_FULLSCREEN
等等,是不是似曾相识呢?相信大家在 onCreate 函数setContentView
前都有过重设当前 Activity 样式的经历,严格来说应该是 Window (Activity 全屏、去除标题栏)。也就是说屏幕上页面的尺寸,样式都是通过 Window 来控制的!看来此函数很重要,打起精神,我们再来分析一波。
//PhoneWindow.java
protected ViewGroup generateLayout(DecorView decor) {
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
WindowManager.LayoutParams params = getAttributes();
if (!hasSoftInputMode()) {
params.softInputMode = a.getInt(R.styleable.Window_windowSoftInputMode, params.softInputMode);
}
if (params.windowAnimations == 0) {
params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
}
...
if (getContainer() == null) {
final Drawable background;
if (mBackgroundResource != 0) {
background = getContext().getDrawable(mBackgroundResource);
} else {
background = mBackgroundDrawable;
}
mDecor.setWindowBackground(background);
final Drawable frame;
if (mFrameResource != 0) {
frame = getContext().getDrawable(mFrameResource);
} else {
frame = null;
}
mDecor.setWindowFrame(frame);
...
if (mTitle != null) {setTitle(mTitle);}
if (mTitleColor == 0) {mTitleColor = mTextColor;}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();
return contentParent;
}
首先我们看下官方给的注释:
- Remaining setup -- of background and title -- that only applies to top-level windows.
将自己置身于情境之中,大概意思:设置标题,背景颜色,将视图应用在顶层 View 上,谁呢?DecorView。
其次:
获取 Window 的一些属性,并根据情况对输入法模式、载入动画等等进行配置。
最后:
对 Window 承载的视图进行背景设置,边距设置,标题栏颜色设置,标题栏设置等等。
到这里相信你对 Window 的作用,以及其在 Android 中扮演着怎样的角色有了一定的认知。下面我们来看一张图片,辅助我们理解 Activity、Window、View 之间的关系:(图片源自互联网,出处不明)
最外层是 Activity、其次是 PhoneWindow、最后是我们常操作的 View 控件。通过这张图相信你对 Activity、Window、View 有了进一步的认识,下面我们再来重新认识下三者。
View:说到 View 相信大家都很熟悉,像什么 TextView、Button、ImageView 等等我相信你可以一口气说一大堆。View 是具体视图的最小展示单元。在页面上可以是一张图片,一段文本,一个网页。简而言之:View 就是具体视图(常常与用户打交道)。
Window:我们真正意义上所说的页面,是 Activity 任命的大使,主要用于管理 View,设置窗口(视图)样式、尺寸、输入法模式等。我们也可以通过WindowManager
对 View 进行添加和移除操作等。简而言之:Window 主要用于控制显示窗口的尺寸、样式、View 的移除添加(常常与研发人员打交道)。
Activity:页面的载体。维护应用程序的生命周期、提供用户处理事件的 API、进程间通信等。简而言之:用于管理系统组件相关事物。
整理:
Activity-> PhoneWindow-> DecorView。
总结:
Activity 作为四大组件主要与系统进行交互,用于组件间通信,生命周期管理、进程通信等。Window 作为视图载体,主要用于管理视图的尺寸、样式、输入法模式、View 的移除添加等,需要依托于 Activiity。View 就比较简单了,不同的 View 用于展示不同的视图,例:文本组件,图片组件,甚至网页等,需要依托于 Window。也就是说三者相辅相成,谁离开都将无存在意义。
尾声:
为什么 View 不直接与 Activity 关联呢?反而又设计出一个 Window 对象?
其实这里有些面向对象的概念,也就是说对于庞大的功能我们需要进行拆分,让其尽量独立,各司其职,同时在功能互不影响的情况下,相辅相成。
完~
喜欢有帮助的话: 双击、评论、转发,动一动你的小手让更多的人知道!