Activity,Window,View的关联与理解

Activity,Window,View相关

Activity&Window&View

1. 什么是Activity,Window,View以及职能简介

  • Activity:主要负责生命周期与事件分发入口,不直接管理底层绘制。一个Activity通常对应一个PhoneWindowActivity更像控制器,通过回调与Window/View协作。
  • Window:Window是视图承载器,抽象类,Activity中的实现通常是PhoneWindow。它持有DecorView,并负责把页面内容组织到窗口结构中。
  • View:DecorView继承自FrameLayout,作为顶级View容器,内部一般包含标题栏区域与内容栏(android.R.id.content)。Activity通过setContentView()把业务布局放入内容栏。
  • ViewRoot:实现类是ViewRootImpl,是WindowManagerServiceDecorView在应用进程侧的桥接点。ViewRootImpl不属于View树节点,但实现了ViewParent并驱动measure/layout/draw。
alt

2. Activity如何和Window,View关联(附源码)

Activity和Window关联

Activity启动过程

在此简述下Activity的启动过程:

  • 调用ContextImpl.startActivity()实质调用ContextImpl.startActivityForResult()
  • 执行到performLaunchActivity()在其中完成启动流程
  • 通过Instrumentation.newActivity使用类加载器创建Activity对象
  • 通过LoadedApk.makeApplication()尝试创建Application对象(Application已被创建则跳过)
  • 创建ContextImpl对象,并执行Activity.attach()完成一些重要数据的初始化
  • 最终调用Activity.onCreate()完成启动流程。

其中Activity和Window的关联发生在Activity.attach()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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*/);
//进行了PhoneWindow的初始化并进行关联
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
}

其中PhoneWindow就是Activity的根Window,可以在其上添加其他的Window(例如Dialog),PhoneWindow就是ActivityView之间的桥梁,Activity无法直接操作View。

Window和View关联

Activity无法直接和View交互,需要通过Window管理

1
2
3
4
5
6
7
8
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}

public Window getWindow() {
return mWindow;
}

Activity通过setContentView()加载要显示的布局,观察源码可知还是通过Window进行了加载操作。

这里要区分两件事:setContentView()完成的是“把布局inflate进DecorView的内容区”;真正显示到屏幕还需要后续addView -> setView -> performTraversals链路。

加载View

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
51
52
53
54
55
56
 @Override
public void setContentView(int layoutResID) {
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;
}

private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(); //生成DecorView
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor); // 为DecorView设置布局格式,并返回mContentParent
...
}
}
}

protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}

Activity通过setContentView()调用到PhoneWindow.setContentView()执行DecorView的创建流程。

DecorView直接和PhoneWindow进行关联,其内部包含了我们定义的布局(ContentView)以及一个titlebar

显示View

上述方法只是创建了一个DecorView,而没有完成真正的显示流程。接下来需要结合Activity生命周期与窗口添加流程来看“何时可见”。

更完整的显示链路如下:

ActivityThread.handleResumeActivity() -> WindowManagerImpl.addView() -> WindowManagerGlobal.addView() -> ViewRootImpl.setView() -> requestLayout() -> performTraversals() -> draw()

这条链路里,setContentView()负责“装内容”,addView/setView负责“挂到窗口并进入绘制调度”。

生命周期与首帧关系

  • onCreate():完成页面初始化与视图装载。
  • onResume():进入可交互生命周期,但不等于首帧已提交到屏幕。
  • onWindowFocusChanged(true):常用于感知页面真正进入前台焦点状态。

因此,“可见”是一个从生命周期到渲染提交的过程,不是单点事件。

输入事件如何进入View树

窗口建立后,输入事件大致走这条路径:

InputDispatcher -> InputChannel -> ViewRootImpl -> DecorView -> ViewGroup -> View

这也是为什么ViewRootImpl既出现在绘制链路,也出现在输入分发链路中。

View的工作原理

View需要通过Window才能展示在Activity上。

关系速查表

对象 主要职责 是否在View树中 典型关键方法
Activity 生命周期与交互入口 attach()setContentView()
PhoneWindow 窗口容器与页面框架 setContentView()installDecor()
DecorView 页面顶层View容器 dispatchTouchEvent()draw()
ViewRootImpl 驱动绘制与输入桥接 setView()performTraversals()
WMS 系统侧窗口管理 否(系统进程) addWindow()relayoutWindow()

3.总结

Activity负责生命周期与事件入口,Window负责承载与组织页面结构,DecorView是顶层View容器。

setContentView()主要完成布局装载;真正显示依赖WindowManagerGlobal.addView() -> ViewRootImpl.setView() -> performTraversals()

ViewRootImpl是应用进程侧的关键桥接层,既串起绘制流程,也串起输入分发流程。

Activity包含一个PhoneWindow,Activity通过setContentView()把布局放入PhoneWindow的内容区域;随后由WindowManager体系执行addView()/updateViewLayout()/removeView()完成窗口层管理。

从跨进程角度看:Activity启动主链路依赖AMS,窗口添加与更新主链路依赖WMS(经IWindowSession进行通信)。


Activity,Window,View的关联与理解
https://leo-wxy.github.io/2018/04/01/Activity-Window-View的关联与理解/
作者
Leo-Wxy
发布于
2018年4月1日
许可协议