Android源码分析-View是如何绘制到屏幕上的

在Android中如果要显示一个页面,那么只需要在Activity的onCreate()方法中,把我们写好的页面布局layout传入到setContentView()方法中即可,这样Activity就会完成接下来的工作,通过一系列的操作把我们想要的布局页面显示出来,不需要我们再做任何的处理,所以最初的时候一直以为是Activity将layout布局中的控件绘制出来的,但事实究竟是怎样的呢,我们来一探究竟。

首先,我们都知道在onCreate()方法中调用setContentView()函数,Activity并不会立刻将页面显示出来,而是在执行到Activity的onResume()生命周期之后才会将Activity显示出来。所以针对于这个问题 “View是如何绘制到屏幕上的?” ,可以从以下两个方面来进行思考。

  • 调用setContentView之后都做了哪些事?

  • 在onResume这个生命周期里也就是Activity被显示之前做了哪些事?

Activity#setContentView

看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 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();
}

public Window getWindow() {
return mWindow;
}

final void attach(Context context, .....) {
attachBaseContext(context);
//....
mWindow = new PhoneWindow(this, window, activityConfigCallback);
//....
}

可以看到在Activity的setContentView()方法中,调用了getWindow()的setContentView(),Activity在第一时间就将它交到了getWindow()的手里,getWindow()返回了一个Window的对象,Window是一个抽象类,PhoneWindow是它唯一的实现类,并且从代码中我们可以得知mWindow这个对象是在Activity的attach()方法中进行创建的。(attach()这个方法很重要,很多跟Activity相关的重要信息都是在这个方法中进行初始化的,尤其是初始化了和显示相关的信息,是我们需要重点关注的)此时我们的布局如下:
image

PhoneWindow#setContentView

接下来看一下PhoneWindow中的setContentView方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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;
}

在phoneWindow的setContentView()中,主要做了两件事:

  • installDecor:如果mContentParent为null,就会在这个方法中创建DecorView和ContentParent。

  • inflate:通过inflate的方式将我们在Activity中传入的xml布局文件也就是R.layout.activity_main,转换成了树形结构的View,并且把ContentParent作为父节点。

接下来看installDecor方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void installDecor() {
//......
if (mDecor == null) {
mDecor = generateDecor(-1);
//......
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
//......
}

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.
//......
return new DecorView(context, featureId, this, getAttributes());
}

protected ViewGroup generateLayout(DecorView decor) {
//......
if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}

mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//......
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
//......
return contentParent;
}

见上面代码,首先在installDecor()方法中,通过generateDecor()创建出了DecorView,以及通过generateLayout()创建出了ContentParent。

  • generateDecor:创建DecorView,DecorView继承自FrameLayout,此时为我们的布局中创建出了DecorView,布局如下:

image

  • generateLayout:创建ContentParent,在创建的过程中会根据不同的feature创建不同的系统布局。
    这里我们主要来了解一下ContentParent的创建过程,首先会根据不同的的feature创建不同的系统布局,这里的feature较多,我们挑一个最简单也是最普遍的R.layout.screen_simple为例,xml文件代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<LinearLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout height="match parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="a+id/action mode bar stub"
android:inflatedId="a+id/action mode bar"
android:layout="alayout/action mode bar"
android:layout width="match_parent"
android:layout height="wrap content"
android:theme="?attr/actionBarTheme"/>
<Framelavout
android:id="aandroid:id/content"
android:layout_width="match parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontalltop"
android:foreground="?android:attr/windowContentOverlay"/>
</LinearLayout>

R.layout.screen_simple中是由ViewStub和FrameLayout两个部分组成,进入ViewStub的layout布局能够发现其实就是一个ActionBar;这里注意下FrameLayout的id为content,后面会用到。

DecorView#onResourcesLoaded

了解了screen_simple中的内容后,接下来我们看mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)这行代码,在onResourcesLoaded()中先是将布局id转换成View,然后再通过addView将此View添加到DecorView中。onResourcesLoaded()代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
//......
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));
}
//......
}

在onResourcesloaded()之后呢又通过findViewById(ID_ANDROID_CONTENT)查找到了ContentParent,并且将ContentParent返回,这个ID_ANDROID_CONTENT就是 com.android.internal.R.id.content,也就是我们上面提到的R.layout.screen_simple布局文件中id为content的FrameLayout,所以最终将screen_simple中的布局控件添加到DecorView中之后,此时的布局如下:
image

此时onResourcesloaded()方法就结束了,我们再次返回到PhoneWindow的setContentView()方法中的第②部分,可以看到通过inflate的方式将我们从Activity中传入的布局R.layout.activity_main加载到了mContentParent,而这个mContentParent就是我们上一步通过generateLayout()创建出来的ContentParent。

