前言
前面几节介绍了activity 的启动流程,但是仅仅梳理到了activty的声明周期的调用并没有深入分析每一个生命周期具体做了什么,本小节着重分析一下Activity 的resume 生命周期
一、resume
前面的章节介绍了AMS 通过Binder远程调用ApplicationThread的scheduleResumeActivity方法
public final void scheduleResumeActivity(IBinder token, int processState,
boolean isForward, Bundle resumeArgs) {
updateProcessState(processState, false);
sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0);
}
sendMessge 利用Handler 对象发出一个消息,Handler 收到此消息后调用handleResumeActivity方法。
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
......
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
......
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
}
}
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
......
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
//添加View
wm.addView(decor, l);
}
}
......
}
......
}
这里先是通过performResumeActivity 调用activity的onResume 之后才调用的wm.addView, 这也是在onResume 里面无法获取View 宽高的原因,因为此时显示的View 还没有加入WindowMangerService系统内。
wm 实际类型是WindowManagerImpl,接着看WindowManagerImpl的 addView。
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
WindowManagerImpl 的 addView 接着调用了WindowManagerGlobal的addView。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent and we're running on L or above (or in the
// system context), assume we want hardware acceleration.
final Context context = view.getContext();
if (context != null
&& context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
//创建ViewRoot
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//保存view
mViews.add(view);
//保存ViewRoot
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView);
}
adjustLayoutParamsForSubWindow 用于调整LayoutParams,之后便创建一个ViewRoot对象,android 会通过ViewRoot 发起对于页面的测量,布局,绘制这三个流程。创建ViewRoot 之后会调用其setView 方法。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
……
requestLayout();;
}
}
}
setView 内部调用了requestLayout方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
这里post了一个mTraversalRunnable,我们看看这个runnable做了啥事
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
try {
performTraversals();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
核心就是调用performTraversals方法,performTraversals是整个绘制流程的开端。
由于performTraversals 非常的长,这里会分为若干个小部分介绍。
第一部分
private void performTraversals() {
// cache mView since it is used so much below...
mView就是DecorView根布局,记录ViewRootImpl管理的
//View树的根节点,final修饰,避免运行过程中修改
final View host = mView;
//判断View绘制的条件是否具备.mAdded是DecorView是否成功添加到
//Window的标示位
if (host == null || !mAdded)
return;
//是否正在遍历
mIsInTraversal = true;
//是否马上绘制View
mWillDrawSoon = true;
//视图的大小可能改变
boolean windowSizeMayChange = false;
boolean newSurface = false;
boolean surfaceChanged = false;
WindowManager.LayoutParams lp = mWindowAttributes;
//顶层视图DecorView所需要窗口的宽度和高度
int desiredWindowWidth;
int desiredWindowHeight;
//DecorView视图是否可见
final int viewVisibility = getHostVisibility();
boolean viewVisibilityChanged = mViewVisibility != viewVisibility
|| mNewSurfaceNeeded;
xxxx
mWindowAttributesChangesFlag = 0;
//将一个全局的Rect对象赋给了局部frame对象,
//它用来保存Activity窗口当前的宽度和高度
Rect frame = mWinFrame;
if (mFirst) {
//在构造方法中mFirst已经设置为true,
//表示是否是第一次被请求执行测量、布局和绘制操作,
//是否需要全部重绘
mFullRedrawNeeded = true;
mLayoutRequested = true;
//如果窗口的类型是有状态栏的,那么Activity窗口的宽度和高度
//就是除了状态栏
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 {
//否则Activity窗口的宽度和高度就是整个屏幕的宽高
DisplayMetrics packageMetrics =
mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
}else {
//除了第一次被请求执行测量、布局和绘制操作以外。
// 期望宽高为frame 保存的宽高。
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
//mWidth ,mHeight 是页面当前显示的宽高,
//如果当前显示宽高与期望宽高不一致
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
//需要进行完整的重绘以适应新的窗口尺寸
mFullRedrawNeeded = true;
//需要对控件树进行重新布局
mLayoutRequested = true;
windowSizeMayChange = true;
}
}
WMS 有可能会单方面修改页面高度。例如页面当先正在全屏显示,由于某些场景此时WMS 希望页面高度变为屏幕的一般,此时wms 会将新的宽高传递到mWinFrame 里面,同时调用requestLayout,此时frame里面保存的就是WMS 最细传递过来的宽高,自然与当前显示的宽高不一致。
boolean insetsChanged = false;
boolean layoutRequested = mLayoutRequested && !mStopped;
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
if (mFirst) {
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
} else {
//mPendingOverscanInsets 是AMS主动传递过来的数据
//下面的pending 也是一样的
if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
insetsChanged = true;
}
if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
insetsChanged = true;
}
if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
insetsChanged = true;
}
if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;
//这里可看到
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 {
//也是获取屏幕的宽高,不知道两者之间的区别是什么
DisplayMetrics packageMetrics = res.getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
}
}
// Ask host how big it wants to be
//内部会调用测量的方法,测量当前页面的大小
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
这里涉及到几个屏幕区域的问题
mOverscanInsets 在手机端一般不存在OverScan区域,在电视端存在。
mContentInsets 记录的是内容区域,
mStableInsets 记录了Stable区域,比mContentInsets区域大 ,包含了状态栏但是不包含导航栏。
mVisibleInsets 记录了可见区域。
这些区域一般都是不变的。
这里我们可以看到即使页面的宽高设置为WRAP_CONTENT,也即跟内容大小一致,但是实际上desiredWindowWidth与desiredWindowHeight 依然是屏幕的宽高,也就是说设WRAP_CONTENT对于Activity 的大小无效,此时Actiivty 依然是满屏大小。
之后会第一次调用measureHierarchy,这个方法内部会测量View的大小。
执行完measureHierarchy方法后,ViewRootImpl就知道了View树需要的大小,并同时修改windowSizeMayChange的值,它的表示有可能需要改变窗口大小以适应View树的大小要求。
例如第一次测量的时候经过测量之后知道了页面的根View也就是DectorView的大小,但是此时还没有结算Window 窗口的胆小,因此第一次调用measureHierarchy返回的是true。
假如是第若干次调用measureHierarchy,此时系统的Window的大小已经确定了,调用measureHierarchy 发现这次根View的大小与上次不一样了,根View大小变了就可能导致Window 大小的变化,后面会通过WMS重新计算Window 的大小。
if (collectViewAttributes()) {
params = lp;
}
if (mAttachInfo.mForceReportNewAttributes) {
mAttachInfo.mForceReportNewAttributes = false;
params = lp;
}
if (mFirst || mAttachInfo.mViewVisibilityChanged) {
mAttachInfo.mViewVisibilityChanged = false;
int resizeMode = mSoftInputMode &
WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
// If we are in auto resize mode, then we need to determine
// what mode to use now.
if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
final int N = mAttachInfo.mScrollContainers.size();
for (int i=0; i<N; i++) {
if (mAttachInfo.mScrollContainers.get(i).isShown()) {
resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
}
}
if (resizeMode == 0) {
resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
}
if ((lp.softInputMode &
WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) != resizeMode) {
lp.softInputMode = (lp.softInputMode &
~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) |
resizeMode;
params = lp;
}
}
}
if (params != null) {
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
if (!PixelFormat.formatHasAlpha(params.format)) {
params.format = PixelFormat.TRANSLUCENT;
}
}
mAttachInfo.mOverscanRequested = (params.flags
& WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0;
}
if (mApplyInsetsRequested) {
mApplyInsetsRequested = false;
mLastOverscanRequested = mAttachInfo.mOverscanRequested;
dispatchApplyInsets(host);
if (mLayoutRequested) {
// Short-circuit catching a new layout request here, so
// we don't need to go through two layout passes when things
// change due to fitting system windows, which can happen a lot.
windowSizeMayChange |= measureHierarchy(host, lp,
mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
}
}
这一段主要是处理fitSystemWindow 属性,当用户在xml 布局文件里面的某个View 中使用了fitSystemWindow 属性,然后在onCreate 调用了view.requestApplyInsets 方法,这个方法会一直向上调用直到ViewRoot的requestApplyInsets方法,ViewRoot 子方法内设置mApplyInsetsRequested=true,dispatchApplyInsets 方法会直到那么设置了fitSystemWindow属性的View,给这个View 添加一个相当于状态栏高度的Padding,这个会导致整个View 树的高度发生变化因此会再次measureHierarchy 方法测量View 树的高度。
boolean windowShouldResize = layoutRequested && windowSizeMayChange
&& ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
// Determine whether to compute insets.
// If there are no inset listeners remaining then we may still need to compute
// insets in case the old insets were non-empty and must be reset.
final boolean computesInternalInsets =
mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
|| mAttachInfo.mHasNonEmptyGivenInternalInsets;
boolean insetsPending = false;
int relayoutResult = 0;
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null) {
if (viewVisibility == View.VISIBLE) {
insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
}
if (mSurfaceHolder != null) {
mSurfaceHolder.mSurfaceLock.lock();
mDrawingAllowed = true;
}
boolean hwInitialized = false;
boolean contentInsetsChanged = false;
boolean hadSurface = mSurface.isValid();
try {
final int surfaceGenerationId = mSurface.getGenerationId();
//测量Window 大小。
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
if (!mDrawDuringWindowsAnimating &&
(relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0) {
mWindowsAnimating = true;
}
if (mPendingConfiguration.seq != 0) {
if (DEBUG_CONFIGURATION) Log.v(TAG, "Visible with new config: "
+ mPendingConfiguration);
updateConfiguration(mPendingConfiguration, !mFirst);
mPendingConfiguration.seq = 0;
}
final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
mAttachInfo.mOverscanInsets);
contentInsetsChanged = !mPendingContentInsets.equals(
mAttachInfo.mContentInsets);
final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
mAttachInfo.mVisibleInsets);
final boolean stableInsetsChanged = !mPendingStableInsets.equals(
mAttachInfo.mStableInsets);
if (contentInsetsChanged) {
if (mWidth > 0 && mHeight > 0 && lp != null &&
((lp.systemUiVisibility|lp.subtreeSystemUiVisibility)
& View.SYSTEM_UI_LAYOUT_FLAGS) == 0 &&
mSurface != null && mSurface.isValid() &&
!mAttachInfo.mTurnOffWindowResizeAnim &&
mAttachInfo.mHardwareRenderer != null &&
mAttachInfo.mHardwareRenderer.isEnabled() &&
lp != null && !PixelFormat.formatHasAlpha(lp.format)
&& !mBlockResizeBuffer) {
disposeResizeBuffer();
}
mAttachInfo.mContentInsets.set(mPendingContentInsets);
if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
+ mAttachInfo.mContentInsets);
}
//overscan 区域变化了,则标示该区域变换了。
//其他几个区域都是这样操作。
if (overscanInsetsChanged) {
mAttachInfo.mOverscanInsets.set(mPendingOverscanInsets);
// Need to relayout with content insets.
contentInsetsChanged = true;
}
if (stableInsetsChanged) {
mAttachInfo.mStableInsets.set(mPendingStableInsets);
if (DEBUG_LAYOUT) Log.v(TAG, "Decor insets changing to: "
+ mAttachInfo.mStableInsets);
// Need to relayout with content insets.
contentInsetsChanged = true;
}
if (contentInsetsChanged || mLastSystemUiVisibility !=
mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested
|| mLastOverscanRequested != mAttachInfo.mOverscanRequested) {
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
mLastOverscanRequested = mAttachInfo.mOverscanRequested;
mApplyInsetsRequested = false;
dispatchApplyInsets(host);
}
if (visibleInsetsChanged) {
mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
//xxxx 删除部分代码
} catch (RemoteException e) {
}
//将计算得到的Activity窗口的左上角坐标保存在变量attachInfo所指向的一个
// AttachInfo对象的成员变量mWindowLeft和mWindowTop中
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
//将计算得到的Activity Window的宽度和高度保存在ViewRoot类的成员变量mWidth和mHeight中。
if (mWidth != frame.width() || mHeight != frame.height()) {
mWidth = frame.width();
mHeight = frame.height();
}
// xxx
if (!mStopped) {
//这段代码用来检查是否需要重新测量Activity窗口的大小。如果满足以下条件之一,那么就需要重新测量:
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
//mWidth,mHeight 是WMS计算出来的窗口的大小。
//View 树的大小不应该操作窗口的大小。
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// //以WMS返回的宽高为限制再次测量一次
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
//WMS返回的宽是100,实际测量的结果是50,然后用户还设置了horizontalWeight(0-1)
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) {
//因为设置了Weight 那么再次测试View 树的大小。
if (DEBUG_LAYOUT) Log.v(TAG,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
} else {
// Not the first pass and no window/insets/visibility change but the window
// may have moved and we need check that and if so to update the left and right
// in the attach info. We translate only the window frame since on window move
// the window manager tells us only for the new frame but the insets are the
// same and we do not want to translate them more than once.
// TODO: Well, we are checking whether the frame has changed similarly
// to how this is done for the insets. This is however incorrect since
// the insets and the frame are translated. For example, the old frame
// was (1, 1 - 1, 1) and was translated to say (2, 2 - 2, 2), now the new
// reported frame is (2, 2 - 2, 2) which implies no change but this is not
// true since we are comparing a not translated value to a translated one.
// This scenario is rare but we may want to fix that.
final boolean windowMoved = (mAttachInfo.mWindowLeft != frame.left
|| mAttachInfo.mWindowTop != frame.top);
if (windowMoved) {
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWinFrame(frame);
}
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
}
}
windowShouldResize 表示是否需要重新请求wms 测量Window 的大小。
layoutRequested 为true,说明view正在调用requestLayout,请求重新布局。
windowSizeMayChange 是前面测量View 树的高度返回的结构,当View 的高度发生变化的时候,那么Window 的大小也可能要跟着发生变化。
mWidth 记录的是上一次WMS 测量的Window 的大小,同理mHeight 记录的是上次的高度。
当这两者与View 树的测量大小不一致的时候表示View 树大小可能变化了,因此需要WMS 重新测量Window。
最后的条件是如果窗口width或height被设置成WRAP_CONTENT,计算出来的窗口大小desiredWindowWidth/desiredWindowHeight 与上一次测量保存的frame.width()/frame.height()
同时与WindowManagerService服务计算的宽度mWidth和高度mHeight也不一致,那也说明窗口大小变化了。但是数据实话不清楚这个条件对应现实中的哪一种情形。
除了windowShouldResize 可以决定是否重新计算Window 的大小还有其他几个条件,也就是下面这个判断
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null)
mFirst 表示Activity窗口是第一次执行测量、布局和绘制操作,即ViewRoot类的成员变量mFirst的值等于true,这个时候Window 的大小一次都还没有测量过。
windowShouldResize t上面介绍了。
insetsChanged true 说明此次窗口overscan等一些边衬区域发生了改变,与上次不一样。
viewVisibilityChanged 说明View的可见性发生了变化
params 即窗口属性发生了变化,指向了mWindowAttributes
接着变调用relayoutWindow 方法请求WMS 测量Window 窗口的大小。
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
// xxx
//注意后面参数是用来保存测量结果的。
//例如mWinFrame 保存Window 大小。
int relayoutResult = mWindowSession.relayout(
mWindow, mSeq, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f),
viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
mPendingStableInsets, mPendingConfiguration, mSurface);
//xxx
return relayoutResult;
}
调用WindowSession的relayout 方法,WindowSession 的relayout 内部会调用WMS 的方法计算Window的大小。窗口大小的结果会保存在mWinFrame 数组内部。
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
//将计算得到的Activity Window的宽度和高度保存在ViewRoot类的成员变量mWidth和mHeight中。
if (mWidth != frame.width() || mHeight != frame.height()) {
mWidth = frame.width();
mHeight = frame.height();
}
frame 与mWinFrame 是同一个数组。在relayout 计算完之后这里将Window 的位置保存在mAttachInfo里面,Window 的大小保存在mWidth,mHeight 变量里面。
这里之所以将大小赋值给mWidth,mHeight的原因是因为WMS有可能主动修改mWinFrame的值,例如修改了修改某些影响页面显示范围的系统设置,此时系统就会通过WMS 主动调整页面的大小与位置。
当测量完Window 之后可能会再次测量View 树的大小。
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
//mWidth,mHeight 是WMS计算出来的窗口的大小。
//View 树的大小不应该操作窗口的大小。
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// //以WMS返回的宽高为限制再次测量一次
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
mWidth != host.getMeasuredWidth() ||mHeight != host.getMeasuredHeight()表示窗口的大小与上次View 树的大小不一样,此时需要重新测量View 树。
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
这两行代码是根据Window 的大小来生成MeasureSpec,生成的MeasureSpec的大小一定是不大于其第一个参数,这样就保证了View 树的宽高不会大于Window 的宽高。之后就调用performMeasure再次测试View 树的大小。测量完成之后会检查是否设置了宽高的Weight属性,如果设置了此时会再次调用performMeasure测量View 树的大小。
到此第一部分就分析完了,这一部分主要就是测量View树的宽高以及计算Window的宽高。
第二部分
final boolean didLayout = layoutRequested && !mStopped;
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
// By this point all views have been sized and positioned
// We can compute the transparent area
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
// start out transparent
// TODO: AVOID THAT CALL BY CACHING THE RESULT?
host.getLocationInWindow(mTmpLocation);
mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
mTmpLocation[0] + host.mRight - host.mLeft,
mTmpLocation[1] + host.mBottom - host.mTop);
host.gatherTransparentRegion(mTransparentRegion);
if (mTranslator != null) {
mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
}
if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
mPreviousTransparentRegion.set(mTransparentRegion);
mFullRedrawNeeded = true;
// reconfigure window manager
try {
mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
} catch (RemoteException e) {
}
}
}
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after setFrame");
host.debug();
}
}
这里主要的就是调用performLayout 方法进行布局。performLayout方法会调用View的layout方法,在layout方法中回调onLayout方法,开发者可以重写这个方法,这个具体分析与measure类似。
第三部分
//执行回调
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
//开始绘制View
performDraw();
}
} else {
if (viewVisibility == View.VISIBLE) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
在View的Draw方法里绘制一个View分一下几步
drawBackground 绘制背景
保存画布图层
onDraw(canvas) 回调 绘制内容 由子类去实现
dispatchDraw(canvas) 有子view 就绘制子view
绘制淡入淡出效果并恢复图层
onDrawForeground(canvas) 画装饰 比如前景色 滚动条