高级UI<第七篇>:Activity的绘制流程
在上一篇,我为大家介绍了Activity的启动流程,那么Activity的界面(UI)是怎样显示出来的呢?
我相信,只要有点基础的人都知道,Activity设置UI布局的代码如下:
setContentView(R.layout.activity_main);
此时,利用Android Studio -->Tools-->Layout Insector工具查看当前Activity的布局情况,如图:
![](https://img.haomeiwen.com/i12648131/cf59cf7e45f9682b.png)
setContentView
其实就是将布局添加到截图中的FrameLayout布局中,准确的说,是将布局添加到id为content
的ContentFrameLayout中。
我们可以通过以下语句获取这个View
View activityContentView = findViewById(android.R.id.content);
这样,就可以对content
布局为所欲为了。
在content之上,有一个id为action_mode_bar_stub
的ViewStub控件,这个空间其实就是状态栏的占位符,有关ViewStub控件的知识点将在下一篇说明。
根据setContentView
方法往下跟踪:
AppCompatActivity.java
public void setContentView(@LayoutRes int layoutResID) {
this.getDelegate().setContentView(layoutResID);
}
AppCompatDelegate.java
public abstract void setContentView(@LayoutRes int var1);
跟到这里,我们会发现很难跟踪到PhoneWindow,这时,就需要求助于上一篇从源码分析Activity启动流程(Android 9.0),Activity的启动入口方法是ActivityThread
类中的handleLaunchActivity
方法,找到方法performLaunchActivity
,这个方法中的核心代码如下:
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);
也就是说,将一些数据和Activity绑定在一起,最终调用了Activity中的attach方法源码如下:
Activity.java
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);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
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();
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;
mWindow.setColorMode(info.colorMode);
setAutofillCompatibilityEnabled(application.isAutofillCompatibilityEnabled());
enableAutofillCompatibilityIfNeeded();
}
在这个方法中找到如下代码:
mWindow = new PhoneWindow(this, window, activityConfigCallback);
此时,PhoneWindow
实例被创建。
PhoneWindow
的直接父类是Window
对象,所以PhoneWindow
实例就是UI的Window
对象。
这时,我们再来跟踪setContentView
方法。
在高版本API中,Activity默认的父类是AppCompatActivity
public class MainActivity extends AppCompatActivity {
在以前,可能是这样的
public class MainActivity extends Activity {
现在分别按照这两种继承方式介绍setContentView
的具体流程。
- 如果MainActivity继承Activity
[第一步]
MainActivity.java
setContentView(R.layout.activity_main);
[第二步]
Activity.java
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
这里的getWindow()就是PhoneWindow实例,最终调用PhoneWindow中的setContentView
方法。
[第三步]
PhoneWindow.java
@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;
}
第一次执行的时候,mContentParent必然为null,所以执行installDecor
方法,当mContentParent不为null时,将view添加到mContentParent中。
[第四步]
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
//...(省略)
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
//...(省略)
}
}
根据PhoneWindow创建DecorView对象,载根据DecorView对象创建mContentParent。
- 如果MainActivity继承AppCompatActivity
[第一步]
MainActivity.java
setContentView(R.layout.activity_main);
[第二步]
AppCompatActivity.java
public void setContentView(@LayoutRes int layoutResID) {
this.getDelegate().setContentView(layoutResID);
}
[第三步]
@NonNull
public AppCompatDelegate getDelegate() {
if (this.mDelegate == null) {
this.mDelegate = AppCompatDelegate.create(this, this);
}
return this.mDelegate;
}
[第四步]
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}
现在终于知道了getDelegate()
其实就是AppCompatDelegateImpl
对象。
[第五步]
最终执行AppCompatDelegateImpl类中的setContentView
方法
public void setContentView(int resId) {
this.ensureSubDecor();
ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
contentParent.removeAllViews();
LayoutInflater.from(this.mContext).inflate(resId, contentParent);
this.mOriginalWindowCallback.onContentChanged();
}
this.ensureSubDecor()
的作用是初始化id为decor_content_parent
的ViewGroup实例,即mSubDecor实例,这里的ID:16908290就相当于android.R.id.content
。
[第六步]
AppCompatDelegateImpl.java
private void ensureSubDecor() {
if (!this.mSubDecorInstalled) {
this.mSubDecor = this.createSubDecor();
CharSequence title = this.getTitle();
if (!TextUtils.isEmpty(title)) {
if (this.mDecorContentParent != null) {
this.mDecorContentParent.setWindowTitle(title);
} else if (this.peekSupportActionBar() != null) {
this.peekSupportActionBar().setWindowTitle(title);
} else if (this.mTitleView != null) {
this.mTitleView.setText(title);
}
}
this.applyFixedSizeWindow();
this.onSubDecorInstalled(this.mSubDecor);
this.mSubDecorInstalled = true;
AppCompatDelegateImpl.PanelFeatureState st = this.getPanelState(0, false);
if (!this.mIsDestroyed && (st == null || st.menu == null)) {
this.invalidatePanelMenu(108);
}
}
}
[第七步]
AppCompatDelegateImpl.java
private ViewGroup createSubDecor() {
//...(省略)
this.mWindow.setContentView(subDecor);
//...(省略)
}
createSubDecor
方法中,有一句代码,也是上面指出的唯一一行代码,这句代码其实就是调用了PhoneWindow中的setContentView
方法。
[第八步]
PhoneWindow.java
@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;
}
第一次执行的时候,mContentParent必然为null,所以执行installDecor
方法,当mContentParent不为null时,将view添加到mContentParent中。
[第九步]
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
//...(省略)
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
//...(省略)
}
}
根据PhoneWindow创建DecorView对象,载根据DecorView对象创建mContentParent。
到这里Activity的绘制流程已经讲解完了,以下是简化之后的流程。
- 如果MainActivity继承Activity
setContentView(在MainActivity中设置布局)-->执行Activity的setContentView方法-->执行PhoneWindow的setContentView方法-->如果第一次执行,执行PhoneWindow中的installDecor方法,创建DecorView对象,载根据DecorView对象创建mContentParent-->最后将view添加到mContentParent中。(mContentParent其实就相当于android.R.content)
- 如果MainActivity继承AppCompatActivity
setContentView(在MainActivity中设置布局)-->执行AppCompatActivity类中的setContentView方法-->获取AppCompatDelegateImpl对象,调用AppCompatDelegateImpl中的setContentView方法-->执行AppCompatDelegateImpl中 ensureSubDecor方法-->执行AppCompatDelegateImpl中的createSubDecor方法调用PhoneWindow中的setContentView方法-->如果第一次执行,执行PhoneWindow中的installDecor方法,创建DecorView对象,载根据DecorView对象创建mContentParent-->最后将view添加到mContentParent中。(mContentParent其实就相当于android.R.content)
[本章完...]