一、前言
Activity是承載UI顯示的重要組件,而setContentView()又占據(jù)著重要的位置,我們平時所看到的界面都是與它有關系。如果沒有設置setContentView()的話Activity就像是沒有女朋友的屌絲,比如Service就是默默的服務。
二、關鍵函數(shù)、類與變量:
attach()、Window、PhoneWindow、DecorView、mConentRoot、mContentParent
Widnow (虛類)
Window的意思是窗口,用來承載布局。PhoneWindow
PhoneWindow是Window的實現(xiàn)類,在Android手機上的使用的便是PhoneWindow(見名知意)。DecorView
DecorView繼承自FrameLayout,并且是PhoneWindow的內(nèi)部類,且被final 和private所修飾。 DecorView包含一個子布局mContentRoot。mContentRoot
mContentRoot為LinearLaoyout線性布局,垂直方向。mContentRoot與DecorView關聯(lián)。mContentRoot包含標題欄和mConntentParent兩個部分,如果標題欄被取消的話那么mContentRoot只包含mContentParent。mContentParent
mContentParent是resLayout布局的根部局,類型為FrameLayout。我們setContentView()所使用的布局最終被mContentParent所俘獲。 成為mContentParent的子布局。
三、setContentView()的內(nèi)幕
看了這么多的名詞其實我們還是一頭霧水。臥槽,他們好陌生呀。PhoneWindow是位于FrameWork層,所以在我們AS的項目中并不能看到。安利一下網(wǎng)址:Github之Android Framework層源碼
簡單來說,在調(diào)用setContentView()之前會先調(diào)用Activity中的attach()函數(shù)。
為什么會先調(diào)用這個函數(shù)呢?先看源碼再說。
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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this);//mWindow被實例化PhoneWindow
mWindow.setCallback(this);//Activity中的事件響應會分發(fā)到Window中
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();//UI線程原來在這里啟動啊。。。
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
在attach()這個函數(shù)里邊我們看到了Window是如何實例化并且與Activity關聯(lián)起來的。
接下來,我們看看setContentView()具體發(fā)生了什么,可能你也看過它的源代碼,但是是這樣的。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
然后是這樣的:
public abstract void setContentView(@LayoutRes int layoutResID);
當然是這樣子,因為這些都是基類,我們要去實現(xiàn)類里邊去看。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) {//mContentParent是layoutResID的父布局。
installDecor(); //重點在這里,初始化DecorView
} 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();
}
}
我們看看installDecor()函數(shù)具體發(fā)生了什么。
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(); //實例化mDecor
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);//實例化mContentParent
...
}
......//忽略不重要的代碼
}
//generateDecor()
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
//generateLayout(mDecor)
protected ViewGroup generateLayout(DecorView decor) {
....
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;//終于發(fā)現(xiàn)了mContentParent的父布局mContentRoot
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//原來mContentParent對應的資源是ID_ANDROID_CONTENT。
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
.....
return contentParent;
}
由上面的分析,我們看到了Activity是怎么和Window關聯(lián)上的,我們的布局文件又是如何與Window關聯(lián)上的。
四、拓展
- 問題一、為什么我們不能在setConentView()后修改window 的Feature
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
//我們發(fā)現(xiàn)了initWindowDecorActionBar();它是干嘛的呢?
/**
* Creates a new ActionBar, locates the inflated ActionBarView,
* initializes the ActionBar with the view, and sets mActionBar.
*/
private void initWindowDecorActionBar() {//初始化ActionBar
Window window = getWindow();
// Initializing the window decor can change window feature flags.初始化window decor會改變widnow feature的一些標志
// Make sure that we have the correct set before performing the test below.
window.getDecorView();
....
}
//我們看看getDecorView();的注釋
/**
* Retrieve the top-level window decor view (containing the standard
* window frame/decorations and the client's content inside of that), which
* can be added as a window to the window manager.
*
* <p><em>Note that calling this function for the first time "locks in"
* various window characteristics as described in
* {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}.</em></p>
*//大概的意思是會鎖定各種window 特點,比如我們的Feature。
* @return Returns the top-level window decor view.
*/
public abstract View getDecorView();
五、總結(jié)
通過對setContentView()內(nèi)幕的挖掘才能更加深度理解Activity是如何呈現(xiàn)布局的。
我們看到的一個小函數(shù),背后還有很多故事。仔細挖掘Android源碼你會發(fā)現(xiàn)很多內(nèi)涵知識。