Android中View的绘制是一个面试的必答题,网上他人的博文也很多,本文旨在分析出大致流程。
废话不说,read the fucking source code!
先从ActivityThread主线程启动Activity说起,当Activity初始化 Window和将布局添加到
PhoneWindow的内部类DecorView类之后,ActivityThread类会调用handleResumeActivity方法将顶层视图DecorView添加到PhoneWindow窗口,来看看handlerResumeActivity方法的实现:
2974 final void handleResumeActivity(IBinder token,
2975 boolean clearHide, boolean isForward, boolean reallyResume) {
2976 // If we are getting ready to gc after going to the background, well
2977 // we are back active so skip it.
2978 unscheduleGcIdler();
2979 mSomeActivitiesChanged = true;
2980
2981 // TODO Push resumeArgs into the activity for consideration
2982 ActivityClientRecord r = performResumeActivity(token, clearHide);
2983
2984 if (r != null) {
2985 final Activity a = r.activity;
2986
2987 if (localLOGV) Slog.v(
2988 TAG, "Resume " + r + " started activity: " +
2989 a.mStartedActivity + ", hideForNow: " + r.hideForNow
2990 + ", finished: " + a.mFinished);
2991
2992 final int forwardBit = isForward ?
2993 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
2994
2995 // If the window hasn't yet been added to the window manager,
2996 // and this guy didn't finish itself or start another activity,
2997 // then go ahead and add the window.
2998 boolean willBeVisible = !a.mStartedActivity;
2999 if (!willBeVisible) {
3000 try {
3001 willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
3002 a.getActivityToken());
3003 } catch (RemoteException e) {
3004 }
3005 }
3006 if (r.window == null && !a.mFinished && willBeVisible) {
3007 r.window = r.activity.getWindow();
3008 View decor = r.window.getDecorView();
3009 decor.setVisibility(View.INVISIBLE);
3010 ViewManager wm = a.getWindowManager();
3011 WindowManager.LayoutParams l = r.window.getAttributes();
3012 a.mDecor = decor;
3013 l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
3014 l.softInputMode |= forwardBit;
3015 if (a.mVisibleFromClient) {
3016 a.mWindowAdded = true;
3017 wm.addView(decor, l);
3018 }
3019
3020 // If the window has already been added, but during resume
3021 // we started another activity, then don't yet make the
3022 // window visible.
3023 } else if (!willBeVisible) {
3024 if (localLOGV) Slog.v(
3025 TAG, "Launch " + r + " mStartedActivity set");
3026 r.hideForNow = true;
3027 }
3028
3029 // Get rid of anything left hanging around.
3030 cleanUpPendingRemoveWindows(r);
3031
3032 // The window is now visible if it has been added, we are not
3033 // simply finishing, and we are not starting another activity.
3034 if (!r.activity.mFinished && willBeVisible
3035 && r.activity.mDecor != null && !r.hideForNow) {
3036 if (r.newConfig != null) {
3037 if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
3038 + r.activityInfo.name + " with newConfig " + r.newConfig);
3039 performConfigurationChanged(r.activity, r.newConfig);
3040 freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.newConfig));
3041 r.newConfig = null;
3042 }
3043 if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="
3044 + isForward);
3045 WindowManager.LayoutParams l = r.window.getAttributes();
3046 if ((l.softInputMode
3047 & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
3048 != forwardBit) {
3049 l.softInputMode = (l.softInputMode
3050 & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
3051 | forwardBit;
3052 if (r.activity.mVisibleFromClient) {
3053 ViewManager wm = a.getWindowManager();
3054 View decor = r.window.getDecorView();
3055 wm.updateViewLayout(decor, l);
3056 }
3057 }
3058 r.activity.mVisibleFromServer = true;
3059 mNumVisibleActivities++;
3060 if (r.activity.mVisibleFromClient) {
3061 r.activity.makeVisible();
3062 }
3063 }
3064
3065 if (!r.onlyLocalRequest) {
3066 r.nextIdle = mNewActivities;
3067 mNewActivities = r;
3068 if (localLOGV) Slog.v(
3069 TAG, "Scheduling idle handler for " + r);
3070 Looper.myQueue().addIdleHandler(new Idler());
3071 }
3072 r.onlyLocalRequest = false;
3073
3074 // Tell the activity manager we have resumed.
3075 if (reallyResume) {
3076 try {
3077 ActivityManagerNative.getDefault().activityResumed(token);
3078 } catch (RemoteException ex) {
3079 }
3080 }
3081
3082 } else {
3083 // If an exception was thrown when trying to resume, then
3084 // just end this activity.
3085 try {
3086 ActivityManagerNative.getDefault()
3087 .finishActivity(token, Activity.RESULT_CANCELED, null, false);
3088 } catch (RemoteException ex) {
3089 }
3090 }
3091 }
接着看82行也就是源码3055行(基于android5.0) wm.addView(decor, l);
ViewManager wm= a.getWindowManager();这里有个向上转型,wm的具体对象Activity的WindowManagerImpl对象
接着看 WindowManagerImpl的addView方法
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
这里的mGlobal是个单实例的WindowManagerGlobal类。进入WindowManagerGlobal类看addView()方法。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
............
ViewRootImpl root;
View panelParentView = null;
............
//获得ViewRootImpl对象root
root = new ViewRootImpl(view.getContext(), display);
...........
// do this last because it fires off messages to start doing things
try {
//将传进来的参数DecorView设置到root中
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...........
}
}
创建了一个ViewRootImpl对象root,然后调用ViewRootImpl类中的setView成员方法()。继续跟踪代码进入ViewRootImpl类
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//将顶层视图DecorView赋值给全局的mView
mView = view;
.............
//标记已添加DecorView
mAdded = true;
.............
requestLayout();
.............
}
}
接着看ViewRootImpl的内部方法调用
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
................
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
}
}
..............
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
...............
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
try {
performTraversals();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
}
............
最后DecorView的绘制会进入到ViewRootImpl类中的performTraversals()成员方法。
现在我们主要来分析下 ViewRootImpl类中的performTraversals()方法
private void performTraversals() {
// cache mView since it is used so much below...
//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) {
// NOTE -- system code, won't try to do compat mode.
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);
// Ask host how big it wants to be
//执行测量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
........................
//执行布局操作
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
.......................
//执行绘制操作
performDraw();
}
先看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);
}
}
ViewRootImpl的mView就是DecorView对象
回过头看DecorView的两个参数怎么来的
ViewRootImpl#getRootMeasureSpec
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
继续回溯第二个参数在performTraversals函数中找到
WindowManager.LayoutParams lp = mWindowAttributes;
ViewRootImpl#setView
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
mAttachInfo.mDisplayState = mDisplay.getState();
mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
mFallbackEventHandler.setView(view);
mWindowAttributes.copyFrom(attrs);
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
attrs = mWindowAttributes;
......
再向上找setView在handleResumeActivity中被调用,参数l来源
WindowManager.LayoutParams l = r.window.getAttributes();
继续挖,Window#getAttributes
public final WindowManager.LayoutParams getAttributes() {
return mWindowAttributes;
}<pre name="code" class="java"> // The current window attributes.
private final WindowManager.LayoutParams mWindowAttributes =
new WindowManager.LayoutParams();
好吧,有个默认的值,答案就要解开了
android.view.WindowManager
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
到此为止,终于解开了getRootMeasureSpec传入的第二个参数,默认为
MATCH_PARENT。getRootMeasureSpec传入参数后这个函数走的是MATCH_PARENT,使用MeasureSpec.makeMeasureSpec方法组装一个MeasureSpec,MeasureSpec的specMode等于EXACTLY,specSize等于windowSize,也就是为何根视图总是全屏的原因。