前言
当我们打开一个activity需要显示内容的时候,只需要在onCreate方法中执行setContentView方法,一行代码搞定,很简单,有么有。但是,有没有想过setConentView方法内部,执行了那些操作,Window、DecorView、ViewRootImpl是怎么回事,本文就来一步步分析其内部工作流程。源码基于Android API 21。
Activity#setContentView
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
...
public Window getWindow() {
return mWindow;
}
可以看到,Activity#setContentView内部调用了mWindow
中的setContentView
方法,那这个mWindow
是什么呢?可以看出它是Window类型的。先来看看mWindow
是在哪里创建的,通过源码可以发现在Activity#attach中对其进行了赋值操作(注:在Activity启动过程中,会执行ActivityThread#performLaunchActivity方法,在这个方法中会调用Activity#attach方法)。
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, IVoiceInteractor voiceInteractor) {
...
//在这里创建Activity所属的Window对象并赋值给mWindow
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);//Activity实现了Window的Callback接口,这里给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);
}
...
创建Window并初始化DecorView
接着上文看下PolicyManager#makeNewWindow
public static Window makeNewWindow(Context context) {
// this will likely crash somewhere beyond so we log it.
Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
"Call to PolicyManager.makeNewWindow is not supported", null);
return null;
}
发现PolicyManager#makeNewWindow方法并没有具体实现,并且PolicyManager中的方法全在接口IPolicy中声明了。
public interface IPolicy {
public Window makeNewWindow(Context context);
public LayoutInflater makeNewLayoutInflater(Context context);
public WindowManagerPolicy makeNewWindowManager();
public FallbackEventHandler makeNewFallbackEventHandler(Context context);
}
其真正的实现是在Policy类中,如下:
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
可以看到,最终是实例化了PhoneWindow,PhoneWindow是Window的唯一子类。
至此,Window的创建过程就结束了,接着看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.
//重点在这里,mContentParent为null,执行installDecor方法。
if (mContentParent == null) {
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 {
//这个方法内部会把我们的布局文件的内容添加mContentParent中。
mLayoutInflater.inflate(layoutResID, mContentParent);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();//执行回调
}
}
这个方法中主要两个工作:1. 如果mContentParent
为null,则执行installDecor
方法,即初始化DecorView;2. 通过mLayoutInflater.inflate(layoutResID, mContentParent
将我们的布局内部添加到mContentParent
;3. 当屏幕的内容发生改变时,执行回调方法onContentChanged。那么这个mContentParent
是指什么呢?这里先保留疑问,我们接下来就会解释。来看下installDecor
方法做了什么工作。
private void installDecor() {
//如果DecorView为null,则创建DecorView
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
...
if (mContentParent == null) {
初始化DecorView的布局结构,获取mContentParent并返回。
mContentParent = generateLayout(mDecor);
...
}
...
}
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
...
//获取一堆主题中设置的属性进行相关设置(好长一堆~~)
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 (...) {
...
}
...
// Inflate the window decor.
//根据不同的窗口特征,layoutResource对应不同的布局文件,用来填充DecorView(又是很长一堆~~)。
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
}
...
//重点在这里
//把对应的布局文件填充并加载到DecorView,初始化DecorView的结构
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//看到contentParent了吧,它对应了DecorView中id为com.android.internal.R.id.content的ViewGroup。
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
return contentParent;
}
这个主要做了三件事:1.根据主题中属性进行一些相关设置2.根据不同的窗口特征,获取对应的布局文件,用来初始化DecorView的布局结构。3.获取contentParent,contentParent对应了DecorView布局文件中id为com.android.internal.R.id.content的ViewGroup,它其实是一个FrameLayout。如图所示:
再回到上文PhoneWindow#setContentView方法中,当获取到mContentParent
后,还会执行mLayoutInflater.inflate(layoutResID, mContentParent)
将我们写的xml布局文件加载到mContentParent,至于加载细节我们这里就不做分析了。
小结:至此,我们已经实例化了Window,初始化了DecorView布局结构,并且把我们布局文件加载到了mContentParent
中。不过DecorView并没有显示出来,因为View并不能单独存在,必须依附于Window。
在Activity在启动流程中(Activity的启动流程很复杂,我们这里不做具体分析),当执行了ActivityThread#performLaunchActivity方法后还会执行ActivityThread#handleResumeActivity方法,在这个方法中首先会调用Activity的onResume
方法,接着调用Activity的makeVisible
方法。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();//获取WindowManager
wm.addView(mDecor, getWindow().getAttributes());//通过WindowManager完成Window的添加过程
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);//让DecorView可见
}
此方法将完成Window的添加过程,以及让DecorView显示出来,至此Activity界面加载流程就结束了
将DecorView添加到Window
接着上文,看看DecorView是如何添加到Window中的。Window其实是个抽象的概念,它并不是一个实体。我们可以把Window理解成一种抽象的功能集合,每个Window都关联一个View和ViewRootImpl。
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
发现ViewManager是一个接口,WindowManager继承了ViewManager,也是一个接口。看它的实现类WindowManagerImpl中的addView
方法。
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
发现WindowManagerImpl#addView并没有实现具体细节,而是交给了WindowManagerGlobal中的addView
去处理。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
synchronized (mLock) {
...
//实例化ViewRootImpl,它是WindowManager和DecorView的纽带
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);//保存所有Window对应的View
mRoots.add(root);//保存所有Window对应的ViewRootImpl
mParams.add(wparams);//保存所有Window对应的布局参数
}
// do this last because it fires off messages to start doing things
try {
//重点是这个方法 开始出发消息做事情
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
我们接着来看ViewRootImpl#setView方法
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...//省略一大波代码
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
//内部最终会调用performTraversals方法开启View的绘制流程。
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
//通过IWindowSession来完成Window的添加过程
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
...//省略一大波代码
}
这个方法很长,我们只看与本文主流程相关部分。有两个重要的地方。
- 通过requestLayout方法开启顶级View的测绘。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
try {
performTraversals();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
private void performTraversals() {
...
//执行测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//执行布局
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
//执行绘制
performDraw();
}
在performTraversals
方法依次会执行performMeasure
、performLayout
以及performDraw
来完成对顶级View的测量、布局、绘制。performMeasure
方法中会调用measure
方法,measure
方法中又会调用onMeasure
方法,在onMeasure
方法中会完成子View的measure过程,measure过程就从父View传到了子View。子View又会重复父View的动作,如此反复,就完成了整个View树的测量过程。performLayout
以及performDraw
方法与performMeasure
类似。可以说View的三大工作流程是performTraversals
开始的。
- 通过WindowSession完成window的添加过程。
先来看下mWindowSession的创建过程
public ViewRootImpl(Context context, Display display) {
mWindowSession = WindowManagerGlobal.getWindowSession();//获取WindowSession
}
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
ValueAnimator.setDurationScale(windowManager.getCurrentAnimatorScale());
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
}
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
}
return sWindowManagerService;
}
}
mWindowSession
类型是IWindowManager,它是一个Binder
对象,可见Window的添加过程是个IPC过程。
openSession
方法是在WindowManagerService具体实现的
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
IInputContext inputContext) {
if (client == null) throw new IllegalArgumentException("null client");
if (inputContext == null) throw new IllegalArgumentException("null inputContext");
Session session = new Session(this, callback, client, inputContext);
return session;
}
在WindowManagerService#openSession实例化了Session并返回。Session是IWindowSession的实现类。addToDisplay
方法也是在Session中具体实现的。
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets,
InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outInputChannel);
}
在这个方法内部又调用了WindowManagerService#addWindow方法,最终完成了Window的添加过程。其具体细节不再说了。。。
希望能对您有所帮助,若文中有错误或表述不当的地方还望指出,互相交流,共同成长!
相关文章:
Android View 测量流程(Measure)源码解析
Android View 布局流程(Layout)源码解析
Android View 绘制流程(Draw)源码解析