前言
最近打算把 Android 中和 View
相关的知识做一下更新,本来另一篇博客已经写的差不多了,但是发现缺少了这一块关于 Activity
的构成知识的话,在讲述事件分发机制的时候不太好开展,所以这个知识点也就相当于给后面的 View
相关知识做一个补充!废话不多说,我们现在开始。
先下结论
在解析源码之前,我们先把最终的结论得出来,方便我们后面对源码部分进行分析,在我们的 Android 设备上,我们的 Activity
构成应当是这样子的:
如果你之前从未了解过这部分知识,初看可能会觉得相当的陌生。别急,接下来我将为你从外到内介绍这幅图的含义。
- 首先是最外层的
Activity
,每次系统为我们默认建好的MainActivity
,是继承自AppCompatActivity
的,其实AppCompatActivity
也是间接继承自Activity
的一个类,所以我们的最外框层,就是Activity
。 - 接下来是
PhoneWindow
,我们在MainActivity
使用的setContentView
加载布局方法实际上调用的是PhoneWindow
里面的setContentView
方法。 - 然后就到了
DecorView
了,在PhoneWindow
中我们会生成一个DecorView
,而且DecorView
是继承自FrameLayout
的一个类。 - 最后就是
DecorView
里面的TitleView
和ContentView
了,其中TitleView
是用于显示标题的,而ContentView
是我们平时打交道最多的地方,我们写的布局(例如 activity_main.xml)实际上都是放置在这里的,所以这也是我们加载布局的方法要叫做setContentView
而不是直接叫做setView
的一个原因。
好了,讲完了我们 Activity 的构成,我们现在就开始从源码的角度来看看源码是如何构成上面这幅图的吧!
源码解析
首先,我们的布局文件加载都是通过 Activity
的 setContentView
方法加载进来的,也就是说一切的活动从这里开始,那么我们就来看看这个方法里面是什么吧。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
这里可以看到,代码里面通过 getWindow
调用了 setContentView
方法,getWindow
方法明显是会返回一个对象的,我们就来看看 getWindow
方法的源码是什么。
public Window getWindow() {
return mWindow;
}
这个方法非常的简单,只是简单地返回了一个 mWindow
的变量,这个 mWindow
从前面的定义我们可以得知这是一个 Window
类型的对象,那么它又是从哪里来的呢?
我们接下来把目光聚集到 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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback); // 1
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;
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.setColorMode(info.colorMode);
}
我们看到代码的第12行处,这里 mWindow
被 new
了出来,至于为什么要 new 成一个 PhoneWindow
类型的对象,原因是 Window
是一个抽象类,而 PhoneWindow
是 Window
的唯一实现类。从这里我们也就能得到一个重要的信息:mWindow
就是一个 PhoneWindow
对象。
接下来我们继续回到我们 Activity
的 setContentView
方法,为了方便阅读我这里把 setContentView
的代码重新贴一遍:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
从上面的分析我们知道了 getWindow
返回的是一个 PhoneWindow
对象,也就是我们前面那幅图说的,Activity
里面包含了一个 PhoneWindow
。所以这个 setContentView
就是 PhoneWindow
里面的 setContentView
方法,我们来看看里面是什么。
@Override
public void setContentView(int layoutResID) {
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;
}
我们看到第4行的代码,这是这个方法的一个关键语句,一般情况下,我们都可以进入到这个 if
里面执行 installDecor
方法,我们接下来看看 installDecor
方法里面是什么吧!
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) { // 关键点1
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); // 关键点2
// 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);
......
}
这段代码非常的长,我们从中截取关键的部分来看下这个方法做了什么。首先我们看到关键点1处,这里面有一个变量 mDecor
,它是一个 DecorView
类型的变量,如果 mDecor
为空的话,就会执行 generateDecor
方法,而我们第一次加载布局文件的时候,mDecor
肯定为空,所以这个方法也就会得到执行。我们来看看这个方法里面做了什么:
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// 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());
}
我们直接看到最后一个返回语句,它会返回一个 DecorView
类型的对象,这个类位于 PhoneWindow
的内部。这里要特别点出:DecorView
是继承自 FrameLayout
的。好了,知道了 generateDecor
方法的功能就是为 mDecor
创建一个 DecorView
类型的对象之后,我们再调过头去看看 installDecor
方法的关键点2处,如果 mContentParent
为空的话,就会调用 generateLayout
方法,我们第一次加载的话,mContentParent
肯定也是空的,所以我们来看看这个方法里面是什么:
protected ViewGroup generateLayout(DecorView decor) {
// Inflate the window decor.
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;
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();
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(contentParent);
}
// 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;
}
同样这个方法的代码也是巨长无比,但是它的功能其实还是比较清晰的。这段代码的主要功能是根据不同的情况加载不同的布局给 layoutResource
,然后返回给 mContentParent
。这个布局也就是我们 DecorView
里面的布局了,我们可以拿其中一个布局的源代码来看看,例如 R.layout.screen_title,它的布局代码如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<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:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
可以看到,它的根布局是 LinearLayout
,里面包含了1个 ViewStub
和2个 FrameLayout
,ViewStub
其实就是我们用来显示 ActionBar
的。下面的两个 FrameLayout
,上面一个是 title
,用来显示标题,下面一个是 content
,用来显示内容。
所以通过这个我们也就知道了,DecorView
在一般情况下,会将自身分成两个区域,一个用来显示标题,另一个用来显示内容。我们平时的布局文件,例如 activity_main
,就是放置在 content
里面的,所以我们的布局文件其实一直是嵌套在一个 FrameLayout
下面的。至此,我们的源码分析就结束了。
总结一下我们的 installDecor
方法:它的作用就是产生一个 DecorView
对象,然后根据不同情况加载进不同的布局(一般情况下是分成一个 title
和一个 content
)。而 installDecor
方法又是在 PhoneWindow
下的 setContentView
方法里面调用的,而这个方法又是由 Activity
的 setContentView
方法调用,所以我们依照这个关系,就可以得出上面的结论图了:
这部分源码还是比较简单的吧!从 Activity
的 setContentView
方法层层递进,最终就可以得出这幅图了。这部分知识是为了后面 View 的知识做一个补充的,所以相对而言比较简单,后面我将会继续介绍事件分发机制和 View 的绘制流程,如果有兴趣就来看看吧!祝你学习愉快!