这篇我们从源码的角度来讲讲android的是怎样将布局显示出来的,我们写一个Activity都会在onCreate方法中去设置一个布局,废话不多说,接下来我们就去探索一下 setContentView(int layout);这个方法。
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
当我们在继续跟进 getDelegate().setContentView(layoutResID);看到的是一个抽象的方法,要去找到具体实现的方法继续跟进最后在AppCompatDelegateImplV9.java具体的实现
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
接下来我们具体看看 ensureSubDecor()方法
private ViewGroup createSubDecor() {
...
根据不同的样式来加载不同的布局
if (!mWindowNoTitle) {
if (mIsFloating) {
// If we're floating, inflate the dialog title decor
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_dialog_title_material, null);
// Floating windows can never have an action bar, reset the flags
mHasActionBar = mOverlayActionBar = false;
} else if (mHasActionBar) {
/**
* This needs some explanation. As we can not use the android:theme attribute
* pre-L, we emulate it by manually creating a LayoutInflater using a
* ContextThemeWrapper pointing to actionBarTheme.
*/
TypedValue outValue = new TypedValue();
mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);
Context themedContext;
if (outValue.resourceId != 0) {
themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
} else {
themedContext = mContext;
}
// Now inflate the view using the themed context and set it as the content view
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
mDecorContentParent = (DecorContentParent) subDecor
.findViewById(R.id.decor_content_parent);
mDecorContentParent.setWindowCallback(getWindowCallback());
/**
* Propagate features to DecorContentParent
*/
if (mOverlayActionBar) {
mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
if (mFeatureProgress) {
mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
}
if (mFeatureIndeterminateProgress) {
mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
}
}
} else {
if (mOverlayActionMode) {
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_screen_simple_overlay_action_mode, null);
} else {
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
}
....
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);
...
return subDecor;
}
我们可以看到这个方法中根据我们在配置文件中不同的样式来加载对应的根布局,关键的实 mWindow.setContentView(subDecor);那么这个mWindow是什么呢我们继续查看代码最终在Activity中看到了 getWindow()
public Window getWindow() {
return mWindow;
}
最后在Activity中找到attach()方法中找到了Window 的实例PhoneWindow
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);
....
}
接下来我们去看看PhoneWindow的setContentView()方法
@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);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
我们挑个关键的地方来看看 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);
.....
}
installDecor()方法调用generateDecor() 创建了一个DecorView DecorView继承FrameLayout 是Activity的第二层View (我看了很多网上的博客包括有些书中都说DecorView是Activity的根View),DecorView 还包裹了一层ViewRootImpl,ViewRootImpl实现了ViewParent
protected DecorView generateDecor(int featureId) {
.....
//创建了一个DecorView返回
return new DecorView(context, featureId, this, getAttributes());
}
接着我们继续看installDecor()方法里面接着调用了 mContentParent = generateLayout(mDecor); 返回 ViewGroup 作为;
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
........
//加载不同的styleable
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
.................
// Inflate the window decor.
int layoutResource;
............
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");
}
.......
mDecor.finishChanging();
return contentParent;
}
在这个方法中调用了mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);这个方法调用是给DecorView来填充内容的具体看看里面做了什么
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 {
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
上方法中就是将我们的下面布局填充添加到DecorView中
<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>
我们在回到generateLayout()继续往下看 找到R.id.content 空间然后返回 这里其实就是找到我们的上面xml中的FrameLayout作为我们自己布局的父空件,我们在Activity 中onCreate()方法中设置的布局将要天加到中。
再回到PhoneWindow setContentView()方法中来看可以看到mLayoutInflater.inflate(layoutResID, mContentParent); //将布局加到R.id.content中。走到这里我们总算是把setContentView()方法看完了。但是我们的界面还没有显示出来。
2总结:
通过这个层级关系我们可以做了解源码我们可以封装出很多框架,比如加载页面,错误页面,还可以封装标题栏等等。