1
mLayoutInflater.inflate(layoutResID, mContentParent);

所以将activity_main添加到ContentParent之后的布局如下:

image

至此,Activity需要显示的内容已经被初始化完成了,但是此时Activity并不是可见的,直到ActivityonResume()阶段才会将PhoneWindow中的DecorView真正的绘制到屏幕上。

总结:
在Activity实例创建好并且执行attach()方法的时候,会为Activity创建一个PhoneWindow,接下来就到了onCreate()的生命周期,在调用setContentView()的时候,如果还没有初始化ContentParent,说明是第一次进行setContentView(),那么就会初始化DecorView,还会给DecorView添加一个系统页面样式的子View(R.layout.screen_simple),那么在系统样式的ViewGroup中,就可以通过id找到用来加载自定义布局的ContentParent,再通过inflate就可以将我们自己写的xml文件(R.layout.main)转化为一颗ViewTree了,这颗ViewTree就在ContentParent里面。

所以setContentView()的作用最终可以总结为:

  • 创建DecorView

  • 创建ContentParent

  • 自定义布局转化为ViewTree,放在ContentParent中

Activity#onResume

ActivityonResume()生命周期是在ActivityThread中的handleResumeActivity()方法中执行的,在这个方法中通过performResumeActivity()触发了onResume()的回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, String reason) {
// TODO Push resumeArgs into the activity for consideration
// skip below steps for double-resume and r.mFinish = true case.
if (!performResumeActivity(r, finalStateRequest, reason)) {
return;
}
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
//......
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
//......
}
//......
}
//......
}

可以看到触发onResume()回调之后,在Activity未关闭并且即将要显示的条件下,先是获取了Activity的DecorView,然后又获取了WindowManager,最后呢再调用了WindowManger的addView()方法将DecorView添加到WindowManager中,我们来看下这个WindowManager是在Activity的attach()中通过setWindowManager创建出来的WindowManagerImpl对象,每一个Activity都会对应一个WindowManager对象,一层一层的进入addView()最终代码来到了WindowMangerGlobal的addView(),如下图所示:

Activity#attach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
final void attach(Context context,//...) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
//......
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();
//......
}

WindowManagerImpl#addView

1
2
3
4
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params){ 
applyDefaultToken(params);
mGlobal.addView(view,params,mContext.getDisplayNoVerify(),mParentWindow,mContext.getUserId());
}

WindowManagerGlobal#addView

1
2
3
4
5
6
7
8
9
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {
......
root = new ViewRootImpl(view.getContext (), display)
view.setLayoutParams(wparams);
mViews.add(view);
mRoots. add(root);
root.setView(view, wparams, panelParentView, userId);
......
}

WindowManagerGlobal是一个单例,在它的addView()方法中如果是首次添加的话就会创建了一个ViewRootImpl,然后将DecorView添加到ViewRootImpl中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
//DecorView对象
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.
//触发布局绘制
requestLayout();
//......
//通知WMS添加窗口
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
mTempControls);
//......
setFrame(mTmpFrames.frame);
//......
//DecorView的parent设置为ViewRootImpl
view.assignParent(this);
}
}
}

在ViewRootImpl的setView()中,首先会调用requestLayout()触发布局的绘制流程,我们熟悉的measure,layout,draw的绘制流程就是从这里开始的,这一步可以确保View被添加到屏幕上之前已经完成了测量和绘制操作。然后会调用mWindowSession的addToDisplayAsUser()通知WMS添加窗口, 这里就涉及到了跨进程通信,方法的最后把decor的parent设置为了ViewRootImpl,这样做的目的就是让ViewRootImpl能够管理整个ViewTree。 接下来我们就来看一下mWindowSession这个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
// Emulate the legacy behavior. The global instance of InputMethodManager
// was instantiated here.
// TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage
InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
IWindowManager windowManager = getWindowManagerService();
//获取WMS进程中的session对象
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}

mWindowSession是WindowManagerGlobal中的一个单例对象,它是IWindowSession类型并且继承自IBinder,它的实现类是System进程中的Session,可以看到它是通过WMS进程的openSession来获取的,至此剩下的工作就交由WMS进程来进行后续的添加工作了。
总结:
在onResume()中会调用WindowManager中的addView()添加DecorView,当WindowManager管理ViewTree的时候会给ViewTree分配一个ViewRootImpl,ViewRootImpl的第一个作用就是管理ViewTree的绘制工作,包括显示、测量,同步刷新以及事件分发等等,第二个作用就是负责与其他的服务进行通信。在同时存在多个Activity的情况下,每个Activity都有自己的PhoneWindow、DecorView以及WindowManagerImpl,WindowManagerGlobal持有每个Activity的RootView,mWindowSession和mWindow是用来和WMS进行双向通信的。

汇总

image