平时我们初始化一个activity的使用会用到下面的代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
}
问题一、setContentView()究竟做了什么?
Activity#setContentView源码如下:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
字面意思:看方法第一行代码,做了两件事:1)获得一个window对象,2)调用对象的setContentView方法。
Activity#getWindow源码如下:
public Window getWindow() {
return mWindow;
}
返回一个mWindow对象实例。
private Window mWindow;
是一个Window对象。
Window#setContentView源码如下:
public abstract void setContentView(@LayoutRes int layoutResID);
抽象方法?回过头再看一下,原来Window对象是一个抽象类:
public abstract class Window {...}
那么它实例的方法,必须要找到它在哪实例化的,不能仅仅看它的声明类型了。
那么,回到Acitivity类中,找了老大一圈,发现一个attach方法中存在Window对象的实例方法。
Activitiy#attach源码如下:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
在上述源码中发现:mWindow = new PhoneWindow(this);
可能有人会在想:Acitivity的attach方法是在哪调用的?先放放,下篇文章马上就讲。其实这也是一个思路,研究源码,一条线研究的,分支不要乱开,否则很容易混乱。
原来我们在setContentView中的getWindow对象获取的是一个PhoneWindow对象。
看看PhoneWindow对象的代码结构:
public class PhoneWindow extends Window implements MenuBuilder.Callback {...}
能看出PhoneWindow继承了Window对象,并实现了Window对象的抽象方法。
然后我整理了一下
二、Window、Acitivity、PhoneWindow的关系如下图:
总结一下:
Acitivity的setContentView获取了一个PhoneWindow对象,并调用PhoneWindow的setContView方法。
到这儿,拿到了真正调用的setcontentview方法了:
PhoneWindow#setContentView源码如下:
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();
}
}
看到这个方法里面内容这么少,这么清晰简单的判断逻辑我就放心了。主题feature的代码这些我们都忽略不看。直接看方法内第一段代码:
if (mContentParent == null) {
installDecor();
}
如果mContentParent是空,那么执行括号内的代码。那么mContentParent是什么呢?发现是一个ViewGroup对象。
private ViewGroup mContentParent;
不难想象,也就是说当我这个viewGroup对象为空,即没有实例化的时候,就对它进行实例化。那么猜想:
installDecor方法内肯定有一个mContentParent的实例化方法。
PhoneWindow#installDecor源码如下:(源码超长,对后面部分精简了一下,见源码省略处)
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
final int localFeatures = getLocalFeatures();
for (int i = 0; i < FEATURE_MAX; i++) {
if ((localFeatures & (1 << i)) != 0) {
mDecorContentParent.initFeature(i);
}
}
mDecorContentParent.setUiOptions(mUiOptions);
if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
(mIconRes != 0 && !mDecorContentParent.hasIcon())) {
mDecorContentParent.setIcon(mIconRes);
} else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
mIconRes == 0 && !mDecorContentParent.hasIcon()) {
mDecorContentParent.setIcon(
getContext().getPackageManager().getDefaultActivityIcon());
mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
}
if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
(mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
mDecorContentParent.setLogo(mLogoRes);
}
// Invalidate if the panel menu hasn't been created before this.
// Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
// A pending invalidation will typically be resolved before the posted message
// would run normally in order to satisfy instance state restoration.
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!isDestroyed() && (st == null || st.menu == null) && !mIsStartingWindow) {
invalidatePanelMenu(FEATURE_ACTION_BAR);
}
} else {
mTitleView = (TextView)findViewById(R.id.title);
if (mTitleView != null) {
mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
View titleContainer = findViewById(
R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
if (mContentParent instanceof FrameLayout) {
((FrameLayout)mContentParent).setForeground(null);
}
} else {
mTitleView.setText(mTitle);
}
}
}
if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
mDecor.setBackgroundFallback(mBackgroundFallbackResource);
}
...(省略)...
}
}
首先我们结合回到最开始的图。源码中mDecor对应整个PhoneWindow外框,mContentParent对应图中setContentView的内容框。(即不含标题部分区域)
把上面的源码拆开来看,先看第一部分:
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
如果mDecor==null则对它进行实例化。看一眼PhoneWindow源码:
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
在看我所说的mDecor的实例化方法:
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
调用构造方法:看看构造方法源码:(简单说一下:DecorView是PhoneWindow的一个内部类)
public DecorView(Context context, int featureId) {
super(context);
mFeatureId = featureId;
mShowInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.linear_out_slow_in);
mHideInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.fast_out_linear_in);
mBarEnterExitDuration = context.getResources().getInteger(
R.integer.dock_enter_exit_duration);
}
(里面没什么东西:一个主题的int型id,动画渐入渐出的插值器,一个动画进入消失的时长int型的初始化。都并没有什么用。)看作一个普通的父类FrameLayout的构造方法即可。就是一个普通的FrameLayout布局。
没有图片的博客不是好博客:如下图:
总结一下:
PhoneWindow的setContView方法:第一步:在当前手机屏幕PhoneWindow上加载了DecorView(最底层FrameLayout布局)。
然后:我们在DecorView上绘制:
继续看installDecor源码第二部分:
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
final int localFeatures = getLocalFeatures();
for (int i = 0; i < FEATURE_MAX; i++) {
if ((localFeatures & (1 << i)) != 0) {
mDecorContentParent.initFeature(i);
}
}
mDecorContentParent.setUiOptions(mUiOptions);
if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
(mIconRes != 0 && !mDecorContentParent.hasIcon())) {
mDecorContentParent.setIcon(mIconRes);
} else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
mIconRes == 0 && !mDecorContentParent.hasIcon()) {
mDecorContentParent.setIcon(
getContext().getPackageManager().getDefaultActivityIcon());
mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
}
if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
(mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
mDecorContentParent.setLogo(mLogoRes);
}
// Invalidate if the panel menu hasn't been created before this.
// Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
// A pending invalidation will typically be resolved before the posted message
// would run normally in order to satisfy instance state restoration.
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!isDestroyed() && (st == null || st.menu == null) && !mIsStartingWindow) {
invalidatePanelMenu(FEATURE_ACTION_BAR);
}
} else {
mTitleView = (TextView)findViewById(R.id.title);
if (mTitleView != null) {
mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
View titleContainer = findViewById(
R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
if (mContentParent instanceof FrameLayout) {
((FrameLayout)mContentParent).setForeground(null);
}
} else {
mTitleView.setText(mTitle);
}
}
}
if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
mDecor.setBackgroundFallback(mBackgroundFallbackResource);
}
...省略...
}
发现还是好多代码:那么我们继续拆分:
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
........
}
mContentParent不知道大家有没有印象,之前我说的它会在installDecor中实例化。就是它了。
看到这个实例方法:generateLayout(mDecor),再联系之前的generateDecor方法。
这个实例方法不打算多讲:简单提一句用来根据不同的设置参数,来动态加载不同的样式的。再将这个样式添加到mDecor这个framelayout上。
比如我们常见的一个用法:设置不加标题栏:FEATURE_NO_TITLE。那么这儿加载的布局样式就是无标题栏的布局样式。
generateLayout(mDecor)摘抄部分代码:
protected ViewGroup generateLayout(DecorView decor) {
...省略...
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...省略...
return contentParent;}
加载方法、添加方法和我们平时自己布局一样一样的。
我们看一下screenSimple.xml这个系统提供的加载的简单的样式:(在上面的generateLayout(mDecor)用到过)
<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>
上面generateLayout(mDecor)摘抄部分代码中ID_ANDROID_CONTENT其实就是布局中的android:id="@android:id/content"这个id。
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
这样子,我们就获得了content布局,也就是我们平时写的布局代码的内容布局部分的底部。上个布局加载流程图。
最后则是通过infalate加载我们的资源id对应的xml资源文件。