UI加载流程
1、Activity->setContentView
Android UI的加载流程也就是View被添加到屏幕窗口上的流程,其入口就在setContentView()方法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(this, R.layout.activity_main);
}
2、PhoneWindow->setContentView
通过传入布局资源ID,setContentView方法又做了什么事情呢?经过一系列线索最终找到了的位置也就是Window的唯一实现类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) {
installDecor();//注释1
} 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);//注释2
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
3、PhoneWindow->installDecor
注释1处installDecor方法顾名思义就是初始化操作,注释2处就是将布局资源ID填充到mContentParent内容布局容器。先进入installDecor方法:
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);//注释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);//注释2
………
}
注释1处,如果mDecor为空,就调用generateDecor方法,进入该方法就发现通过返回一个new出来DecorView,然后赋值给mDecor。注释2处调用generateLayout方法,那么该方法是如何给mContentParent赋值的呢?
4、PhoneWindow->generateLayout
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
……
//注释1,设置属性
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
requestFeature(FEATURE_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
requestFeature(FEATURE_ACTION_MODE_OVERLAY);
}
……
// 注释2
// Inflate the window decor.
int layoutResource;// 布局资源id
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
// 注释3
// 根据不同feature, 对layoutResource进行不同的赋值操作
// 即后续加载不同的布局,这就很好的解释了为什么我们自己要去getWindow.requestFeature时必须在 // setContent之前的原因
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
...
}
mDecor.startChanging();
// 注释4
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 注释5
// ID_ANDROID_CONTENT = com.android.internal.R.id.content;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
return contentParent;
}
在注释1处我们发现根据系统属性的不同,通过requestFeature和setFlag方法设置了许多属性。在注释2处,看到解析窗口decor的提示,继续往下看,如注释3处,会根据不同的特性对布局资源进行不同的赋值,即后续加载不同的布局(就是不同的ActionBar,TitleBar之类的)。这就是为什么我们自己要去getWindow.requestFeature时必须在 setContent之前的原因。再看注释4处的onResourcesLoaded方法:
4、Decorview->onResourcesLoaded
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
主要逻辑就是将传入layoutResource即布局资源通过addView添加到DecorView中。在回到注释5处,通过findViewById,获取id为com.android.internal.R.id.content的contentView,即内容布局容器,最后返回。分析完installDecor方法,再回到PhoneWindow的setContentView方法的注释2处,调用inflate方法,就是将MainActivity的layoutResID即对应的资源布局,添加到mContentParent内容布局容器。至此setContentView的分析就告一段落。
方法内部逻辑比较多,主要做了以下几件事:
1 installDecor方法内部的generateDecor方法初始化DecorView
2 installDecor方法内部的generateLayout
3 根据不同的系统属性,通过requestFeature和setFlag方法设置不同(feature)特性
4 根据不同的feature,通过onResourcesLoaded方法的addView加载不同的layoutResource(布局资源,一般是ActionBar,Title等)
5 通过findViewById获取固定id为com.android.internal.R.id.content的内容布局容器contentParent
6 返回contentParent
setContentView方法内通过inflate方法将初始的layoutResID对于的布局添加到contentParent布局容器
UI绘制流程
View添加完成后就是绘制流程了,UI的绘制最终调用的是ActivityThread的handleLaunchActivity方法,在其方法内,会调用这个方法:
final Activity a = performLaunchActivity(r, customIntent);
performLaunchActivity会调用activity的onCreate方法,接着会调用handleResumeActivity方法,在这个方法中会调用activity的onResume方法:
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
//注释1 回调activity的生命周期方法onResume
// TODO Push resumeArgs into the activity for consideration
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
final Activity a = r.activity;
……
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();//注释2
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();//注释3 获取wm
WindowManager.LayoutParams l = r.window.getAttributes();//注释4 获取属性
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);//注释5
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
}
在注释2处调用window的getDecorView方法,最终还是调用PhoneWindow的相关方法获取DecorView,在注释3处调用Activity的getWindowManager方法获取ViewManager,在注释4处获取窗口的布局属性对象,在注释5处调用WindowManager的addView方法,进入Activity的getWindowManger方法:
/** Retrieve the window manager for showing custom windows. */
public WindowManager getWindowManager() {
return mWindowManager;
}
在Activity中搜索mWindowManager赋值的逻辑:
发现在attach()方法中通过window获取了wm:
mWindowManager = mWindow.getWindowManager();
接着进入Window中查找mWindowManager赋值的地方:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated;
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
接着进入createLocalWindowManager方法,来到了WindowManagerImpl 即WindowManager的实现类:
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
进入WindowManagerImpl的addView方法:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
接着进入mGlobal即WindowManagerGlobal的addView方法:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
……
root = new ViewRootImpl(view.getContext(), display);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
……
}
在此方法中实例化了一个ViewRootImpl,同时设置布局参数,添加到相关集合,并通过ViewRootImpl的setView方法将View和布局参数等进行了关联,进入setView方法:
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
……
if (newView) {
mSoftInputMode = attrs.softInputMode;
requestLayout();
}
….
}
//请求布局进行绘制
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
最终会调用performTraversals方法进入view绘制的三大流程:
// 执行测量
performMeasure(xxx)
...
// 执行布局
performLayout(xxx);
...
// 执行绘制
performDraw();
UI加载绘制的大致流程到这里也就基本结束