版权声明:本文为openXu原创文章【openXu的博客】,未经博主允许不得以任何形式转载
在Activity完全解析的第一篇文章 Activtiy完全解析(一、Activity的创建过程)中,我们分析了从调用startActivtiy()
到Activtiy
创建完成的整个过程。其中**step20:ActivtiyThread.handleLaunchActivity(r, null)**这一步中有两个重要的步骤,第一步就是调用performLaunchActivtiy(r, customIntent)
,这个方法中首先创建了需要打开的activity对象,然后调用其activtiy.attach()
方法完成activtiy对象的初始化,最后调用其onCreate()
方法,这时候activity的声明周期就开始了。而在上一篇文章 Activtiy完全解析(二、layout的inflate过程)中,我们分析了在onCreate()
方法中,从调用setContentView()
到layout布局树加载完毕的过程,到此为止,Activtiy
并不会展示到用户眼前,因为layout只是被填充成一个框架了,就像修房子,首先得画出设计图纸,布局树就像是房子的设计图,设计图画好了还得按照设计图纸一砖一瓦的盖房,所有有了布局树,还需要为上面的每一个控件测量大小,安排摆放的位置并画到屏幕上。本文我们一起分析Activity
窗口的测量、布局和绘制的过程。
在讲解之前,我们先通过关键源码了解一些重要的类之间的关系:Activity
、Window
、PhoneWindow
、ViewManager
、WindowManager
、WindowManagerImpl
,WindowManagerGlobal
,WindowmanagerService
:
/**Window系列*/
public abstract class Window {
private WindowManager mWindowManager;
public void setWindowManager(android.view.WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
...
//调用WindowManagerImpl的静态方法new一个WindowManagerImpl对象
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
public android.view.WindowManager getWindowManager() {
return mWindowManager;
}
}
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private DecorView mDecor;
}
/**WindowManager系列,用于管理窗口视图的显示、添加、移除和状态更新等*/
public interface ViewManager{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
public interface WindowManager extends ViewManager {
...
}
public final class WindowManagerImpl implements WindowManager {
//每个WindowManagerImpl对象都保存了一个WindowManagerGlobal的单例
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...
public WindowManagerImpl createLocalWindowManager(android.view.Window parentWindow) {
return new WindowManagerImpl(mDisplay, parentWindow);
}
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
//调用WindowManagerGlobal对象的addView()方法
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
...
}
public final class WindowManagerGlobal {
private static WindowManagerGlobal sDefaultWindowManager;
private static IWindowManager sWindowManagerService;
private static IWindowSession sWindowSession;
//单例
private WindowManagerGlobal() {
}
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
/**WindowManagerGlobal类被加载时,就获取到WindowManagerService对象*/
public static void initialize() {
getWindowManagerService();
}
public static IWindowManager getWindowManagerService() {
//线程安全的
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
}
return sWindowManagerService;
}
}
public static IWindowSession getWindowSession() {
//线程安全的
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
}
}
/**Activity*/
public class Activity ...{
private Window mWindow; //mWindow是PhoneWindow类型
private WindowManager mWindowManager;//mWindowManager是WindowManagerImpl类型
/**
* Activity被创建后,执行attach()初始化的时候,就会创建PhoneWindow对象和WindowManagerImpl对象
*/
final void attach(...) {
...
//①.为activity创建一个PhoneWindow对象
mWindow = new PhoneWindow(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());
}
//②.调用Window的getWindowManager()方法初始化mWindowManager,其实就是new了一个WindowManagerImpl对象
mWindowManager = mWindow.getWindowManager();
}
}
PhoneWindow:在上一篇文章中就讲过,这其实理解起来也比较容易,我们知道activity就是用于展示界面,控制用户交互的,所以activity中一定维护了用于描述界面的对象,还有就是控制窗口显示的管理器对象,实际上这两个对象都是由PhoneWindow
维护的,而每个activity都拥有一个PhoneWindow
对象mWindow
;
WindowManager:用于管理窗口的一些状态、属性以及窗口的添加、删除、更新、顺序等,WindowManager
是ViewManager
的子接口,ViewManager
接口中只有三个抽象方法(addView
、updateViewLayout
、removeView
),可显而知,这个管理器的作用就是控制窗口View的添加、删除以及状态更新的。WindowManager
也是一个接口,他并没有实现ViewManager
中的抽象方法,其实真正的实现类是WindowManagerImpl
。在activity被创建后调用attach()方法初始化的时候,首先创建了PhoneWindow
对象mWindow
,然后调用mWindow
的setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE) ...)
为其mWindowManager
赋值。
WindowManagerGlobal:由于WindowManagerGlobal
的关键代码比较长,稍后讲解的时候再贴代码。WindowManagerGlobal
是单例模式,Activity
在创建完成调用attach()
方法初始化时,会实例化mWindowManager
(WindowManager
的引用指向WindowManagerImpl
的对象),WindowManagerImpl
创建之后,就维护了WindowManagerGlobal
的单例对象,WindowManagerGlobal
中有一个IWindowSession
类型的变量sWindowSession
,他就是用来和远程服务WindowManagerService
通信的代理。
WindowManagerService:跟之前讲解Activity
启动过程中提到的ActivityManagerService
(用于管理Android
组件的启动、关闭和状态信息等)一样,WMS也是一个复杂的系统服务。
在了解WMS之前,首先要了解窗口(Window
)是什么,Android
系统中的窗口是只屏幕上一块用于绘制UI元素并能相应用户交互的矩形区域,从原理上讲,窗口是独自占有一个Surface
实例的显示区域,例如Dialog
、Activity
界面、比值、状态栏、Toast
等都是窗口。Surface
就是一块画布,应用可以通过Cancas
或者OpenGL
在其上面作画,然后通过urfaceFlinger
将多块Surface
的内容按照特定的顺序进行混合并输出到FrameBuffer
,从而将应用界面显示给用户。
既然每个窗口都有一块Surface
供自己涂鸦,必然需要一个角色对所有窗口的Surface进行协调管理,WMS就是用来做这个事情的,WMS掌管Surface
的显示顺序(Z-order)以及位置尺寸,控制窗口动画,并且还是输入系统的重要中转站。
通过上面的分析,我们知道这几个关键类的大概作用,以及他们之间的嵌套关系,接下来,我们就开始分析Activity
的绘制过程:
step1:ActivityThread.handleResumeActivity()
首先接着第一篇文章step20的handleResumeActivity()
方法分析:
//获取activity的顶层视图decor
View decor = r.window.getDecorView();
//让decor显示出来
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;
//将decor放入WindowManager(WindowManagerImpl对象)中,这样activity就显示出来了
wm.addView(decor, l);
wm.addView(decor, l)
实际上就是调用WindowManagerImpl
的addView()
方法将Activity
的根窗口mDecor
添加到窗口管理器中的,而WindowManagerImpl
的addView()
方法又调用的是WindowManagerGlobal
单例的addView()
方法。接下来我们就分析WindowManagerGlobal.addView()
:
step2:WindowManagerGlobal.addView()
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
...
//创建一个视图层次结构的顶部,ViewRootImpl实现所需视图和窗口之间的协议。
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
try {
//将activity的根窗口mDecor添加到root中
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...
throw e;
}
}
}
在这个方法中,首先创建了一个ViewRootImpl
的对象root
,然后调用root.setView()
,将activity的根窗口视图设置给root,下面我们看看ViewRootImpl
类:
step3:ViewRootImpl.setView()
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
final IWindowSession mWindowSession;
public ViewRootImpl(Context context, Display display) {
mWindowSession = WindowManagerGlobal.getWindowSession();
...
}
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...
int res; /* = WindowManagerImpl.ADD_OKAY; */
//请求对应用程序窗口视图的UI作第一次布局,应用程序窗口的绘图表面的创建过程
requestLayout();
...
try {
...
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
...
throw new RuntimeException("Adding window failed", e);
}
...
}
}
@Override
public void requestLayout () {
if (!mHandlingLayoutInLayoutRequest) {
//检查当前线程是否是主线程,对视图的操作只能在UI线程中进行,否则抛异常
checkThread();
//应用程序进程的UI线程正在被请求执行一个UI布局操作
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//Choreographer类型,用于发送消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
}
ViewRootImpl
实现了ViewParent
,它并不是真正的View
(没有继承View),它定义了顶层视图的职责API,比如requestLayout()
请求布局等等,在setView()
方法中,调用了requestLayout()
方法,请求对Activity
根窗口做第一次布局。
requestLayout()
方法中首先调用checkThread()
检查当前线程是否为UI线程,如果不是则抛出异常,然后调用scheduleTraversals()
方法,scheduleTraversals()
方法中调用了mChoreographer.postCallback()
,mChoreographer
中维护了一个Handler
,通过handler机制实现消息调度,并传入一个回调mTracersalRunnable
。mTracersalRunnable
的run方法中调用了doTraversal()
方法,doTraversal()
方法继续调用performTraversals()
:
step4:ViewRootImpl.performTraversals()
private void performTraversals() {
final View host = mView; //mView就是Activity的根窗口mDecor
...
//下面代码主要确定Activity窗口的大小
boolean windowSizeMayChange = false;
//Activity窗口的宽度desiredWindowWidth和高度desiredWindowHeight
int desiredWindowWidth;
int desiredWindowHeight;
...
//Activity窗口当前的宽度和高度是保存ViewRoot类的成员变量mWinFrame中的
Rect frame = mWinFrame;
...
/*
* 接下来的两段代码都是在满足下面的条件之一的情况下执行的:
* 1. Activity窗口是第一次执行测量、布局和绘制操作,即ViewRoot类的成员变量mFirst的值等于true
* 2. 前面得到的变量windowShouldResize的值等于true,即Activity窗口的大小的确是发生了变化。
* 3. 前面得到的变量insetsChanged的值等于true,即Activity窗口的内容区域边衬发生了变化。
* 4. Activity窗口的可见性发生了变化,即变量viewVisibilityChanged的值等于true。
* 5. Activity窗口的属性发生了变化,即变量params指向了一个WindowManager.LayoutParams对象。
*/
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null) {
...
try {
//★请求WindowManagerService服务计算Activity窗口的大小以及内容区域边衬大小和可见区域边衬大小
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
...
} catch (RemoteException e) {
}
...
//将计算得到的Activity窗口的宽度和高度保存在ViewRoot类的成员变量mWidth和mHeight中
if (mWidth != frame.width() || mHeight != frame.height()) {
mWidth = frame.width();
mHeight = frame.height();
}
...
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 一、测量控件大小(根窗口和其子控件树)
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width,
View.MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height,
View.MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
// 一、测量控件大小(根窗口和其子控件树)
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
}else{
...
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
//二、布局过程
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
}
...
boolean skipDraw = false;
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
...
//三、绘制过程
performDraw();
}
} else {
if (viewVisibility == View.VISIBLE) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
...
}
}
mIsInTraversal = false;
}
performTraversals()
方法中,分为四部分,上面很大一段代码主要作用是确定Activity窗口的大小,也就是通过各种判断,是否是第一次?是否需要重新计算大小?最终确定Activity
根窗口的大小并保存起来。第二步就是遍历测量子控件的大小;第三步就是布局;第四部就是绘制。对于Activity窗口大小的计算,这里就不深入探讨,有兴趣可以看看。接下来我们重点分析后面三个步骤,也就是控件的测量、布局、绘制三个过程。
step5: ViewRootImpl.performMeasure()控件测量过程
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
performMeasure()
方法中调用了mView.measure()
方法,mView
就是Activity
的根窗口,他是DecorView
类型(FrameLayout
的子类),这个类定义在PhoneWindow
类中,接下来我们看看DecorView
的measure()
方法(measure()
方法是View中final修饰的方法,不能覆盖):
#step6: View.measure()
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
//如果View的mPrivateFlags的PFLAG_FORCE_LAYOUT位不等于0时,就表示当前视图正在请求执行一次布局操作;
//或者当前的宽高约束条件不等于视图上一次的约束条件时,需要重新测量View的大小。
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
//首先清除所测量的维度标志
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
...
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
//如果需要强制布局操作,或者忽略测量历史,将会调用onMeasure对控件进行一次测量
//sIgnoreMeasureCache是一个boolean值,初始化为sIgnoreMeasureCache = targetSdkVersion < KITKAT;
//意思是如果Android版本低于19,每次都会调用onMeasure(),而19以上时sIgnoreMeasureCache==false
if (cacheIndex < 0 || sIgnoreMeasureCache) {
//调用onMeasure来真正执行测量宽度和高度的操作
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
...
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
//保存这次测量的宽高约束
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
View
中有两个关于测量的方法measure()
和onMeasure()
,measure()
方法是final的,不能被重写。measure()方法中调用了onMeasure()
方法,onMeasure()
方法在View
中的默认实现是测量自身的大小,所以在我们自定义ViewGroup
的时候,如果我们不重写onMeasure()
测量子控件的大小,子控件将会显示不出来的。ViewGroup中没有重写onMeasure()
方法,在ViewGroup
的子类(LinearLayout
、RelativeLayout
等)中会根据容器自身的布局特性,比如LinearLayout
有两种布局方式,水平或者垂直,分别对onMeasure()
方法进行不同的重写。
总结一下就是measure()
方法不能被重写,它会调用onMeasure()
方法测量控件自身的大小,如果控件是一个容器,它需要重写onMeasure()
方法遍历测量所有子控件的大小。还有当我们继承View自定义控件时,在布局文件中明明使用了wrap_content
可结果还是填充父窗体,这是因为View
的onMeasure()
方法的默认实现,如果要按照我们的需求正确的测量控件大小也需要重写onMeasure()
方法。
由于View.measure()
中调用了onMeasure()
方法,所以就调用到了DecorView
的onMeasure ()
方法,DecorView.onMeasure()
中首先对宽高约束做了一些处理后,接着调用了super.onMeasure()
,也就是FrameLayout
中的onMeasure()
方法:
step7: FrameLayout.onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获得一个视图容器所包含的子视图的个数
int count = getChildCount();
final boolean measureMatchParentChildren =
View.MeasureSpec.getMode(widthMeasureSpec) != View.MeasureSpec.EXACTLY ||
View.MeasureSpec.getMode(heightMeasureSpec) != View.MeasureSpec.EXACTLY;
//mMatchParentChildren用于缓存子控件中布局参数设置为填充父窗体的控件
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
//①、遍历根窗口下所有的子控件,挨个测量大小
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//调用measureChildWithMargins来测量每一个子视图的宽度和高度
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//由于FrameLayout的布局特性(Z轴帧布局,向上覆盖),将子控件中,最大的宽高值记录下来
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
//由于现在本容器的宽高都还没有确定下来,子控件设置为填充父窗体肯定没法计算,所以先缓存起来
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
//②、设置FrameLayout的宽高
//上面已经得到子控件中宽高的最大值,然后加上容器(FrameLayout)设置的padding值
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
//判断是否设置有最小宽度和高度,如果有设置,需要和上面计算的值比较,选择较大的值作为容器宽高
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
//是否设置有前景图,如果有,需要考虑背景的宽高
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
//调用resolveSizeAndState()方法根据计算出的宽高值和宽高约束参数,计算出正确的宽高值;
//然后调用setMeasuredDimension()方法设置当前容器的宽高值
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
//③、计算子控件中设置为填充父窗体的控件的大小
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = View.MeasureSpec.makeMeasureSpec(
width, View.MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
height, View.MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
代码中注释已经写的比较清楚了,简单描述一遍,FrameLayout
的onMeasure
方法可以分为三个步骤:
①. 遍历所有子控件,并调用measureChildWithMargin()
方法测量子控件的大小,measureChildWithMargin()
方法是从ViewGroup
中继承下来的,它会调用child.measure()
,如果子控件又是一个容器就会继续遍历child的子控件测量,最后FrameLayout
的所有子控件(包括子容器中的子控件…)都完成了测量;
②. 根据第①步中记录的子控件宽高的最大值,然后加上padding
值以及最小宽高的比较,最终确定FrameLayout
容器的宽高;
③. 重新计算子控件宽高设置为MATCH_PARENT
(填充父窗体 )的宽高
FrameLayout
的onMeasure()
方法执行完毕后,整个Activity
窗体重所有的控件都完成了测量,这时调用view.getMeasuredWidth()/getMeasuredHeight()
就可以获取到控件测量的宽高值了。测量的部分到此就结束了,如果想进一步深入了解控件测量的相关知识可以参考 Android自定义View(三、深入解析控件测量onMeasure)。下面我们接着step4中的第二步**performLayout()**分析窗口的布局过程。
step8:ViewRootImpl.performLayout()请求布局过程
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
...
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
ViewRootImpl.performLayout()
方法中会调用mView
(Activity
根窗口mDecor
)的layout()
方法,为窗口中所有的子控件安排显示的位置,由于不同的容器有不同的布局策略,所以在布局之前首先要确定所有子控件的大小,才能适当的为子控件安排位置,这就是为什么测量过程需要在布局过程之前完成。接着我们看看DecorView
的layout()
方法(layout方法继承自View
):
step9:View.layout()
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
//记录上一次布局后的左上右下的坐标值
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//为控件重新设置新的坐标值,并判断是否需要重新布局
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//onLayout()方法在View中是一个空实现,各种容器需要重写onLayout()方法,为子控件布局
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
从上面的代码中,我们注意到关于布局有两个重要的方法,View.layout()
和View.onLayout()
,这两个方法有什么关系?各自的作用是什么呢?他们都是定义在View
中的,不同的是layout()
方法中有很长一段实现的代码,而onLayout()
确实一个空的实现,里面什么事也没做。
首先我们要明确布局的本质是什么,布局就是为View
设置四个坐标值,这四个坐标值保存在View的成员变量mLeft、mTop、mRight、mBottom
中,方便View
在绘制(onDraw)的时候知道应该在那个区域内绘制控件。而我们看到layout()
方法中实际上就是为这几个成员变量赋值的,所以到底真正设置坐标的是layout()
方法,那onLayout()
的作用是什么呢?
onLayout()
都是由ViewGroup
的子类实现的,他的作用就是确定容器中每个子控件的位置,由于不同的容器有不容的布局策略,所以每个容器对onLayout()方法的实现都不同,onLayout()方法会遍历容器中所有的子控件,然后计算他们左上右下的坐标值,最后调用child.layout()
方法为子控件设置坐标;由于layout()
方法中又调用了onLayout()
方法,如果子控件child也是一个容器,就会继续为它的子控件计算坐标,如果child不是容器,onLayout()
方法将什么也不做,这样下来,只要Activity
根窗口mDecor
的layout()
方法执行完毕,窗口中所有的子容器、子控件都将完成布局操作。
其实布局过程的调用方式和测量过程是一样的,ViewGroup
的子类都要重写onMeasure()
方法遍历子控件调用他们的measure()
方法,measure()
方法又会调用onMeasure()
方法,如果子控件是普通控件就完成了测量,如果是容器将会继续遍历其孙子控件。
继续查看DecorView.onLayout()
方法:
step10:DecorView .onLayout()
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
getOutsets(mOutsets);
if (mOutsets.left > 0) {
offsetLeftAndRight(-mOutsets.left);
}
if (mOutsets.top > 0) {
offsetTopAndBottom(-mOutsets.top);
}
}
DecorView
是FrameLayout
的子类,FrameLayout
又是ViewGroup
的子类,FrameLayout
重写了onLayout()
方法,DecorView
也重写了onLayout()
方法,但是调用的是super.onLayout()
,然后做了一些边界判断,下面我们看FrameLayout.onLayout()
:
step11:FrameLayout .onLayout()
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom,
boolean forceLeftGravity) {
//获取子控件数量
final int count = getChildCount();
//获取padding值
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
//遍历子控件,为其计算左上右下坐标,由于不同容器的布局特性,下面的计算过程都是根据容器的布局特性计算的
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
//调用其layout()方法为子控件设置坐标
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
所有的布局容器的onLayout
方法都是一样的流程,都是先遍历子控件,然后计算子控件的坐标,最后调用子控件的layout()
方法设置布局坐标,但是不同的布局容器有不同的布局策略,所以区别就在于计算子控件坐标时的差异。比如LinearLayout
线性布局,如果是水平布局,第一个子控件的l值是0,r是100,那第二个子控件的l就是101(只是打个比方),而FrameLayout
,如果没有设置padding
,子控件也没设置margin,第一个子控件的l值就是0,第二个子控件的l还是0,这就是不同容器的计算区别。
FrameLayout.onLayout()
方法执行完毕后,整个Activity
的根窗口的布局过程也就完成了。接下来进入第三个过程–绘制过程:
step12:ViewRootImpl.performDraw()控件绘制过程
private void performDraw() {
...
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
...
}
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
final Rect dirty = mDirty;
...
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
...
//使用硬件渲染
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
} else {
...
// 通过软件渲染.
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
...
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
try {
...
canvas = mSurface.lockCanvas(dirty);
...
} catch (Surface.OutOfResourcesException e) {
return false;
} catch (IllegalArgumentException e) {
return false;
}
...
try {
...
try {
...
mView.draw(canvas);
...
} finally {
...
}
} finally {
...
}
return true;
}
ViewRootImpl
的performDraw()
方法调用draw(boolean)
,在这个过程中主要完成一些条件判断,surface的设置准备,以及判断使用硬件渲染还是软件渲染等操作,由于我们主要研究绘制代码流程层面,所以直接看drawSoftware()
方法,对于硬件渲染具体是怎样的有兴趣可以跟踪一下。drawSoftware()
方法中,通过mSurface.locakCanvas(dirty)
拿到画布,然后调用mView.draw(canvas)
,这里的mView
就是Activity
的根窗口DecorView
类型的对象。
step13:DecorView.draw()
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenuBackground.draw(canvas);
}
}
DecorView
重写了View
的draw()
方法,增加了绘制菜单背景的内容,因为Activity
根窗口上会有一些菜单按钮(比如屏幕下方的返回键等),draw()
方法中调用了super.draw(cancas)
,所以我们看看View
的draw()
方法:
step14:View.draw()
public void draw(Canvas canvas) {
...
/*
* 绘制遍历执行几个绘图步骤,必须以适当的顺序执行:
* 1.绘制背景
* 2.如果有必要,保存画布的图层,以准备失效
* 3.绘制视图的内容
* 4.绘制子控件
* 5.如果必要,绘制衰落边缘和恢复层
* 6.绘制装饰(比如滚动条)
*/
// Step 1, 绘制背景
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// 通常情况请跳过2和5步
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, 绘制本控件的内容
if (!dirtyOpaque) onDraw(canvas);
// Step 4, 绘制子控件
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// we're done...
return;
}
//下面的代码是从第一步到第六步的完整流程
...
}
View
的draw()
方法中有六个步骤,其中最为重要的就是第1步绘制背景、第2步绘制内容、第3步绘制子控件;绘制背景我们就不看了,我们看看绘制内容,调用的是onDraw()
方法,View
中的onDraw()
方法是一个空的实现,可以想象,因为View是所有控件的超类,它并不知道他的孩子要怎样画自己,所以绘制的具体操作由孩子重写onDraw()
方法自己绘制,所以DecorView
重写了onDraw()
方法来绘制Activity
的跟窗口,具体怎么画的请跟踪DecorView
的onDraw()
方法:
step15:DecorView.onDraw()
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
mBackgroundFallback.draw(mContentRoot, c, mContentParent);
}
mView
(根窗口)把自己的内容绘制完成之后,紧接着开始遍历绘制他的孩子,View
中的dispatchDraw()
也是一个空的实现,因为超类的后代是不一定有孩子的(比如Button
,TextView
等),dispatchDraw()
是在ViewGroup
中实现的,我们看看ViewGroup.dispatchDraw()
:
#step16:ViewGroup.dispatchDraw()
protected void dispatchDraw(Canvas canvas) {
...
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
...
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
...
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
dispatchDraw()
方法中首先获取子控件的数量childrenCount
,然后遍历所有子控件,调用drawChild()
方法,drawChild()
方法中只是简单的调用child.draw()
,这样就完成了子控件的绘制。细心的可以发现child的draw()
方法又会执行之前DecorView.draw()
的六步(draw()
是在View
里面实现的),所以说,所有的控件在绘制的时候都会调用
draw()方法,
draw()方法中会先调用
onDraw()方法绘制自己,然后调用
dispatchDraw()绘制它的子控件,如果此控件不是
ViewGroup的子类,也就是说是叶子控件,
dispatchDraw()`什么也不做。
到此为止,Activity
的整个显示过程就已经分析完毕,这篇文章主要讲解了View
的测量、布局、绘制流程,这三个流程一共涉及到六个重要的方法measure()
、onMeasure()
、layout()
、onLayout()
、draw()
、onDraw()
;onMeasure()
、onLayout()
、onDraw()
一般都由View
的子类(具体的控件或者布局)重写,measure()
、layout()
、draw()
在View中有默认的实现,他们都是xxx()调用onXxx(),这种形式就能依次完成整个布局树的绘制流程。将这篇文章的流程整理如下图:
##参考源码工程:
https://github.com/openXu/AndroidActivityLaunchProgress