为什么说setContentView
熟悉呢?因为该方法是我们从入门Android开发就接触的一个方法,在我们写过的每一个Activity
中都有他的身影。但为什么又说setContentView
陌生呢?因为我们在日常的开发中只知道用,并没有深入分析该方法是怎么将我们传入的layout
资源id
变为一个可视的界面的。今天我们就来揭开这层神秘的面纱,扒一扒其中的原理(注:源码基于Android12)。
因为我们日常开发的Activity
最终都会继承自android.app.Activity
,所以先看Activity
类的setContentView
方法:
android.app.Activity
1 2 3 4
| public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
|
可以看到注释1处其实是调用了getWindow()
的setContentView
方法,我们再看getWindow()
做了什么。
android.app.Activity
1 2 3
| public Window getWindow() { return mWindow; }
|
返回了一个mWindow
,那mWindow
又是什么,看他的声明代码:
android.app.Activity
1 2
| @UnsupportedAppUsage private Window mWindow;
|
mWindow
是一个Window
类,查看源码在Activity
的attach
方法中mWindow
被赋值:
android.app.Activity
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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, IBinder assistToken, IBinder shareableActivityToken) { attachBaseContext(context);
mFragments.attachHost(null );
mWindow = new PhoneWindow(this, window, activityConfigCallback); ...
|
看到这里,我们知道Activity
的setContentView
最终调用的是PhoneWindow
的setContentView
方法,我们继续看PhoneWindow
的setContentView
方法:
com.android.internal.policy.PhoneWindow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @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; }
|
看注释1处的installDecor
方法:
com.android.internal.policy.PhoneWindow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private void installDecor() { mForceDecorInstall = false; 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); ...
|
看注释1处的generateDecor
方法:
com.android.internal.policy.PhoneWindow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| protected DecorView generateDecor(int featureId) { Context context; if (mUseDecorContext) { Context applicationContext = getContext().getApplicationContext(); if (applicationContext == null) { context = getContext(); } else { context = new DecorContext(applicationContext, this); if (mTheme != -1) { context.setTheme(mTheme); } } } else { context = getContext(); } return new DecorView(context, featureId, this, getAttributes()); }
|
generateDecor
最终new
了一个DecorView
对象,并把该对象返回并赋值给installDecor
方法中的mDecor
引用。
返回到installDecor
方法继续看注释2的generateLayout
方法:
com.android.internal.policy.PhoneWindow
1 2 3 4 5 6 7 8 9 10
| protected ViewGroup generateLayout(DecorView decor) { ... int layoutResource; ... layoutResource = R.layout.screen_title; ... mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); ... return contentParent;
|
由于generateLayout
的代码很长,此处只贴出关键的代码。generateLayout
的主要工作是将系统中的R.layout.screen_title
布局资源添加到DecorView
中,然后在PhoneWindow
中调用findViewById(ID_ANDROID_CONTENT)
,找到R.layout.screen_title
中id
为ID_ANDROID_CONTENT
的FrameLayout
,并返回给installDecor
方法中的mContentParent
引用。
R.layout.screen_title
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:fitsSystemWindows="true"> <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>
|
注意最下面的FrameLayout
的id
是为:content
,再看源码中的ID_ANDROID_CONTENT
的值:
1
| public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
|
最后再回到PhoneWindow
的setContentView
方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @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; }
|
注释1处的代码之前已经讲完了,然后看注释2处的代码,这句代码就是将setContentView
传入的布局资源id
加载到mContentParent
,而mContentParent
就是之前讲过的id
为content
的FrameLayout
。
至此一个Activity
的结构也清楚了,结构如下图所示:
Activity
中包含一个PhoneWindow
,PhoneWindow
中包含一个DecorView
,DecorView
中包含系统的布局资源R.layout.screen_title
。而setContentView
就是将在Activity
中传入的布局资源文件加载到id
为content
的FrameLayout
中。
关注我