在咱们开发过程当中,咱们有的时候会碰到一些眼熟的单词,如Window、PhoneWindow、DecorView、ViewGroup等等名词,虽然不知道它们都包含什么意思,可是常常会碰到,做为一个准备进阶的Android程序员,咱们有必要了解一下来龙去脉,接下来咱们便一一了解这些名词。首先咱们从最经常使用的部分-setContentView学起。javascript
//开发工具
一、Android Studio
Android Studio 2.2.3
Build #AI-145.3537739, built on December 2, 2016
JRE: 1.8.0_112-release-b05 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
//源码环境
二、Android API -25
compileSdkVersion 25
buildToolsVersion "25.0.2"复制代码
首先咱们从最多见的setContentView开始分析源码。html
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//最多见的开始分析,进入详细代码
setContentView(R.layout.activity_set_content_view_learn);
}复制代码
进入到了详细代码中咱们会发现其实咱们进入到了activity类代码中了。java
//代码清单Activity.java
/** * Set the activity content from a layout resource. The resource will be * inflated, adding all top-level views to the activity. * * @param layoutResID Resource ID to be inflated. * * @see #setContentView(android.view.View) * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams) */
public void setContentView(@LayoutRes int layoutResID) {
//调用Window类中的方法setContentView。
getWindow().setContentView(layoutResID);
//无关代码,初始化actionBar相关东东...
initWindowDecorActionBar();
}复制代码
首先咱们会发现,咱们先调用getWindow获取window对象,而后再调用Window类中的setContentView方法,咱们能够发现Window类实际上是一个抽象类,因此确定会有它的实现类,在这里,它的实现类就是PhoneWindow类。在这里,咱们终于碰到了一个常常会提到的类PhoneWindow。android
//代码清单 抽象类Window
public abstract class Window {
//省略相关代码...
}
//代码清单 Window实现类,PhoneWindow
public class PhoneWindow extends Window implements MenuBuilder.Callback {
//省略相关代码...
}复制代码
从上面的类申明当中咱们就能够发现,PhoneWindow类实际上是抽象类Window的实现类,因此对于setContentView方法,其实咱们就获得它的实现类PhoneWindow中去查看。git
//代码清单 PhoneWindow.java
// 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;
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}复制代码
在PhoneWindow这个具体的实现类中,咱们能够看到的流程是首先判断mContentParent是否是等于null,是的话就加在installDecor()方法,不是的话再判断是否使用了这个FEATURE_CONTENT_TRANSITIONS的flag,若是没有的话则mContentParent删除其中全部的view。接下来再判断是否须要经过LayoutInflater.inflate将咱们传入的layout放置到mContentParent中。程序员
其实咱们能够稍微预测下installDecor()的功能,大概就是初始化mContentParent这个变量。而mContentParent这个变量就是包裹咱们设置的整个xml布局内容的ViewGroup。github
最后就是调用了一个接口Callback里面的方法。咱们能够发现其实Activity实现了这个接口,可是onContentChanged这个接口方法在Activity中是一个空实现,这不是重点。windows
接下来,咱们继续研究installDecor()中的源码~app
//代码清单 PhoneWindow中的installDecor方法
//申明变量
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
//省略无关代码...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
//省略无关代码...
} else {
mTitleView = (TextView) findViewById(R.id.title);
//设置是否须要标题
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
final View titleContainer = findViewById(R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
mContentParent.setForeground(null);
} else {
mTitleView.setText(mTitle);
}
}
}
//省略无关代码...
}
}复制代码
从上面代码咱们能够看出大概的流程。首先经过generateDecor(-1)来初始化一下mDecor,这个mDecor是DecorView的对象,看吧,这里面已经出现了这个常见名词,等等咱们来分析一下DecorView这个类的做用。ide
接下来,咱们用mDecor这个对象经过generateLayout(mDecor)来初始化mContentParent这个对象。而后咱们即可以经过findViewById这个方法来获取相关的控件了。
//代码清单 window.java类获取相关控件
@Nullable
public View findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}复制代码
在这里的getDecorView()方法其实就是获取mDecor这个对象。接下来咱们开始分析DecorView这个类。入口点就是咱们刚刚在installDecor方法中初始化mDecor这个对象的地方mDecor = generateDecor(-1)。
//代码清单 DecorView.java类
protected DecorView generateDecor(int featureId) {
Context context;
//没什么鸟用的无关代码,省略...
return new DecorView(context, featureId, this, getAttributes());
}复制代码
从generateDecor中咱们没发现什么有用的东西,咱们继续分析相关代码。接下来咱们分析mContentParent = generateLayout(mDecor);
从这个方法名便能看出个大概了,它是生成布局,而后赋值给mContentParent,咱们进入到方法中详细了解一下整个布局生成过程。
//代码清单 generateLayout方法流程
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//设置当前activity的theme
TypedArray a = getWindowStyle();
//省略无关代码...
//首先经过WindowStyle中设置的各类属性,对Window进行requestFeat
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
//省略无关代码...
//根据feature来加载对应的xml布局文件
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
//将上面获取到的xml布局加载到mDecor对象中
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
registerSwipeCallbacks();
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
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);
mDecor.setElevation(mElevation);
mDecor.setClipToOutline(mClipToOutline);
if (mTitle != null) {
setTitle(mTitle);
}
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();
return contentParent;
}复制代码
上面这个方法流程咱们能看出个大概,首先getWindowStyle在当前的Window的theme中获取咱们的Window中定义的属性。而后就根据这些属性的值,对咱们的Window进行各类requestFeature,setFlags等等。
还记得咱们平时写应用Activity时设置的theme或者feature吗(全屏啥的,NoTitle等)?咱们通常是否是经过XML的android:theme属性或者java的requestFeature()方法来设置的呢?譬如:
经过java文件设置:
requestWindowFeature(Window.FEATURE_NO_TITLE);
经过xml文件设置:
android:theme="@android:style/Theme.NoTitleBar"复制代码
对的,其实咱们平时requestWindowFeature()设置的值就是在这里经过getLocalFeature()获取的;而android:theme属性也是经过这里的getWindowStyle()获取的。
因此这里就是解析咱们为Activity设置theme的地方,至于theme通常能够在AndroidManifest里面进行设置。接下来,经过对features和mIsFloating的判断,为layoutResource进行赋值,至于值能够为R.layout.screen_custom_title;R.layout.screen_action_bar;等等。至于features,除了theme中设置的,咱们也能够在Activity的onCreate的setContentView以前进行requestFeature,也解释了,为何须要在setContentView前调用requestFeature设置全屏什么的。
最后经过咱们获得了layoutResource,而后将它加载给mDecor对象,这个mDecor对象实际上是一个FrameLayout,它的中心思想就是根据theme或者咱们在setContentView以前设置的Feature来获取对应的xml布局,而后经过mLayoutInflater转化为view,赋值给mDecor对象,这些布局文件中都包含一个id为content的FrameLayout,最后将其引用返回给mContentParent。
//代码清单 xml布局文件,包含id为content的FrameLayout布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/action_mode_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="?attr/actionBarTheme" /> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" android:foregroundInsidePadding="false" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout>复制代码
咱们用word来画一个简单的示意图
到这里咱们分析了生成布局后的大概流程,这样生成了布局后咱们接着干嘛呢?请回看当初的代码:
//代码清单 PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
//咱们上面分析的生成系统xml布局的流程
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//将咱们本身写的xml加载到咱们上面获取到的里面包含id为content的xml布局中去,并赋值给mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
//省略无关代码...
}复制代码
咱们刚刚写了一个系统生成的xml布局就是包含id为content的布局:
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />复制代码
其实这就是咱们将本身设置的xml布局装载进这个布局中。这就是整个setContentView所作的工做。
最后咱们来总结一下所有流程,用一个图来表示,更方面直接:
Android中有一个成员叫Window,Window是一个抽象类,提供了绘制窗口的一组通用API,PhoneWindow继承自Window,是Window的具体实现类,PhoneWindow中有一个私有成员DecorView,这个DecorView对象是全部应用Activity页面的根View,DecorView继承自FrameLayout,在内部根据用户设置的Activity的主题(theme)对FrameLayout进行修饰,为用户提供给定Theme下的布局样式。通常状况下,DecorView中包含一个用于显示Activity标题的TitleView和一个用于显示内容的ContentView。
能够看出,DecorView中包含一个Vertical的LinearLayout布局文件,文件中有两个FrameLayout,上面一个FrameLayout用于显示Activity的标题,下面一个FrameLayout用于显示Activity的具体内容,也就是说,咱们经过setContentView方法加载的布局文件View将显示在下面这个FrameLayout中。
首先初始化mDecor,即DecorView为FrameLayout的子类。就是咱们整个窗口的根视图了。而后,根据theme中的属性值,选择合适的布局,经过infalter.inflater放入到咱们的mDecor中。在这些布局中,通常会包含ActionBar,Title,和一个id为content的FrameLayout。最后,咱们在Activity中设置的布局,会经过infalter.inflater压入到咱们的id为content的FrameLayout中去。
一、http://blog.csdn.net/lmj623565791/article/details/41894125
二、http://blog.csdn.net/yanbober/article/details/45970721/
三、http://www.2cto.com/kf/201409/331824.html
github: github.com/crazyandcod…
博客: crazyandcoder.github.io