簡(jiǎn)單的梳理一下setContentView()方法都干了什么。啟動(dòng)Activity之后,我們寫的布局文件怎么就展示在了該Activity的頁(yè)面之上。我們知道的window,windowmanager,decorview,viewrootImpl它們具體的職責(zé)是什么,并且它們之間又存在著什么關(guān)系。帶著這些問(wèn)題,通過(guò)閱讀源碼大致的捋一下思路,對(duì)Android布局的繪制過(guò)程有一個(gè)大概的輪廓框架。
先進(jìn)入setContentView方法會(huì)看到,會(huì)來(lái)到Activity的setContentView方法中
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
看到調(diào)用到了getWindow()的setContentView方法,這個(gè)window類是一個(gè)抽象類,它有一個(gè)唯一的實(shí)現(xiàn)類,就是PhoneWindow。所以直接查看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;
}
mContentParent此時(shí)為空,會(huì)進(jìn)入到installDecor()方法中。
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);
......
......
}
首先mDecor為null,進(jìn)入if塊中的generateDecor()方法,為mDeocr賦值。當(dāng)mDecor不為null時(shí),就直接調(diào)用mDecor.setWindow(this);方法,將該phonewindow對(duì)象與該decorview相關(guān)連,decorview中有成員變量mWindow來(lái)指向所關(guān)聯(lián)的phonewindow對(duì)象。在decorview的構(gòu)造函數(shù)中也為mWindow賦值了。
此方法new了一個(gè)DecorView對(duì)象return了回來(lái)。接著調(diào)用generateLayout方法為mContentParent變量賦值。此方法就是設(shè)置DecorView的具體細(xì)節(jié),
int layoutResource;
int features = getLocalFeatures();
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;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = R.layout.screen_progress;
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
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;
}
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
以上代碼的第二行通過(guò)getLocalFeatures()方法獲取我們?cè)O(shè)置想要的樣式,這也是為什么調(diào)用requestWindowFeature()方法要在setContentView()之前調(diào)用的原因,如果在之后調(diào)用,就獲取不到我們想要設(shè)置的值,也就不會(huì)得到我們想要的效果。方法中一大段if的判斷就是根據(jù)不同的feature來(lái)選擇要在decorview下添加什么樣的布局樣式。如果什么都沒(méi)有設(shè)置,會(huì)默認(rèn)選擇R.layout.screen_simple這個(gè)布局,然后會(huì)進(jìn)入mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);這個(gè)方法中
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
此段代碼可以很清晰的看出是將layoutResource的布局inflater為一個(gè)view對(duì)象,并添加到了decorview下,并將decorview中的mContentRoot變量設(shè)置為了這個(gè)view對(duì)象。返回上一段代碼,findViewById調(diào)用的就是Decorview的findviewById,而ID_ANDROID_CONTENT 的值為com.android.internal.R.id.content,看下R.layout.screen_simple布局到底是什么樣子
<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>
可以很明顯的看出,這個(gè)findviewById返回的這個(gè)ViewGroup就是此布局中的FrameLayout。
接著回到PhoneWindow的setContentView方法中執(zhí)行mLayoutInflater.inflate(layoutResID, mContentParent);此方法會(huì)調(diào)用下面方法,resource為我們?cè)赼ctivity中setContentView()中傳入的布局ID,root為phonewindow的mContentParent,也就是DecorView中為id為content的framelayout
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
首先獲取XmlResourceParser布局解析器,調(diào)用inflate(parser, root, attachToRoot); parser為解析器,root為mContentParent,attachToRoot為true.
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
}
return result;
}
}
AttributeSet attrs = Xml.asAttributeSet(parser);解析布局封裝到attrs中。View result = root;定義一個(gè)名為result的view指向mContentParent,這個(gè)result最終會(huì)被此方法當(dāng)做返回值返回。然后獲取到根節(jié)點(diǎn)的name,進(jìn)入if判斷,如果根節(jié)點(diǎn)是marge標(biāo)簽,而且如果mContentParent為空,或者attachToRoot為false,會(huì)拋出異常。接著會(huì)調(diào)用rInflate方法,此方法會(huì)在下面描述。如果跟布局不是marge標(biāo)簽,會(huì)進(jìn)入else,一般情況下,都會(huì)走這里的代碼。首先調(diào)用createViewFromTag方法返回一個(gè)跟布局標(biāo)簽的實(shí)例化對(duì)象temp。接著判斷root如果不為空,調(diào)用root.generateLayoutParams方法返回該ViewGroup在xml布局中對(duì)應(yīng)的LayoutParams。在此情景中,會(huì)返回給我們Framelayout.LayoutParams類型的layoutparams,此時(shí)判斷attachToRoot為false,會(huì)走temp.setLayoutParams(params);但是此時(shí)attachToRoot為true,所以不會(huì)走此方法。來(lái)到了最重要的一句代碼rInflateChildren(parser, temp, attrs, true);進(jìn)入此方法就是調(diào)用的上面沒(méi)展開(kāi)的rInflate方法。
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (finishInflate) {
parent.onFinishInflate();
}
}
此方法獲取到布局的深度之后,while循環(huán)里開(kāi)始執(zhí)行以下邏輯:獲取跟布局,進(jìn)入if判斷,如果不是requestFocus、tag、include、merge標(biāo)簽,就會(huì)進(jìn)入else,此時(shí)的情景也是進(jìn)去else,繼續(xù)分析else的代碼,跟上一個(gè)方法很類似,也是根據(jù)標(biāo)簽名創(chuàng)建對(duì)象,然后獲取layoutparams,將此view添加到他的parent中,重復(fù)調(diào)用rInflateChildren方法。這個(gè)深度遍歷過(guò)程結(jié)束后,DecorView中的framelayout已經(jīng)被添加上了我們需要添加的布局。但是這僅僅是devorview有了視圖,它又是怎樣展示到了我們的activity上的呢?它們之間怎么關(guān)聯(lián)在一起的呢?那就去activity的啟動(dòng)過(guò)程尋找看看了。
布局與Activity的關(guān)聯(lián)
來(lái)到ActivityThread類中handleLaunchActivity方法中,Activity a =performLaunchActivity(r, customIntent);進(jìn)入performLaunchActivity方法通過(guò)反射創(chuàng)建了該Activity實(shí)例,之后調(diào)用了該實(shí)例的attach方法,此方法中執(zhí)行了一些與window有關(guān)的代碼
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
該Activity對(duì)象實(shí)例化了一個(gè)PhoneWindow的成員變量,并設(shè)置了該windos對(duì)象的callback就是此Activity。然后執(zhí)行了此代碼,調(diào)用了activity的onCreate函數(shù),mInstrumentation.callActivityOnCreate(activity, r.state);而onCreate中調(diào)用了我們熟悉的setContentView(R.layout.activity_main);完成了我們上面DecorView初始化,并把布局添加到DecorView中的framelayout中。
回到handleLaunchActivity方法中,初始化完Activity
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
}
此時(shí)a!=null成立,進(jìn)入if執(zhí)行了handleResumeActivity方法,重點(diǎn)看此方法中的這一段代碼
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
主要看此段代碼的最后一行,獲取到Activity的windowmanger對(duì)象,執(zhí)行wm.addView(decor, l);WindowManager是個(gè)接口,他的實(shí)現(xiàn)類是WindowManagerImpl,所以看它的addView方法,他的addView方法又調(diào)用了WindowManagerGlobal的addView。所以,直接查看WindowManagerGlobal的addView方法
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//......
ViewRootImpl root;
View panelParentView = null;
//......
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
創(chuàng)建了一個(gè)ViewRootImpl類的實(shí)例,把decorview, viewRootImpl, wparams添加到相應(yīng)的集合中,接著執(zhí)行了root.setView方法,此方法重點(diǎn)有三部分代碼
-
requestLayout();:會(huì)執(zhí)行scheduleTraversals方法,此方法又會(huì)執(zhí)行一個(gè)TraversalRunnable的runnable對(duì)象,此對(duì)象run方法的實(shí)現(xiàn)是一個(gè)doTraversal();方法,它又會(huì)執(zhí)行performTraversals();而此方法會(huì)依次執(zhí)行performMeasure、performLayout、performDraw來(lái)進(jìn)行繪制。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
通過(guò)跨進(jìn)程方式通知WindowManagerService來(lái)添加此window.
-
view.assignParent(this);將此ViewRootImpl的實(shí)例設(shè)置為該decorview的mParent。所以每個(gè)view調(diào)用requestLayout()方法進(jìn)行重繪的時(shí)候,都會(huì)調(diào)用到父類的requestLayout,會(huì)一直調(diào)用到ViewRootImpl的requestLayout,而去執(zhí)行scheduleTraversals()方法進(jìn)行重繪。
總結(jié)
可以看到,
- Windowmanager是單例的,它對(duì)所有的Window進(jìn)行管理的類。它內(nèi)部管理著ViewRootImpl,View,WindowManager.LayoutParams
- window是描述窗口的抽象類,它是一個(gè)抽象的概念,沒(méi)有具體的體現(xiàn)。所有的視圖都依附它而存在。他的實(shí)現(xiàn)類PhoneWindow提供了操作窗口的實(shí)現(xiàn)。它包含一個(gè)decorview實(shí)例。
- Decorview是所有布局的根view,它承載著對(duì)我們要顯示布局的修飾。
- ViewRootImpl是視圖樹(shù)的頂級(jí)view,是繪制的起點(diǎn)。它是window與view之間的橋梁