Activity 原理剖析
作为 Android 四大组件之一的 Activity 我的印象中就是用来展示界面的,在很长一段时间里,只要提及界面、UI、View 我脑子里第一个闪过的就是 Activity ,我的理解中一直认为 界面、UI、View == Activity
。其实呢非也!如果你曾经和我一样,说起 Actvity 就是聊他的生命周期,各个方法背的滚瓜烂熟,但也仅限于此而已并没有真正的去认识 Activity 的话,那么请跟我一起从【 Activity 的组成】、【启动】、【显示】来重新认识一下 Activity
一 Activity 的本质
首先明确一下 Activity 的基本概念,我们先来看下 Google 官方对 Activity 给出的定义:
Activity 是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。 每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。
读完了好像也没整明白。
我们抛开之前对 Activity 的印象,从代码层面上来看他就是一个普通的 Java 类,本质上并不是一个 View 或 ViewGroup,他没有继承任何 View 和 ViewGroup 相关类。
从源码中可以看到 Activity 实现了 Window 、KeyEvent 等一系列的 Callback 以及 Listener 。看到这些大概可以知道 Activity 的职责相当于一个控制器了,接收各种回调以及处理监听事件。
基于 Google 官方的定义和 Activity 源码,Activity 的大概职责可以总结成一张图:
1.1 小结
- Activity 本身并不具备 View 任何属性,从代码层面看就是普通的类。
- 本身不是 View ,所以 Activity 本身并不做和 UI、视图控制相关的事,控制 UI、视图的另有其人。
- 在整体架构中扮演
控制器
角色,负责控制生命周期和事件处理。
二 Activity 组成剖析
是时候来揭开 Activity 的庐山真面目了。一图胜千言!先来看一张图。
上图描述了 Activity 从外到内大概的一个层级结构
2.1 DecorView
角色
顶层 View
当前 Activity 所有 View 的祖先, 即当前 Activity 视图树根节点。
作用
- 显示 & 加载布局。但其本身并不做任何 View 呈现。
- 分发 View 层事件,View 层事件由 ViewRoot 分发至 DecorView,再由 DecorView 分发给具体 View。
简介
- DecorView 本质上是一个 FrameLayout ( DecorView 类继承自 FrameLayout )。
其内部包含一个竖直布局的 LinearLayout ,分为2部分:
id 为
title_container
的 TitleBar 。id 为
content
的 Content 。在Activity中通过 setContentView()所设置的布局文件最终就是被添加到此处的 Content 中。- Content 内部的视图树对应的就是 Activity 的布局文件。
2.2 PhoneWindow
角色
视图承载器
作用
承载视图 View 的显示。
简介
PhoneWindow 为 Window 的实现类。每个 Activity 均会创建一个 Window 用以承载 View ,是视图真正的控制者。
2.3 ViewRoot
角色
连接器
作用
- 连接
WindowManager
和DecorView
的纽带。 - 完成 View 绘制的三大流程:
measure
、layout
、draw
。 - 向 DecorView 分发用户发起的 View 层事件,如 KeyEvent,TouchEvent。
简介
对应实现类为 ViewRootImpl ,实现了 ViewParent 接口。作为 WindowManagerGlobal 大部分内部实现的实际实现者,需负责与 WindowManagerService 交互通信,以调整窗口的位置大小,以及对来自 WindowManagerService 的事件(如窗口尺寸改变等)作出相应的处理。
WindowManagerGlobal 方法实际实现如:
1
2
3
4
5
6
7
8
9
10
11
12
13public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
ViewRootImpl root;
......
root = new ViewRootImpl(view.getContext(), display);
try {
//最终实现,调用 ViewRootImpl setView 方法。
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
......
}
}与 WindowManagerService 交互通信:
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
27public ViewRootImpl(Context context, Display display) {
......
mWindowSession = WindowManagerGlobal.getWindowSession();
......
}
//与 WindowManagerService 建立远程连接
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
三 Activity 显示
在简单分析了 Activity 各个组成元件之后,接下来就是看这些元件是如何完成完整的 Activity 组装并将 View 呈现到用户面前。
一图胜千言:
下面跟随上图的流程走一遍源码(为避免篇幅过长只展示重点代码)。
3.1 Activity 启动
这部分主要做的是一些初始化设置的工作,为 Actvity 最终呈现在用户面前做准备。
1.初始化 Looper 创建消息循环
做为 Android 应用程序的入口类,先来看下 ActivityTread 做了什么工作。
源码路径:ActivityTread 类 main 方法。1 | public static void main(String[] args) { |
方法解析:
ActivityTread 的 main 方法初始化了一个 Looper消息循环用以接收主线程的操作消息。
关于Looper消息循环详细介绍可以查看
Android Handler浅析一文,这里就不再展开。
2.接收 LAUNCH_ACTIVITY 消息
Application 启动后,主线程会收到 LAUNCH_ACTIVITY
消息,该消息由 ActivityTread 内部类 H (继承自 Handler)
接收。
1 | public void handleMessage(Message msg) { |
接收消息后会调用 handleLaunchActivity()
方法,下面重点都在 handleLaunchActivity()
方法里面了。
1 | private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) { |
方法解析:
次方法内部重点看 performLaunchActivity()
和 handleResumeActivity()
方法的调用。
先来看 performLaunchActivity()
方法,此方法就开始了我们上图的第 3~9
步的流程,直到流程走完,方法调用栈会回退至 handleLaunchActivity()
中继续往下执行,调用 handleResumeActivity()
。
3. 创建 Activity 对象
源码路径:ActivityTread 类 performLaunchActivity 方法。1 | private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { |
方法解析:
performLaunchActivity
方法做了三件重要的事:
- 通过当前 Activity 的类名为当前的 Activity 创建一个对象。
- 调用
activity.attach
方法为当前 Activity 初始化一个 Window 对象。 - 通过 Instrumentation 调用 Activity 的 onCreate 方法。
5.创建 PhoneWindow对象
performLaunchActivity
方法中调用 activity.attach
方法为当前 Activity 初始化一个 Window 对象,我们来看下 activity.attach
方法的具体实现。
1 | final void attach(Context context, ActivityThread aThread, |
attach
方法中为 Activity 的 mWindow 变量实例化了一个 PhoneWindow 对象,PhoneWindow 是 Window 的一个具体实现,这在上文中已经说过,就不再赘述了。
6. 执行 Activity onCreate()
performLaunchActivity
方法中通过 Instrumentation 调用 Activity 的 onCreate
方法,该方法的具体实现在 Activity 中。
一般来说我们都会重写 Activity 的 onCreate 方法并在里面调用 setContentView
。下面来看下我们熟悉的 setContentView
里面做了些什么。
7. 调用 setContentView()
源码路径:Activity 类 setContentView 方法。1 | public void setContentView(@LayoutRes int layoutResID) { |
Activity 的 setContentView
实际的实现来自 PhoneWindow,可以看下 getWindow()
方法返回的其实就是步骤5
里 attach
方法赋值的 mWindow
对象。
1 | public Window getWindow() { |
既然这样我们不妨跟进 PhoneWindow 中一探究竟:
源码路径:PhoneWindow 类 setContentView 方法。1 |
|
方法解析:
mContentParent
这个变量就是我们上文在介绍DecroView
时提到的 FrameLayout 对应的布局部分。installDecor()
方法为 PhoneWindow 初始化一个 DecroView 对象。- 一番骚操作之后将当前 Activity 的布局添加至
mContentParent
中。
8. 初始化 DecorView
PhoneWindow 的 setContentView 方法会为当前的 Window 初始化一个 DecorView。
源码路径:PhoneWindow 类 installDecor 方法。1 | private void installDecor() { |
方法解析:
1.调用
generateDecor
方法创建 DecorView 对象,generateDecor
实现很简单直接。源码路径:PhoneWindow 类 generateDecor 方法。
1 | protected DecorView generateDecor(int featureId) { |
2. 调用
generateLayout
方法为 DecorView 的 content 设置布局格式。源码路径:PhoneWindow 类 generateLayout 方法。
1 | protected ViewGroup generateLayout(DecorView decor) { |
9. 添加布局到 DecorVIew Content,设置布局格式
这个步骤的源码步骤7
中已经贴出来了,在 setContentView
方法中:1
2//为 mContentParent 添加布局文件
mLayoutInflater.inflate(layoutResID, mContentParent);
到这里 handleLaunchActivity
方法中的 performLaunchActivity
方法这条线就差不多走完了,现在回到 handleLaunchActivity
方法中继续往下执行,下面的关键方法是 handleResumeActivity
。
3.2 Activity 显示
先看 handleResumeActivity
方法的重点代码。
1 | final void handleResumeActivity(IBinder token, |
10. 执行 Activity onResume()
handleResumeActivity
方法中通过调用 performResumeActivity
方法来执行 Activity onResume() 方法。具体实现有兴趣的可以自己跟进源码进去看下,这里就不再贴源码了,文章篇幅已经长的头皮发麻了。
11. 添加 DecorView 到 WindowManager
handleResumeActivity
方法中通过调用 WindowManager 的 addView
方法将之前流程中已赋值的 DecorView 对象添加至当前 Window。
12. 初始化创建 ViewRootImpl
初始化 ViewRootImpl 工作就是在 WindowManager 的 addView
方法中进行的。而 WindowManager 是一个接口,对应的实现类是 WindowManagerImpl
,那就去 WindowManagerImpl 中找 addView
的具体实现咯。
1 |
|
方法解析:
该方法内部直接调用
WindowManagerGlobal
的 addView
方法,WindowManager 的 addView
方法由 WindowManagerGlobal 代理了。源码路径:WindowManagerGlobal 类 addView 方法。
1 | public void addView(View view, ViewGroup.LayoutParams params, |
13. DecorView 对象传给 ViewRootImpl
WindowManagerGlobal 类 addView 方法在创建 ViewRootImpl 对象后调用其 setView
方法将将 DecorView 实例对象交给 ViewRootImpl 用以绘制 View 。
14. 开始 View 绘制流程,呈现布局
所有一切准备就绪之后我们终于开始对我们 Activity 的布局进行绘制了。最终将调用
ViewRootImpl 的 performTraversals
方法(此方法贼长)开始 View 绘制的三大流程,measure、layout、draw
。
1 | private void performTraversals() { |
OK到此 Activity 的部件组装以及显示流程就梳理了一个大概,文章篇幅有点长,但是内容不算多,贴了很多代码,有兴趣的可以跟着我的流程自己跟进源码看下加深印象。
参考文献: