从源码角度看Activity显示视图流程

简介

之前的《从源码角度看Activity生命周期》分析了运行在system_server进程中的AMS与运行在APP进程中的ActivityThread是如何交互、控制着Activity的生命周期的;《从源码角度看Activity的launchMode与Stack/Task》分析了Activity的launchMode不同是如何导致被启动的Activity在Task与Stack中变化的不同的

这两篇都没有侧重讲Activity与显示视图的关系,但是大多的开发者都知道Activity.onCreate中调用setContentView去设置要显示的视图,在Activity.onResume中才真正的显示视图,这中间的流程又涉及到ViewRootImpl,PhoneWindow,DecorView等等运行在APP进程中的类,同时ViewRootImpl又涉及到与WMS交互的逻辑。本篇文章我将从源码的角度带着大家看看Activity从被创建到显示视图在APP进程的这段流程

整体类图

查看大图

以上的类图涉及到了在APP进程中显示视图使用到的类:

  • ActvityThread: main函数是APP进程的入口,主要用来管理与AMS的交互
  • ActivityClientRecord: Activity在客户端的所涉及到的数据
  • Activity: 用来管理客户端视图、点击、输入等功能的抽象,具有完整的生命周期
  • PhoneWindow: 继承Window,是Activity视图管理的抽象
  • DecorView: 本质上是个FrameLayout,是Activity视图整个控件树的父节点
  • WindowManaegrImpl: 代理类
  • WindowManagerGlobal: WindowManager的实现类
  • ViewRootImpl: APP进程与system_server进程进行视图相关通信的桥梁,同时也管理着APP进程视图方面操作的主要逻辑

setContentView

Activity.setContentView

frameworks/base/core/java/android/app/Activity.java

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
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 调用setContentView讲要显示的视图添加到控件树中
setContentView(R.layout.sample);
}
public void setContentView(@LayoutRes int layoutResID) {
// getWindow返回的是在attach方法初始化的PhoneWindow对象,attach方法是在performLaunchActivity
// 期间、在Activity.onCreate之前被调用
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
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) {
...
mWindow = new PhoneWindow(this);
}

PhoneWindow.setContentView

PhoneWindow的setContentView方法有3种:

  • public void setContentView(int layoutResID): 传入layoutId, PhoneWindow将会将资源文件的视图自动解析并添加到控件树中,使用默认的LayoutParams,将占用父控件的最大空间
  • public void setContentView(View view): 直接传入View添加到控件树中,使用默认的LayoutParams,将占用父控件的最大空间
  • public void setContentView(View view, ViewGroup.LayoutParams params): 直接传入View并且指定LayoutParams控制大小

我们看看使用最频繁并且实现最复杂的 setContentView(int layoutResID)

frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void setContentView(int layoutResID) {
// 如果mContentParent尚未初始化,则mDecorView可能也尚未初始化
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...
} else {
// 解析视图文件,并将解析出来的View以mContentParent为父控件添加进控件树
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
}

PhoneWindow.installDecor

frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

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
private void installDecor() {
// mDecorView是整个控件树最顶部的ViewGroup,必须确保它已经被初始化
if (mDecor == null) {
mDecor = generateDecor();
...
}
// mContentViewParent是Activity中内容的顶部ViewGroup,如果想要在Activity中显示视图,也必须要确保它被初始化
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
...
}
protected DecorView generateDecor() {
// 直接新建并返回了一个DecorView,本质上是一个继承了FrameLayout的ViewGroup
return new DecorView(getContext(), -1);
}
protected ViewGroup generateLayout(DecorView decor) {
...
int layoutResource;
...
// 通过判断feature FLAG要决定需要解析的视图
...
// 解析视图完成后,直接添加为mDecorView的子视图,并且占据了它的全部空间
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
...
// 最后找到id为content的ViewGroup并解析
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}

ID_ANDROID_CONTENT=content,id=content的ViewGroup实际上是layoutResource控件树中的一个子View,以下以R.layout.screen_simple为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<!--可以由开发者插入视图的FrameLayout-->
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

查看大图

