1. 程式人生 > >Android-setContentView做的一些事

Android-setContentView做的一些事

從MainActivity的setContentView進入根據原始碼追蹤找到AppCompatDelegate的實現類AppCompatDelegateImplV9,從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();
}

將我們MainActivity#setContentView的resId通過LayoutInflater.from(mContext).inflate(resId, contentParent);例項化,新增到介面;
其中mSubDecor則是呼叫我們AppCompatDelegateImplV9#ensureSubDecor()創建出來的

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;
         PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
         if (!isDestroyed() && (st == null || st.menu == null)) {
             invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
         }
     }
 }

mSubDecorInstalled先判斷了是否有這個佈局
其中mSubDecor在mSubDecor = createSubDecor()時候建立例項化

private ViewGroup createSubDecor() {
	// 省略部分程式碼,都是一些屬性的判斷

	// 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;
	
	// 省略部分程式碼,對各種情況進行判斷,對subDecor進行例項化,如果到這步還是為空就丟擲一個異常
	
	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
						+ " }");
	}

	// 省略部分程式碼,都是通過 subDecor 例項化的佈局

	// 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;
}

AppCompatDelegateImplV9#createSubDecor()中先判斷了一系列的屬性,然後就
呼叫mWindow.getDecorView();判斷DecorView是否已經例項化,最後呼叫了mWindow.setContentView(subDecor); 將subDecor設定進去;
Window是一個抽象類,根據說明可以知道PhoneWindow例項化了繼承自這個類,所以可以檢視
PhoneWindow#getDecorView(),在這個方法裡面做了呼叫PhoneWindow#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);

		// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
		mDecor.makeOptionalFitsSystemWindows();

		final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
				R.id.decor_content_parent);
				
		// 省略部分程式碼,都是一些屬性的判斷和控制元件例項化
	}
}

沿著裡面往下走可以知道DecorView是由mDecor = generateDecor(-1)建立生成的;
在PhoneWindow#generateDecor()中通過return new DecorView(context, featureId, this, getAttributes());建立一個DecorView

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, getContext().getResources());
			if (mTheme != -1) {
				context.setTheme(mTheme);
			}
		}
	} else {
		context = getContext();
	}
	// 最後建立一個DecorView返回
	return new DecorView(context, featureId, this, getAttributes());
}

回到AppCompatDelegateImplV9#createSubDecor()中,接著mWindow.getDecorView();往下走,看到mWindow.setContentView(subDecor); 這裡也可以繼續跳到PhoneWindow處搜尋PhoneWindow#setContentView()

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
	// 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)) {
		view.setLayoutParams(params);
		final Scene newScene = new Scene(mContentParent, view);
		transitionTo(newScene);
	} else {
		mContentParent.addView(view, params);
	}
	mContentParent.requestApplyInsets();
	final Callback cb = getCallback();
	if (cb != null && !isDestroyed()) {
		cb.onContentChanged();
	}
	mContentParentExplicitlySet = true;
}

在PhoneWindow#setContentView()中,一開始的mSubDecor通過mContentParent.addView(view, params);新增到mContentParent中
mContentParent是在PhoneWindow#installDecor()裡面在DecorView之後創建出來的並且通過PhoneWindow#generateLayout(DecorView decor)方法創建出來並新增至DecorView裡面

protected ViewGroup generateLayout(DecorView decor) {
	
	// 省略部分程式碼,都是判斷一些主題和屬性

	// 開始將控制元件新增入DecorView
	mDecor.startChanging();
	mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
	
	// 例項化mContentParent,其中findViewById(ID_ANDROID_CONTENT)呼叫的是Window這個類裡面的findViewById,
	// 在裡面通過getDecorView().findViewById(id)進行賦值,getDecorView()就是PhoneWindow的DecorView
	
	ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
	if (contentParent == null) {
		throw new RuntimeException("Window couldn't find content container view");
	}
	if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
		ProgressBar progress = getCircularProgressBar(false);
		if (progress != null) {
			progress.setIndeterminate(true);
		}
	}
	if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
		registerSwipeCallbacks();
	}
	// Remaining setup -- of background and title -- that only applies
	// to top-level windows.
	if (getContainer() == null) {
		final Drawable background;
		if (mBackgroundResource != 0) {
			background = getContext().getDrawable(mBackgroundResource);
		} else {
			background = mBackgroundDrawable;
		}
		mDecor.setWindowBackground(background);
		final Drawable frame;
		if (mFrameResource != 0) {
			frame = getContext().getDrawable(mFrameResource);
		} else {
			frame = null;
		}
		mDecor.setWindowFrame(frame);
		mDecor.setElevation(mElevation);
		mDecor.setClipToOutline(mClipToOutline);
		if (mTitle != null) {
			setTitle(mTitle);
		}
		if (mTitleColor == 0) {
			mTitleColor = mTextColor;
		}
		setTitleColor(mTitleColor);
	}
	mDecor.finishChanging();
	return contentParent;
}

在PhoneWindow#generateLayout(DecorView decor)裡面通過
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
例項化mContentParent,其中findViewById(ID_ANDROID_CONTENT)呼叫的是Window這個類裡面的findViewById,在Window裡面Window#findViewById()通過getDecorView().findViewById(id)進行例項化,getDecorView()就是PhoneWindow的DecorView
整個的setContentView結構大致如下圖(圖片是借用網路上一個哥們的)
在這裡插入圖片描述