解密setContentView
我們先來看一下Android中View視圖在Activity中的整個層級關係:
包含關係:Activity中有個成員變量Window,Window是個抽象類,它的實現類是PhoneWindow,PhoneWindow有一個成員變量DecorView.
Phonewindow對象創建的開始
簡要說一下整個調用流程:
入口:ActivityThread#handleLaunchActivity() ->ActivityThread#performLaunchActivity()
開始創建PhoneWindow : performLaunchActivity() -> Activity.attach()
後續的DecorView的創建主要在SetContentView中,後面會分析到.
// Activity類
final void attach(...) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
// 創建PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
}
setContentView登場
學過Android的都知道,setContentView通常會在Acvitity中的Oncreate方法中調用,那麼SetContentView到底做了什麼呢?
DecorView的創建
DecorView創建來龍去脈 Activity#setConTentView -> PhoneWindow#setConTentView -> PhoneWindow#install -> PhoneWindow#generateDecor
代碼調用鏈如下:
// Activity類
public void setContentView(@LayoutRes int layoutResID) {
// 獲取PhoneWindow對象,調用PhoneWindow中的SetContentView方法
getWindow().setContentView(layoutResID);
// 初始化ActionBar
initWindowDecorActionBar();
}
// PhoneWindow類
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
// 創建DecorView對象實例
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);
}
...
}
private void installDecor() {
mForceDecorInstall = false;
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);
}
...
}
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
// 返回創建的DecorView對象,至此DecorView創建完畢
return new DecorView(context, featureId, this, getAttributes());
}
LayoutInflater.inflate(layoutResID, mContentParent)做了什麼
參數:
-
第一個參數layoutResId:資源文件id(對應xxx.xml)
-
第二個參數mContentParent: viewGroup對象,表示創建的view位於viewGroup下
邏輯處理過程如下:
// LayoutInflater類
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
// 第三個參數表示創建的view是否依附於viewGroup下
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
// 解析xml,返回XmlResourceParser對象,通過該對象可以讀取xml中的view定義
final XmlResourceParser parser = res.getLayout(resource);
try {
// 通過XmlResourceParser創建出一個view層級(一棵view數)
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...
...
// 獲取xml中所有的屬性
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
// Temp is the root view that was found in the xml
// 創建,取得當前xml的根view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
...
...
...
// Inflate all children under temp against its context.
// 創建根view下的子view
rInflateChildren(parser, temp, attrs, true);
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
return result;
}
}
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
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)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} 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 {
// 此處遞歸創建子view,最終會得到一棵view樹
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 (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
總結一下 LayoutInflater.inflate一共做了什麼:
解析xml得到XmlPullParser對象 -> 通過XmlPullParser對象創建出viewRoot -> 把創建處理的view添加到父view中 -> 遞歸創建子view並把創建處理的view添加到父view中,最後得到一棵viewTree
以上就是SetContentView的所有過程
更多Android分享關注公衆號:Android開發