以上的图是一类Activity控件树的层级,其中ViewStub是用来显示ActionBar的父节点,mContentParent可以说是Activity视图主内容的父节点

LayoutInflater.inflate

frameworks/base/core/java/android/view/LayoutInflater.java

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 View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
...
// 这边不再细看视图文件的解析过程了,在文件解析完成后会创建一个View对象,
// 随后添加到父控件,也就是mContentParent中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
...
}

Activity.onCreate主要是将APP自定义的视图添加到id=content的mContentParent中,如果添加之前发现mDecorView与mContentParent尚未初始化,那么会先对其进行初始化再添加

handleResumeActivity

frameworks/base/core/java/android/app/ActivityThread.java

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
40
41
42
43
44
45
46
47
48
49
50
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
...
// 执行Activity.onResume方法,打印出 am_on_resume_called 的eventlog
// 此时Activity尚未真正的可见
ActivityClientRecord r = performResumeActivity(token, clearHide);
...
if (r != null) {
if (r.window == null && !a.mFinished && willBeVisible) {
// 获取PhoneWindow对象
r.window = r.activity.getWindow();
// 获取DecorView对系那个
View decor = r.window.getDecorView();
// 先将mDecorView可见性置为不可见
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) {
a.mWindowAdded = true;
// 调用WindowManager.addView进行视图显示流程,期间可能会涉及与WMS的交互
wm.addView(decor, l);
}
...
}
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
...
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
// 将Activity置为可见
r.activity.makeVisible();
}
}
}
...
}
public final ActivityClientRecord performResumeActivity(IBinder token,
boolean clearHide) {
...
r.activity.performResume();
...
EventLog.writeEvent(LOG_AM_ON_RESUME_CALLED,
UserHandle.myUserId(), r.activity.getComponentName().getClassName());
}

WindowManager.addView

addView的流程分为两步重要的流程:scheduleTraversals和perfromTraversals,这两部分我都不准备从代码里去看这中间的流程了,辅以流程图,相信大家也能理解大致的流程

查看大图

这张流程图涉及到从调用WindowManager.addView到调用performTraversals之间的流程,其中调用nativeScheduleVsync将会在native层等待vsync事件,当事件到来时,native层将会调用dispatchVsync方法通知java层可以执行performTraversals方法

关于vsync的作用与实现原理我会在以后的文章中再分析

查看大图

ViewRootImpl.performTraversals所实现的功能就是广大安卓开发者普遍熟知的控件树绘制阶段,这块的代码繁杂,代码量堪比BroadcastQueue.processNextBroadcast,ActiveServices.bringUpServiceLocked,ActivityStackSupervisor.resumeTopActivitiesLocked…这些重量级的AMS组件方法了

这块的逻辑大体就分为大家熟知的三个阶段:测量-布局-绘制,对应View中可以重写的三个方法:onMeasure-onLayout-onDraw。注意的是,这三个方法不是每次调用performTravesals方法时都会被触发,需要满足特定的条件。同时每个方法也可能被调用多测,例如在预测量阶段,ViewRootImpl可能会调用多次View.onMeasure方法

相对比较重要的阶段在标红的色块中。其中的WindowSession.relayout涉及到Window管理服务端的relayout方法,这块的逻辑留给以后关于WMS的博客再写,本篇主要还是写视图显示在APP进程所涉及到的逻辑

总结

本篇文章主要分析在APP进程中,从Activity被创建,再到设置视图,再到如何显示视图,最后到ViewRootImpl向WMS发出 binder call 请求

我们会发现Activity是一个界面周期的抽象,PhoneWindow是Activity涉及视图方面操作的抽象,而DecorView是整个Activity控件树父控件的抽象。ViewRootImpl的performTraversals借助于Vsync机制,相当于是Activity界面显示的心跳操作,没有它可以说就没有可见APP的存在了

最后本篇文章没有列出大多数的分析代码,而是以UML图与逻辑图的形式展出,一来是减少文章幅度方便阅读,二来是方便以后忘记该流程时方便查阅。需要查看细节的同学可以自行查看并分析对应源码

扫码支持0.99元,您的支持将鼓励我继续创作!