簡單的梳理一下setContentView()方法都干了什么吱抚。啟動Activity之后百宇,我們寫的布局文件怎么就展示在了該Activity的頁面之上。我們知道的window秘豹,windowmanager携御,decorview,viewrootImpl它們具體的職責是什么,并且它們之間又存在著什么關系既绕。帶著這些問題啄刹,通過閱讀源碼大致的捋一下思路,對Android布局的繪制過程有一個大概的輪廓框架凄贩。
先進入setContentView方法會看到誓军,會來到Activity的setContentView方法中
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
看到調(diào)用到了getWindow()的setContentView方法,這個window類是一個抽象類怎炊,它有一個唯一的實現(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此時為空债查,會進入到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瓜挽,進入if塊中的generateDecor()方法盹廷,為mDeocr賦值。當mDecor不為null時久橙,就直接調(diào)用mDecor.setWindow(this);
方法俄占,將該phonewindow對象與該decorview相關連,decorview中有成員變量mWindow來指向所關聯(lián)的phonewindow對象淆衷。在decorview的構造函數(shù)中也為mWindow賦值了缸榄。
此方法new了一個DecorView對象return了回來。接著調(diào)用generateLayout方法為mContentParent變量賦值祝拯。此方法就是設置DecorView的具體細節(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);
以上代碼的第二行通過getLocalFeatures()方法獲取我們設置想要的樣式,這也是為什么調(diào)用requestWindowFeature()方法要在setContentView()之前調(diào)用的原因佳头,如果在之后調(diào)用鹰贵,就獲取不到我們想要設置的值,也就不會得到我們想要的效果康嘉。方法中一大段if的判斷就是根據(jù)不同的feature來選擇要在decorview下添加什么樣的布局樣式碉输。如果什么都沒有設置,會默認選擇R.layout.screen_simple這個布局亭珍,然后會進入mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
這個方法中
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為一個view對象敷钾,并添加到了decorview下枝哄,并將decorview中的mContentRoot變量設置為了這個view對象。返回上一段代碼闰非,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>
可以很明顯的看出,這個findviewById返回的這個ViewGroup就是此布局中的FrameLayout财松。
接著回到PhoneWindow的setContentView方法中執(zhí)行mLayoutInflater.inflate(layoutResID, mContentParent);
此方法會調(diào)用下面方法瘪贱,resource為我們在activity中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;
定義一個名為result的view指向mContentParent眨攘,這個result最終會被此方法當做返回值返回主慰。然后獲取到根節(jié)點的name,進入if判斷,如果根節(jié)點是marge標簽鲫售,而且如果mContentParent為空共螺,或者attachToRoot為false,會拋出異常情竹。接著會調(diào)用rInflate方法藐不,此方法會在下面描述。如果跟布局不是marge標簽秦效,會進入else雏蛮,一般情況下,都會走這里的代碼阱州。首先調(diào)用createViewFromTag方法返回一個跟布局標簽的實例化對象temp挑秉。接著判斷root如果不為空,調(diào)用root.generateLayoutParams方法返回該ViewGroup在xml布局中對應的LayoutParams苔货。在此情景中衷模,會返回給我們Framelayout.LayoutParams類型的layoutparams,此時判斷attachToRoot為false蒲赂,會走temp.setLayoutParams(params);
但是此時attachToRoot為true,所以不會走此方法刁憋。來到了最重要的一句代碼rInflateChildren(parser, temp, attrs, true)滥嘴;
進入此方法就是調(diào)用的上面沒展開的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)里開始執(zhí)行以下邏輯:獲取跟布局若皱,進入if判斷镊叁,如果不是requestFocus、tag走触、include晦譬、merge標簽,就會進入else互广,此時的情景也是進去else敛腌,繼續(xù)分析else的代碼,跟上一個方法很類似惫皱,也是根據(jù)標簽名創(chuàng)建對象像樊,然后獲取layoutparams,將此view添加到他的parent中旅敷,重復調(diào)用rInflateChildren方法生棍。這個深度遍歷過程結束后,DecorView中的framelayout已經(jīng)被添加上了我們需要添加的布局媳谁。但是這僅僅是devorview有了視圖涂滴,它又是怎樣展示到了我們的activity上的呢?它們之間怎么關聯(lián)在一起的呢晴音?那就去activity的啟動過程尋找看看了柔纵。
布局與Activity的關聯(lián)
來到ActivityThread類中handleLaunchActivity方法中,Activity a =performLaunchActivity(r, customIntent);
進入performLaunchActivity方法通過反射創(chuàng)建了該Activity實例段多,之后調(diào)用了該實例的attach方法,此方法中執(zhí)行了一些與window有關的代碼
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
該Activity對象實例化了一個PhoneWindow的成員變量首量,并設置了該windos對象的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);
}
此時a!=null成立,進入if執(zhí)行了handleResumeActivity方法杠人,重點看此方法中的這一段代碼
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對象,執(zhí)行wm.addView(decor, l);
WindowManager是個接口嗡善,他的實現(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)建了一個ViewRootImpl類的實例各吨,把decorview, viewRootImpl, wparams添加到相應的集合中,接著執(zhí)行了root.setView方法袁铐,此方法重點有三部分代碼
-
requestLayout();
:會執(zhí)行scheduleTraversals方法揭蜒,此方法又會執(zhí)行一個TraversalRunnable的runnable對象横浑,此對象run方法的實現(xiàn)是一個doTraversal();方法,它又會執(zhí)行performTraversals();而此方法會依次執(zhí)行performMeasure屉更、performLayout徙融、performDraw來進行繪制。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
通過跨進程方式通知WindowManagerService來添加此window.
-
view.assignParent(this);
將此ViewRootImpl的實例設置為該decorview的mParent瑰谜。所以每個view調(diào)用requestLayout()方法進行重繪的時候欺冀,都會調(diào)用到父類的requestLayout,會一直調(diào)用到ViewRootImpl的requestLayout似舵,而去執(zhí)行scheduleTraversals()方法進行重繪脚猾。
總結
可以看到,
- Windowmanager是單例的砚哗,它對所有的Window進行管理的類龙助。它內(nèi)部管理著ViewRootImpl,View,WindowManager.LayoutParams
- window是描述窗口的抽象類,它是一個抽象的概念蛛芥,沒有具體的體現(xiàn)提鸟。所有的視圖都依附它而存在。他的實現(xiàn)類PhoneWindow提供了操作窗口的實現(xiàn)仅淑。它包含一個decorview實例称勋。
- Decorview是所有布局的根view,它承載著對我們要顯示布局的修飾。
- ViewRootImpl是視圖樹的頂級view涯竟,是繪制的起點赡鲜。它是window與view之間的橋梁