首先我们看一下 Android 中的 UI 界面架构:
上面这个图就是 Android 中的 UI 界面架构图,Activity 对象就是 Android System 提供的我们所看到的 UI 界面。从上图可以看到,每个 Activity 都包含一个 Window 对象(Window 类是抽象类,PhoneWindow 是 Window 类的具体实现类),在 Android 中 Window 对象通常由 PhoneWindow 来实现。PhoneWindow 将一个 DecorView 设置为整个应用窗口的根 View。DecorView 作为窗口界面的顶层视图,一般情况下,它内部会包含一个竖直方向的 LinearLayout ,在这个 LinearLayout 里面有上下两部分,上面是 TitleView(标题栏),下面是 ContentView(内容栏)。这个 ContentView 其实就是一个 id 为 content 的 FramLayout。我们在创建一个活动时,在 onCreate() 方法中调用 setContentView(R.layout.activity_main); 时,其实就是将 activity_main.xml 布局文件设置到这个 FrameLayout 里。而 TitleView 多数情况下(不同版本,会有变化)就是默认显示的 ActionBar。
我们知道,通过调用 requestWindowFeature(Window.FEATURE_NO_TITLE) 可以全屏显示界面,也就是没有标题栏,只剩下 ContentView。并且这个方法要在 setContentView 方法之前调用才能生效,下面是隐藏标题栏只显示 ContentView 的代码:
// 当继承自 Activity,并且 requestWindowFeature() 方法调用在 setContentView() 之前才会有效
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
}
}
// 当继承自 AppCompatActivity 时,隐藏标题栏如下即可。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(getSupportActionBar() != null){
getSupportActionBar().hide();
}
}
}
这里我们要注意,并不是调用了 setContentView() 方法之后我们就可以看到视图显示了。而是由 ActivityManagerService 回调 Activity 的 onResume() 方法之后,此时系统才会把整个 DecorView 添加到 PhoneWindow 中,并让其显示出来,从而最终完成界面的绘制。
下面我们看一下,顶层视图 DecorView 怎么添加到 PhoneWindow 中的:
当 Activity 初始化完 Window 并将布局添加到 PhoneWindow 的内部类 DecorView 之后,ActivityThread 类会调用 handleResumeActivity() 方法将顶层视图 DecorView 添加到 PhoneWindow 中:
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
if (r.window == null && !a.mFinished && willBeVisible) {
// 获取当前 activity 的 PhoneWindow 对象
r.window = r.activity.getWindow();
// 获取 PhoneWindow 对象的 DecorView(也就是顶层视图)
View decor = r.window.getDecorView();
// 设置 DecorView 不可见
decor.setVisibility(View.INVISIBLE);
// 获取当前 activity 的 WindowManagerImpl 对象
ViewManager wm = a.getWindowManager();
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
// 标记 DecorView 已经添加到 PhoneWindow
a.mWindowAdded = true;
// WindowManagerImpl 将 DecorView 添加到 PhoneWindow 中
wm.addView(decor, l);
} else {
...
}
}
...
}
}
可以看到最终是通过 WindowManagerImpl 类的 addView() 方法:
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
...
}
继续追踪 WindowManagerGlobal 类中的 addView():
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
...
ViewRootImpl root;
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
try {
// 将 DecorView 设置到 ViewRootImpl 对象中
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...
}
}
继续追踪 ViewRootImpl 类中的 setView() 方法:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
// 将传过来的 DecorView 赋值给全局 mView 参数
mView = view;
// 标记已经添加了 DecorView 到窗口
mAdded = true;
...
// 请求开始布局
requestLayout();
...
}
看一下 requestLayout() 方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
再继续到 scheduleTraversals() 方法:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
...
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
再继续执行 mTraversalRunnable 中的 doTraversal() 方法:
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
继续追踪 doTraversal() 方法:
void doTraversal() {
if (mTraversalScheduled) {
...
try {
// 最终调用 performTraversals() 方法开始真正的 View 绘制流程
performTraversals();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
}
最后看一下 ViewRootImpl 中的 performTraversals() 方法:
private void performTraversals() {
// 这个 mView 就是 DecorView 根布局
final View host = mView;
// mAdded赋值为true,因此条件不成立
if (host == null || !mAdded)
return;
//是否正在遍历
mIsInTraversal = true;
//是否马上绘制View
mWillDrawSoon = true;
//顶层视图DecorView所需要窗口的宽度和高度
int desiredWindowWidth;
int desiredWindowHeight;
//在构造方法中mFirst已经设置为true,表示是否是第一次绘制DecorView
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
//如果窗口的类型是有状态栏的,那么顶层视图DecorView所需要窗口的宽度和高度就是除了状态栏
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
//否则顶层视图DecorView所需要窗口的宽度和高度就是整个屏幕的宽高
DisplayMetrics packageMetrics = mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
}
...
//获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.widthhe和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//执行测量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//执行布局操作
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
//执行绘制操作
performDraw();
}
通过上面的源码追踪,我们可以知道,View 的绘制流程是从 ViewRootImpl 对象中的 performTraversals() 方法开始的。 ViewRootImpl 对象是连接 WindowManager 和 DecorView 的桥梁。在 performTraversals() 方法内部,经过 measure(测量)、layout(布局)、draw(绘制)三个过程才能最终将一个 View 绘制出来,其中 measure 用来测量 View 的宽和高,layout 用来确定 View 在父容器中的放置位置,而 draw 则负责将 View 绘制在屏幕上。
通过 performTraversals() 方法我们可以看到,它最后会依次调用 performMeasure()、performLayout()、performDraw() 这三个方法,这三个方法分别完成顶级 View 的 measure、layout 和 draw 这三大流程,其中在 performMeasure 中会调用 measure() 方法 -> 接着调用 onMeasure() 方法 -> 接着对所有的子元素进行 measure 过程,这个时候 measure 流程就从父容器传递到子元素中了,这样就完成了一次 measure 过程(父容器的 measure 过程完成)。接着子元素会重复父容器的 measure 过程,如此反复就完成了整个 View 树的遍历。同理,performLayout() 和 performDraw() 的传递流程和 performMeasure() 是类似的,唯一不同的是,performDraw() 的传递过程是在 draw() 方法中通过 dispatchDraw() 来实现的,不过这并没有本质区别。
measure 过程决定了 View 的宽和高,measure 完成以后,可以通过 getMeasuredWidth() 和 geyMeasuredHeight() 方法来获取到 View 的宽/高,在几乎所有的情况下它都等同 View 的最终的宽/高,当然有特殊情况,后面会学习。
layout 过程决定了 View 的四个顶点的坐标和实际的 View 的宽/高,完成以后,可以通过getTop()、getBottom()、getLeft() 和 getRight() 来拿到四个顶点的位置,并可以通过 getWidth() 和 getHeight() 方法来拿到 View 的最终宽/高。
draw 过程则最终决定了 View 的显示,只有 draw 方法完成以后,View 的内容才能呈现在屏幕上。
下面是 ViewRootImpl 对象的 performTraversals() 方法的工作流程图: