前言
继前篇文章 Activity启动流程
咱继续来分析ViewRootImpl
前篇文章讲到:handleResumeActivity()方法里面逻辑顺序:
- 首先会执行performResumeActivity去调用Activity的onStart()、onResume(),返回ActivityClientRecord实例,通过此实例获取Activity
- 从Activity中获取PhoneWindow,
- 然后从PhoneWindow中获取到DecorView
- 接着从Activity中获取WindowManager(实际上是WindowManagerImpl)
- 再继续WindowManagerImpl调用addView()方法把DecorView添加进去
- WindowManagerImpld的addView()方法会调用到WindowManagerGlobal的addView()方法,在此方法中会创建ViewRootImpl的实例。
- 最终会调用ViewRootImpl的setView()方法加载DecorView,执行绘制流程。
1. ViewRootImpl的SetView()方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
....
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout(); // 标注:1️⃣
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
...
try {
...
// 标注:2️⃣
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
setFrame(mTmpFrame);
} catch (RemoteException e) {
...
} finally {
...
}
...
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
...
}
}
}
标注1️⃣: requestLayout() 逻辑下面解读
标注:2️⃣:调用mWindowSession.addToDisplay通过Binder ipc调用到WMS,实现对Window的真正的添加,这里的mWindow为 W 对象
2. ViewRootImpl的requestLayout()
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
3. ViewRootImpl的scheduleTraversals()
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 标注1️⃣
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 标注2️⃣
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
标注1️⃣: 同步屏障SyncBarrier, 开启postSyncBarrier()之后,MessageQueue 中的同步消息将不能再继续执行,等下一步调用removeSyncBarrier()之后,同步消息才能继续执行。
标注2️⃣: 设置了同步屏障后,发送一个 CALLBACK_TRAVERSAL 类型消息到 Choreographer 的消息队列。等执行完之后回调TraversalRunnable的run方法
4. ViewRootImpl里TraversalRunnable 的run()方法
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 标注1️⃣
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
标注1️⃣: 调用ViewRootImpl的doTraversal()方法
5. ViewRootImpl的doTraversal()方法
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 标注1️⃣
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 标注2️⃣
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
标注1️⃣ 移除同步屏障
标注2️⃣ 调用ViewRootImpl的performTraversals()方法
6. ViewRootImpl的performTraversals()方法
private void performTraversals() {
......
// 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) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
// 标注1️⃣
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
......
// 标注2️⃣
performLayout(lp, mWidth, mHeight);
......
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
......
// 标注3️⃣
performDraw();
......
}
上述标注中的1️⃣2️⃣3️⃣分别会调用View对应的measure()、layout()、draw()这几个方法。
7. ViewRootImpl的performMeasure()方法
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
// 标注1️⃣
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
标注1️⃣: 调用View 的measure()方法
8. ViewRootImpl的performLayout()方法
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
....
final View host = mView;
if (host == null) {
return;
}
....
try {
// 标注1️⃣
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
....
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
...
// 标注2️⃣
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
标注1️⃣ 与 2️⃣ 都是调用View的layout()方法
9. ViewRootImpl的performDraw()方法
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
} else if (mView == null) {
return;
}
...
try {
// 标注1️⃣
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
}
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
...
}
// 标注1️⃣ 调用的方法
private boolean draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
...
int xOffset = -mCanvasOffsetX;
int yOffset = -mCanvasOffsetY + curScrollY;
final WindowManager.LayoutParams params = mWindowAttributes;
final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
if (surfaceInsets != null) {
xOffset -= surfaceInsets.left;
yOffset -= surfaceInsets.top;
// Offset dirty rect for surface insets.
dirty.offset(surfaceInsets.left, surfaceInsets.right);
}
...
// 标注2️⃣
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
...
}
// 标注2️⃣ 调用的方法
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
// Draw with software renderer.
final Canvas canvas;
// We already have the offset of surfaceInsets in xoff, yoff and dirty region,
// therefore we need to add it back when moving the dirty region.
int dirtyXOffset = xoff;
int dirtyYOffset = yoff;
if (surfaceInsets != null) {
dirtyXOffset += surfaceInsets.left;
dirtyYOffset += surfaceInsets.top;
}
try {
dirty.offset(-dirtyXOffset, -dirtyYOffset);
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
canvas = mSurface.lockCanvas(dirty);
// TODO: Do this in native
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
...
mLayoutRequested = true; // ask wm for a new surface next time.
return false;
} finally {
dirty.offset(dirtyXOffset, dirtyYOffset); // Reset to the original value.
}
try {
...
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
// 标注3️⃣
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
...
}
return true;
}
标注1️⃣: 调用View 的draw()方法
总结ViewRootImpl调用流程:
- step1: 最先调用setView()方法,传入View
- step2: 调用requestLayout() ,检查创建ViewRootImpl时的线程和当前的线程是否一致。执行下一步scheduleTraversals()
- step3: 调用scheduleTraversals(),开启同步屏障,之后发送一个 CALLBACK_TRAVERSAL 类型消息到 Choreographer 的消息队列。等执行完之后回调TraversalRunnable的run方法。
- step4: 在TraversalRunnable的run方法里面调用doTraversal()方法。
- step5: 在doTraversal()方法中,移除同步屏障,接着调用performTraversals()方法。
- step6: 在performTraversals()方法中按顺序调用performMeasure(),performLayout(),performDraw()这几个方法,它们又分别调用View的measure() ,layout() ,draw()方法。
常问问题:
1.什么时候获取 View 的测量宽高?
private void performTraversals() {
......
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
performLayout(lp, mWidth, mHeight);
......
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
//标注1️⃣
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
......
performDraw();
......
}
答:根据 ViewRootImpl#performTraversals的调用逻辑。标注1️⃣的dispatchOnGlobalLayout()方法 在 performMeasure - performLayout 之后调用,通过该回调可以获取到测量的宽高。
App 层可以通过注册 OnGlobalLayoutListener 方式获取,代码如下:
view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// view.getMeasuredWidth()/view.getMeasuredHeight()
......
}
});
2.Activity怎么在onCreate()里获取view的宽高?
有三种方法:
第1️⃣种:通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了,这时候就可以获取view的宽高了
注意:代码只会执行一次
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.main_clock_time);
Log.e(TAG, "mtextview width is : "+mTextView.getWidth()+", height is : "+mTextView.getHeight() );
mTextView.post(new Runnable() {
@Override
public void run() {
Log.e(TAG, "mtextview width is : "+mTextView.getWidth()+", height is : "+mTextView.getHeight() );
}
});
}
第2️⃣种:通过ViewTreeObserver的OnGlobalLayoutListener接口,当View树的状态或者View树内部的View的可见性发生改变的时候,onGlobalLayout方法将被回调,此时便可以获取View的宽高。
注意:伴随着View树的状态改变等,onGlobalLayout会被多次调用。
@Override
protected void onCreate(Bundle savedInstanceState) {
mTextView = (TextView) findViewById(R.id.main_clock_time);
ViewTreeObserver observer = mTextView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
tv.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int width = mTextView.getWidth();
int height = mTextView.getHeight();
}
});
}
第3️⃣种:重写Activity/View 的onWindowFocusChangedView方法获取view宽高。
注意:onWindowFocusChanged会被调用多次,当Activity的窗口得到焦点和失去焦点的时候都会被调用一次。
public class MainActivity extends AppCompatActivity {
TextView mTextView;
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
int width = mTextView.getWidth();
int height = mTextView.getHeight();
}
}
小总结:
- 调用getMeasuredWidth() 获取宽度,必须要等measure()方法走完才能获取到值。
- 调用getWidth()必须要layout()完之后才能获取到值。
- onMeasure() 和layout()是怎么调用的呢,简单捋一下view的绘制流程,每一个view的绘制流程大致包括,measure() ,layout() ,draw() ,view的绘制是从ViewRootImpl的performtraversal() 方法开始的,这个方法里面依次调用了 performMeasure(),performLayout(), performDraw() ,对应View里面的方法是measure() ,layout() ,draw()
注意:
1️⃣ getWidth()在layout()走完之后才有值
2️⃣ getMeasuredWidth()在measure()走完之后才有值
3.在子线程中可以更新 UI 吗?
在 Activity#onResume 之前,可以在子线程中更新 UI。
checkThread 线程检查是在 ViewRootImpl 的方法中进行的 (如 invalidate 和 requestLayout)
而通过溯源代码,ViewRootImpl 是在 ActivityThread#handleResumeActivity 创建的。在 Activity#onResume 之前 ViewRootImpl 还没创建,所以也不会检查线程和绘制 UI。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
......
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a" +
"view hierarchy can touch its views.");
}
}
4.MeasureSpec 自定义 View 的方式有哪几种?
-
EXACTLY:精确模式(父 View 指定了子 View 的精确大小)
对应我们在布局文件中设置宽高时给一个具体值或者match_parent;当前的尺寸就是当前View应该取的尺寸.
对应关系:match_parent--->EXACTLY。
match_parent就是要利用父View给我们提供的所有剩余空间,而父View剩余空间是确定的,也就是这个测量模式的整数里面存放的尺寸。
固定尺寸(100dp)--->EXACTLY.
用户自己制定了尺寸大小,我们就不用再去干涉了,当然是以指定的大小为主。
父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间。 -
AT_MOST:最大值模式(父 View 指定了子 View 的最大值,子 View 最大到达这个最大值)
对应设置宽高时给一个wrap_content; 当前尺寸是当前View能取的最大尺寸
对应关系:warp_parent---> AT_MOST
我们想要将大小设置为包裹我们的View内容,那么尺寸大小就是父View给我作为参考的尺寸,至于不超过这个尺寸就可以啦。具体尺寸就根据我们的需求去设定。 -
UNSPECIFIED:未指明模式(父 View 没有对子 View 做限制)
这种测量模式多用在ScrollView中,或者系统内部调用;当前的尺寸就是当前View应该取的尺寸;