setContentView
方法是我们Activity
里面必须要用的方法,那么当我们调用这个方法的时候系统到底做了什么呢?现在来一探究竟
首先查看Activity
的setContentView
方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
里面其实是把view
设置到getWindow()
对象上面,那getWindow()
返回的是mWindow
,mWindow
就是一个Window
对象的引用,点进Window的setContent
方法,发现Window
是一个抽象类.那mWindow
是什么时候赋值的呢,并且实际类型是什么呢?
public Window getWindow() {
return mWindow;
}
在attach
方法里面我们看到有赋值,其实mWindow
实际是一个PhoneWindow
,那么attach
方法什么时候执行的呢?肯定是在onCreate
执行前,系统会调用这个方法。后面我们分析startActivity
的时候会发现,Activity
被系统实例化后会调用此方法。
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,
Window window, ActivityConfigCallback activityConfigCallback) {
....省略部分代码
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(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;
....省略部分代码
//疑问1
mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
}
于是我们去PhoneWindow
里面查找setContent
方法
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
..省略部分代码
}
第一次调用setContentView
方法时,mContentParent
为空,走installDecor
方法,点到installDecor
方法里面去看
private void installDecor() {
mForceDecorInstall = false;
//同样第一次调用,mDecor也为null.走if里面
if (mDecor == null) {
//生成一个DecorView对象
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
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);
}
}
}
}
同样的mDecor
也是空,调用generateDecor
方法生成一个DecorView
,DecorView
是个啥类呢,DecorView
是一个继承了FrameLayout
的ViewGroup
。而且该类是被hide
的类,也就是说只有系统可以用。
protected DecorView generateDecor(int featureId) {
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
继续查看installDecor
方法,mDecor
已经生成,类型是个继承FrameLayout
的DecorView
类。mContentParent
前面我们知道也为空,这个时候调用generateLayout
方法返回的
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
..//省略后面代码
}
于是ctrl右击到generateLayout
方法
protected ViewGroup generateLayout(DecorView decor) {
TypedArray a = getWindowStyle();
...//省略部分代码
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
//根据不一样的features,去获取不一样的布局文件
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} 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!");
}
mDecor.startChanging();
//将layoutResoure资源文件的view加载,并且添加到mDecor
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//拿到资源id为ID_ANDROID_CONTENT的ViewGroup
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
..//省略部分代码
return contentParent;
}
看主要的代码,这个根据features
的值加载相应的布局文件,由于我们并没有设置features
,于是默认选择了R.layout.screen_simple
布局载入.当然我们可以通过Activity的getWindow().requestFeature(features)
方法去设置,前提是要在setContent方法前执行。
Window.java的requestFeature
方法的注释已经说明了。
/**
* Enable extended screen features. This must be called before
* setContentView(). May be called as many times as desired as long as it
* is before setContentView(). If not called, no extended features
* will be available. You can not turn off a feature once it is requested.
* You canot use other title features with {@link #FEATURE_CUSTOM_TITLE}.
* @param featureId The desired features, defined as constants by Window.
* @return The features that are now set.
*/
public boolean requestFeature(int featureId) {
final int flag = 1<
mFeatures |= flag;
mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
return (mFeatures&flag) != 0;
}
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)
;方法就是把layoutResource
资源文件加载进来,并且添加到mDecor
。里面也是我们平常用的LayoutInflater.inflate
方法。现在资源文件加载进来后,然后去查找了一个id为ID_ANDROID_CONTENT
的ViewGroup
.那这个ViewGroup
是个啥布局呢?
ID_ANDROID_CONTENT
我们同样在Window里面查找
Window.java
/**
* 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;
在系统源码frameworks\base\core\res\res\layout
的资源文件R.layout.simple.xml
里面,R.id.content
就是一个FrameLayout
布局,contentParent
就是它。最外层是个LinearLayout
,@android:id/content
是个FramenLayout
。@+id/action_mode_bar
就是我们的默认头部内容
<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" />
<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>
可以看到installDecor()
方法主要是生成一个DecorView
类并复赋值mDecor
,加载系统的布局文件并且添加到mDecor
,将一个R.id.content
的容器控件赋值给mContentParent
,继续回到PhoneWindow.setContent
方法
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
//没有设置feature为FEATURE_CONTENT_TRANSITIONS故走else
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//我们自己acitivity的资源文件加载到mContentParent上面
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
由上面的代码得知,我们自己的布局是加载到一个叫R.id.content
容器控件上面。
通过上面的源码分析得知:一个Acitivity
里面包含了一个PhoneWindow
对象,一个PhonWindow
对象里面有个DecorView
容器控件,这个控件是根控件。这个根控件里面包含一个id为R.Id.Content
的子容器,我们自己写的布局文件添加到该容器当中
从网上扣了一张图可以明确控件的关系如下
setContentView.png查看Dialog
的setContentView
方法,也是设置到mWindow
上面,mWindow
又是何时赋值的?
public void setContentView(int layoutResID) {
mWindow.setContentView(layoutResID);
}
在Dialog
的初始化的时候
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (themeResId == 0) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
themeResId = outValue.resourceId;
}
mContext = new ContextThemeWrapper(context, themeResId);
} else {
mContext = context;
}
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//同样生成一个PhoneWindow
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
在Dialog
里面也是这样的层次关系。Dialog
包含了一个PhoneWindow
.我们写的布局也是添加到R.id.content
父控件中去。
疑问1
在activity.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,
Window window, ActivityConfigCallback activityConfigCallback) {
....省略部分代码
mWindow = new PhoneWindow(this, window, activityConfigCallback);
....省略部分代码
//疑问1
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
为什么这行代码会有疑问呢?这行代码看起来就是设置window
的windowManager
。而我们的第一直觉是应该就是获取系统的windowManager
然后设置进去,是不是这样我们继续查看Window
的里面的这个方法
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
咦?不是直觉mWindowManager=wm
,而是又调用了WindowManagerImpl
的createLocalWindowManager
方法,继续去WindowManagerImpl
类里面查看createLocalWindowManager
方法。
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
直接创建了一个新的WindowManagerImpl
对象.
虽然WindowManagerImpl
里面的主要方法是调用WindowManagerGlobal
的,而WindowManagerGlobal
类是个单例。要是WindowManagerImpl
直接是单例不是更好?
疑问2上面我们分析了setContent
是把我们的布局添加到R.id.content
布局上。并且DecorView是个根布局。那么DecorView
又是如何最终显示在我们手机界面上的呢?
疑问3 Activity
和Dialog
的添加到R.id.content
布局上,并且根目录都是DecorView
。那是否其他的视图比如Toast
也是如此?后面查看Toast
显示源码再做分析
网友评论