setContentView的過程
基于sdk30
- setContentView是展示頁面重要的方法,需要通過setContentView才能xml顯示到頁面上來
- setContentView有2種傳參方式一種是通過layoutID一種是直接傳遞view 本質(zhì)上都是一樣
- 值得注意的是繼承AppCompatActivity和Activity的流程是稍有差別的。
Activity的過程
- 源碼太多只抄了關(guān)鍵代碼
- getWindow().setContentView(layoutResID);
- getWindow()指的是Window甜奄,Window是一個(gè)抽象類晤硕,唯一實(shí)現(xiàn)類是PhoneWindow邢滑,MockWindow不算他是在com.android.setupwizardlib.test.util目錄
-
Activtiy的window是在attach方法中實(shí)例化的,attach是在ActivityThread.performLaunchActivity放中調(diào)用,(多嘴一句Activtiy侧蘸,Application的實(shí)例化也是在這塊實(shí)現(xiàn)的撤嫩,Activtiy和Application都是通過反射newInstance去創(chuàng)建的)
1
2
- 通過上面分析,其實(shí)最終調(diào)用的是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怎么實(shí)現(xiàn)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實(shí)例化DecorView(注意的是如果是mUseDecorContext為true,也就是通過PhoneWindow(Context context, Window preservedWindow,
ActivityConfigCallback activityConfigCallback)這構(gòu)造參數(shù)實(shí)現(xiàn)寻拂,才會使mUseDecorContext為tur程奠。而這只有Activity new出來的PhoneWindow才調(diào)用了這個(gè)構(gòu)造, 使用這里DecorView的context才會是getApplicationContext())
3
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());
}
- 創(chuàng)建完DecorView的過程看完繼續(xù)分析mContentParent = generateLayout(mDecor); 這部分其實(shí)挺操蛋的只要關(guān)注2個(gè)點(diǎn)就行了
protected ViewGroup generateLayout(DecorView decor) {
// ...省略
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
//...省略
}
-
上面就是通過判斷不同屬性去setFlags以及拿到確定layoutResource的系統(tǒng)預(yù)設(shè)的xml文件
4 onResourcesLoaded這個(gè)就是將layoutResource添加到DecorView里面去
5
- findViewById(ID_ANDROID_CONTENT)這個(gè)就是先通過DecorView的findViewById然后在通過Viewgroup的findViewTraversal循環(huán)拿到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)有沒有過度動畫,如果有就去調(diào)用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);
}
- 看到這得出一個(gè)結(jié)論瞄沙, 無論有沒有動畫都有會走LayoutInflater.inflater方法
- 最后看下inflater到底干啥了,通過套娃調(diào)用找到inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)傳這3個(gè)參數(shù)的inflate
- 知道XmlResourceParser這個(gè)是個(gè)解析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)) 這個(gè)判斷條件是意思是如果根布局是個(gè)merge那就走rInflate這個(gè)方法(關(guān)于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去解析不同的標(biāo)簽直接關(guān)注else的重點(diǎn)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這個(gè)
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;
}
- 發(fā)現(xiàn)這里是通過mFactory2或者mFactory去創(chuàng)建view的垮卓,如果上面createViewFromTag的 View view = tryCreateView(parent, name, context, attrs);不為空了就沒法走LayoutInflater自己的onCreateView了垫桂,mFactory和mFactory2是在構(gòu)造方法的時(shí)候賦值的
protected LayoutInflater(LayoutInflater original, Context newContext) {
mContext = newContext;
mFactory = original.mFactory;
mFactory2 = original.mFactory2;
mPrivateFactory = original.mPrivateFactory;
setFilter(original.mFilter);
initPrecompiledViews();
}
- 去探究下LayoutInflater的創(chuàng)建過程先回到PhoneWindow
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
mRenderShadowsInCompositor = Settings.Global.getInt(context.getContentResolver(),
DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 1) != 0;
}
- 結(jié)合上面的內(nèi)容LayoutInflater其實(shí)在attach通過實(shí)例化Phoneview的時(shí)候就創(chuàng)建了直接看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诬滩,看到這又有點(diǎn)小蒙圈了怎么又用到了LayoutInflater.from()這不是是玩蛇么。直接去看下getBaseContext()是何方神圣
- 回到Activtiy的attach方法看到傳遞了一個(gè)Context然后調(diào)用了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調(diào)用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這個(gè)在SystemServiceRegistry類被加載的時(shí)候就去put了大量對象,后續(xù)只要通過名稱就可以拿到對象了比如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)實(shí)際就是調(diào)用了PhoneLayoutInflater.cloneInContext
public LayoutInflater cloneInContext(Context newContext) {
return new PhoneLayoutInflater(this, newContext);
}
protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
}
- 這樣看來現(xiàn)在LayoutInflater的mFactory和mFactory2還是空的疼鸟,上面的View view = tryCreateView(parent, name, context, attrs);為null這樣就繼續(xù)走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判斷這個(gè)-1==name.indexOf('.')猜惋,如果name不包含.比如TextView,ImgaeView走onCreateView伞辛,否則如自定義com.xxx.xxxView陡舅,androidx.constraintlayout.widget.ConstraintLayout吮旅, androidx.recyclerview.widget.RecyclerView這些需要在xml使用全路徑的view就走createView
- 先看onCreateView,由于上面LayoutInflater的創(chuàng)建是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);
}
- 先假設(shè)generateLayout中 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);糟秘,mLayoutInflater的layout為R.layout.screen_simple黔攒,他的第一個(gè)布局是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加名稱補(bǔ)全得到的view欣鳖,再通過constructor.newInstance去實(shí)例化,如LinearLayout通過循環(huán)補(bǔ)全android.widget.LinearLayout才能用反射創(chuàng)建出來這也就是說名為啥TextView,ImageView茴厉,LinearLayout這些不用在xml寫全路徑(這些都在 "android.widget.",
"android.webkit.",
"android.app."目錄里面這里通過for循環(huán)sClassPrefixList的方式自動補(bǔ)全了路徑)泽台,其他的需要。 - 到了這里if (-1 == name.indexOf('.'))的else判斷也不用再去看了矾缓,最終都是通過全路徑得到class怀酷,通過反射實(shí)例化出來。
- 看下rInflateChildren這個(gè)創(chuàng)建子view
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
- rInflate已經(jīng)扯完了
- 回到inflate中if (TAG_MERGE.equals(name))這個(gè)判斷的地方看下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嗜闻,到這流程其實(shí)已經(jīng)扯完了蜕依。inflate其實(shí)就是通過xml解析出來的名稱
、AttributeSet去反射創(chuàng)建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實(shí)現(xiàn)MenuBuilder.Callback、 LayoutInflater.Factory2接口
- 通過一番源碼跳轉(zhuǎn)到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) {
//重點(diǎn)
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();
//重點(diǎn)
// 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);
//重點(diǎn)
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
//重點(diǎn)
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.
//重點(diǎn)
windowContentView.setId(View.NO_ID);
//重點(diǎn)
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);
}
}
//重點(diǎn)
// 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去調(diào)用了PhoneWindow的setContentView的方法剩下的又是Activity的流程了
- 注意的是下面幾行行代碼
- 找到ContentFrameLayout
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
- 找到android.R.id.content 這個(gè)其實(shí)就是activity流程中PhoneWindow.generateLayout方法創(chuàng)建的layout布局id如下圖
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
6
- 把原來Activity中設(shè)定的布局名稱設(shè)置成null
windowContentView.setId(View.NO_ID);
- 把現(xiàn)在設(shè)定的布局名稱設(shè)置成content翠肘,父布局就變成AppCompatActivity創(chuàng)建的ContentFrameLayout了檐束,就這樣完成了父布局的替換
contentView.setId(android.R.id.content);
- setContentView方法中,不也是將自己Activty的xml布局添加到父布局么束倍,和Activity不同的是這里通過包裝操作將原來的父布局變成了AppCompatActivity的contentParent了
LayoutInflater.from(mContext).inflate(resId, contentParent);
到這里流程講完了總結(jié)一下
Activity被ActivityThread創(chuàng)建出來調(diào)用了attch方法拿到ContextImpl被丧,調(diào)用PhoneWindow的setContentView方先創(chuàng)建了DecorView,通過一番判斷設(shè)定了flag和父布局xml绪妹,然后把這個(gè)父布局添加到DecorView甥桂,在把自己Activty的xml添加到父布局。
AppCompatActivity在Activity的基礎(chǔ)上添加了層父布局喂急,通過改名替換的操作替換了Activity的父怒拒格嘁,后續(xù)自己activity的xml將添加這父布局
LayoutInflater的inflate是將xml解析出來,通過全路徑(ImageView廊移、TextView這些通過循環(huán)補(bǔ)全)名稱和屬性去反射創(chuàng)建View糕簿,并將這個(gè)view添加到參數(shù)root布局里面
流程圖
- Activtiy
Activtiy
-
AppCompatActivity
AppCompatActivity