setContentView的过程
基于sdk30
- setContentView是展示页面重要的方法,需要通过setContentView才能xml显示到页面上来
- setContentView有2种传参方式一种是通过layoutID一种是直接传递view 本质上都是一样
- 值得注意的是继承AppCompatActivity和Activity的流程是稍有差别的。
Activity的过程
- 源码太多只抄了关键代码
- getWindow().setContentView(layoutResID);
- getWindow()指的是Window,Window是一个抽象类,唯一实现类是PhoneWindow,MockWindow不算他是在com.android.setupwizardlib.test.util目录
-
Activtiy的window是在attach方法中实例化的,attach是在ActivityThread.performLaunchActivity放中调用,(多嘴一句Activtiy,Application的实例化也是在这块实现的,Activtiy和Application都是通过反射newInstance去创建的)
- 通过上面分析,其实最终调用的是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) {
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方法,直接看generateDecor怎么实现DecorView(mDecor是DecorView顶层试图最外面的view)
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);
}
//...
}
-
generateDecor方法,直接将context和WindowManager.LayoutParams实例化DecorView(注意的是如果是mUseDecorContext为true,也就是通过PhoneWindow(Context context, Window preservedWindow,
ActivityConfigCallback activityConfigCallback)这构造参数实现,才会使mUseDecorContext为tur。而这只有Activity new出来的PhoneWindow才调用了这个构造, 使用这里DecorView的context才会是getApplicationContext())
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
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());
}
- 创建完DecorView的过程看完继续分析mContentParent = generateLayout(mDecor); 这部分其实挺操蛋的只要关注2个点就行了
protected ViewGroup generateLayout(DecorView decor) {
// ...省略
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
//...省略
}
-
上面就是通过判断不同属性去setFlags以及拿到确定layoutResource的系统预设的xml文件
onResourcesLoaded这个就是将layoutResource添加到DecorView里面去
- findViewById(ID_ANDROID_CONTENT)这个就是先通过DecorView的findViewById然后在通过Viewgroup的findViewTraversal循环拿到view。(Activtiy和Diaolog的findViewById都是通过DecorView的findViewById去找的)
- 看setContentView的最后一部分
//判断是否有过度动画
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
- hasFeature(FEATURE_CONTENT_TRANSITIONS)有没有过度动画,如果有就去调用transitionTo
private void transitionTo(Scene scene) {
if (mContentScene == null) {
scene.enter();
} else {
mTransitionManager.transitionTo(scene);
}
mContentScene = scene;
}
- 第一次mContentScene肯定是null的直接看 scene.enter();
public void enter() {
// Apply layout change, if any
if (mLayoutId > 0 || mLayout != null) {
// empty out parent container before adding to it
getSceneRoot().removeAllViews();
if (mLayoutId > 0) {
LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
} else {
mSceneRoot.addView(mLayout);
}
}
// Notify next scene that it is entering. Subclasses may override to configure scene.
if (mEnterAction != null) {
mEnterAction.run();
}
setCurrentScene(mSceneRoot, this);
}
- 看到这得出一个结论, 无论有没有动画都有会走LayoutInflater.inflater方法
- 最后看下inflater到底干啥了,通过套娃调用找到inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)传这3个参数的inflate
- 知道XmlResourceParser这个是个解析xml的东西就好了,感兴趣的可以百度下。
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
//...省略
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 {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
//...省略
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
//将view添加到rootview
root.addView(temp, params);
}
}
return result;
}
}
- 先看if (TAG_MERGE.equals(name)) 这个判断条件是意思是如果根布局是个merge那就走rInflate这个方法(关于merge、include、ViewStub感兴趣的可以百度下)
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 {
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();
}
}
- 通过while遍历去判断xml的name去解析不同的标签直接关注else的重点createViewFromTag
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
//...省略
try {
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
//...省略
}
- 先看下tryCreateView这个
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
return view;
}
- 发现这里是通过mFactory2或者mFactory去创建view的,如果上面createViewFromTag的 View view = tryCreateView(parent, name, context, attrs);不为空了就没法走LayoutInflater自己的onCreateView了,mFactory和mFactory2是在构造方法的时候赋值的
protected LayoutInflater(LayoutInflater original, Context newContext) {
mContext = newContext;
mFactory = original.mFactory;
mFactory2 = original.mFactory2;
mPrivateFactory = original.mPrivateFactory;
setFilter(original.mFilter);
initPrecompiledViews();
}
- 去探究下LayoutInflater的创建过程先回到PhoneWindow
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
mRenderShadowsInCompositor = Settings.Global.getInt(context.getContentResolver(),
DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 1) != 0;
}
- 结合上面的内容LayoutInflater其实在attach通过实例化Phoneview的时候就创建了直接看LayoutInflater.from
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
- context.getSystemService(),这下子得回到Activity去看了,毕竟这里的context是Activity继承的ContextThemeWrapper的(ContextThemeWrapper继承ContextWrapper,ContextWrapper继承Context抽象类)
- getSystemService通过super之后到了ContextThemeWrapper的getSystemService
@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}
- 看到如果是LAYOUT_INFLATER_SERVICE并且mInflater为null直接走cloneInContext,看到这又有点小蒙圈了怎么又用到了LayoutInflater.from()这不是是玩蛇么。直接去看下getBaseContext()是何方神圣
- 回到Activtiy的attach方法看到传递了一个Context然后调用了attachBaseContext(context)才赋值了getBaseContext()。莫非...
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) {
attachBaseContext(context);
//省略...
}
- ActivityThread.performLaunchActivity调用activity.attach的地方
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//省略...
ContextImpl appContext = createBaseContextForActivity(r);
//省略...
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken);
//...
- 所以到最后终归是走了ContextImpl的getSystemService
@Override
public Object getSystemService(String name) {
//...省略
return SystemServiceRegistry.getSystemService(this, name);
}
- SystemServiceRegistry的getSystemService
public static Object getSystemService(ContextImpl ctx, String name) {
if (name == null) {
return null;
}
final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
//...省略
final Object ret = fetcher.getService(ctx);
//...省略
return ret;
}
- SYSTEM_SERVICE_FETCHERS这个在SystemServiceRegistry类被加载的时候就去put了大量对象,后续只要通过名称就可以拿到对象了比如LAYOUT_INFLATER_SERVICE
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
- 所以上面LayoutInflater.from(getBaseContext()).cloneInContext(this)实际就是调用了PhoneLayoutInflater.cloneInContext
public LayoutInflater cloneInContext(Context newContext) {
return new PhoneLayoutInflater(this, newContext);
}
protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
}
- 这样看来现在LayoutInflater的mFactory和mFactory2还是空的,上面的View view = tryCreateView(parent, name, context, attrs);为null这样就继续走onCreateView或者createView了
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
- 看下if判断这个-1==name.indexOf('.'),如果name不包含.比如TextView,ImgaeView走onCreateView,否则如自定义com.xxx.xxxView,androidx.constraintlayout.widget.ConstraintLayout, androidx.recyclerview.widget.RecyclerView这些需要在xml使用全路径的view就走createView
- 先看onCreateView,由于上面LayoutInflater的创建是mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this),所以要去看下PhoneLayoutInflater.onCreateView也没有重写。
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
- 先假设generateLayout中 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);,mLayoutInflater的layout为R.layout.screen_simple,他的第一个布局是LinearLayout而LinearLayout的全路径为android.widget.LinearLayout。接着往createView看,PhoneLayoutInflater没有重写createView,所以又回到了LayoutInflater.createView
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> clazz = null;
//...
try {
//...
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
//...
constructor = clazz.getConstructor(mConstructorSignature);
//...省略
】
//...
final View view = constructor.newInstance(args);
//..
return view;
}
//...省略
}
- 看到Class是通过prefix加名称补全得到的view,再通过constructor.newInstance去实例化,如LinearLayout通过循环补全android.widget.LinearLayout才能用反射创建出来这也就是说名为啥TextView,ImageView,LinearLayout这些不用在xml写全路径(这些都在 "android.widget.",
"android.webkit.",
"android.app."目录里面这里通过for循环sClassPrefixList的方式自动补全了路径),其他的需要。 - 到了这里if (-1 == name.indexOf('.'))的else判断也不用再去看了,最终都是通过全路径得到class,通过反射实例化出来。
- 看下rInflateChildren这个创建子view
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
- rInflate已经扯完了
- 回到inflate中if (TAG_MERGE.equals(name))这个判断的地方看下else部分
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 {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
//...
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// 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);
}
}
- 又是createViewFromTag和rInflateChildren,到这流程其实已经扯完了。inflate其实就是通过xml解析出来的名称
、AttributeSet去反射创建view然后addView到rootview。 - 回到PhoneWindow中setContentView那句代码
mLayoutInflater.inflate(layoutResID, mContentParent);
- 综合上面的分析,不就是将自己Activty的xml布局添加到父布局么
成热打铁下看下AppCompatActivity的setContentView过程
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
if (Build.VERSION.SDK_INT >= 24) {
return new AppCompatDelegateImplN(context, window, callback);
} else if (Build.VERSION.SDK_INT >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else {
return new AppCompatDelegateImplV14(context, window, callback);
}
}
AppCompatDelegateImplN继承AppCompatDelegateImplV23
AppCompatDelegateImplV23继承AppCompatDelegateImplV14
AppCompatDelegateImplV14继承AppCompatDelegateImplV9
AppCompatDelegateImplV9继承AppCompatDelegateImplBase实现MenuBuilder.Callback、 LayoutInflater.Factory2接口
- 通过一番源码跳转到AppCompatDelegateImplV9的setContentView
@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();
}
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
//重点
mSubDecor = createSubDecor();
// If a title was set before we installed the decor, propagate it now
CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
onTitleChanged(title);
}
applyFixedSizeWindow();
onSubDecorInstalled(mSubDecor);
mSubDecorInstalled = true;
// Invalidate if the panel menu hasn't been created before this.
// Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
// A pending invalidation will typically be resolved before the posted message
// would run normally in order to satisfy instance state restoration.
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!isDestroyed() && (st == null || st.menu == null)) {
invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
}
}
}
private ViewGroup createSubDecor() {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
a.recycle();
throw new IllegalStateException(
"You need to use a Theme.AppCompat theme (or descendant) with this activity.");
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
}
mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
a.recycle();
//重点
// Now let's make sure that the Window has installed its decor by retrieving it
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
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);
}
if (Build.VERSION.SDK_INT >= 21) {
// If we're running on L or above, we can rely on ViewCompat's
// setOnApplyWindowInsetsListener
ViewCompat.setOnApplyWindowInsetsListener(subDecor,
new OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
final int top = insets.getSystemWindowInsetTop();
final int newTop = updateStatusGuard(top);
if (top != newTop) {
insets = insets.replaceSystemWindowInsets(
insets.getSystemWindowInsetLeft(),
newTop,
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom());
}
// Now apply the insets on our view
return ViewCompat.onApplyWindowInsets(v, insets);
}
});
} else {
// Else, we need to use our own FitWindowsViewGroup handling
((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
new FitWindowsViewGroup.OnFitSystemWindowsListener() {
@Override
public void onFitSystemWindows(Rect insets) {
insets.top = updateStatusGuard(insets.top);
}
});
}
}
if (subDecor == null) {
throw new IllegalArgumentException(
"AppCompat does not support the current theme features: { "
+ "windowActionBar: " + mHasActionBar
+ ", windowActionBarOverlay: "+ mOverlayActionBar
+ ", android:windowIsFloating: " + mIsFloating
+ ", windowActionModeOverlay: " + mOverlayActionMode
+ ", windowNoTitle: " + mWindowNoTitle
+ " }");
}
if (mDecorContentParent == null) {
mTitleView = (TextView) subDecor.findViewById(R.id.title);
}
// Make the decor optionally fit system windows, like the window's decor
ViewUtils.makeOptionalFitsSystemWindows(subDecor);
//重点
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
//重点
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
// There might be Views already added to the Window's content view so we need to
// migrate them to our content view
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
// Change our content FrameLayout to use the android.R.id.content id.
// Useful for fragments.
//重点
windowContentView.setId(View.NO_ID);
//重点
contentView.setId(android.R.id.content);
// The decorContent may have a foreground drawable set (windowContentOverlay).
// Remove this as we handle it ourselves
if (windowContentView instanceof FrameLayout) {
((FrameLayout) windowContentView).setForeground(null);
}
}
//重点
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);
contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
@Override
public void onAttachedFromWindow() {}
@Override
public void onDetachedFromWindow() {
dismissPopups();
}
});
return subDecor;
}
- 这里AppCompatActivity通过AppCompatDelegateImplV9包了一层layou去调用了PhoneWindow的setContentView的方法剩下的又是Activity的流程了
- 注意的是下面几行行代码
- 找到ContentFrameLayout
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
- 找到android.R.id.content 这个其实就是activity流程中PhoneWindow.generateLayout方法创建的layout布局id如下图
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
- 把原来Activity中设定的布局名称设置成null
windowContentView.setId(View.NO_ID);
- 把现在设定的布局名称设置成content,父布局就变成AppCompatActivity创建的ContentFrameLayout了,就这样完成了父布局的替换
contentView.setId(android.R.id.content);
- setContentView方法中,不也是将自己Activty的xml布局添加到父布局么,和Activity不同的是这里通过包装操作将原来的父布局变成了AppCompatActivity的contentParent了
LayoutInflater.from(mContext).inflate(resId, contentParent);
到这里流程讲完了总结一下
Activity被ActivityThread创建出来调用了attch方法拿到ContextImpl,调用PhoneWindow的setContentView方先创建了DecorView,通过一番判断设定了flag和父布局xml,然后把这个父布局添加到DecorView,在把自己Activty的xml添加到父布局。
AppCompatActivity在Activity的基础上添加了层父布局,通过改名替换的操作替换了Activity的父怒拒,后续自己activity的xml将添加这父布局
LayoutInflater的inflate是将xml解析出来,通过全路径(ImageView、TextView这些通过循环补全)名称和属性去反射创建View,并将这个view添加到参数root布局里面
- Activtiy
-
AppCompatActivity