首先思考一个问题
public class V8Activity extends Activity {
private String TAG = "V8Activity";
ActivityV8Binding v8Binding;
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initBinding();
//Log-1
Log.d(TAG, "Button Height onCreate:" + v8Binding.btn.getMeasuredHeight());
v8Binding.btn.post(new Runnable() {
@Override
public void run() {
//Log-2
Log.d(TAG, "Button Height post:" + v8Binding.btn.getMeasuredHeight());
}
});
}
private void initBinding() {
v8Binding = DataBindingUtil.setContentView(this, R.layout.activity_v8);
}
protected void onResume() {
super.onResume();
//Log-3
Log.d(TAG, "Button Height onResume:" + v8Binding.btn.getMeasuredHeight());
}
}
Log - 1 打印值为多少 ?
Log - 2 打印值为多少 ?
Log - 3 打印值为多少 ?
运行结果 -> 「Log1 = 0」「Log2 = 132」「Log3 = 0」
为什么会产生这样的结果呢?下面一个一个来分析
Log1 打印 0 原因
之前分析了 setContentView 源码,setContentView 是将布局加载到 DecorView 中,这个时候 View 还没有测量,所以 Log1 打印为 0。
Log3 为什么打印 0
追一下源码
ActivityThread -> handleResumeActivity
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
...
wm.addView(decor, l);
...
}
performResumeActivity() -> ActivityThread. performResumeActivity()
ActivityThread -> performResumeActivity
public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
String reason) {
...
r.activity.performResume(r.startsNotResumed, reason);
...
}
r.activity.performResume() -> Activity.performResume()
Activity -> performResume
final void performResume(boolean followedByPause, String reason) {
...
mInstrumentation.callActivityOnResume(this);
...
}
mInstrumentation.callActivityOnResume() -> Instrumentation.callActivityOnResume()
Instrumentation -> callActivityOnResume
public void callActivityOnResume(Activity activity) {
activity.mResumed = true;
activity.onResume();
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
am.match(activity, activity, activity.getIntent());
}
}
}
}
最后调用到 Activity 的 onResume 方法
handleResumeActivity 执行过程
- performResumeActivity
- wm.addView
- Activity 执行 onResume
- 将 DecorView 添加到 WindowManager
View 的绘制流程在 wm.addView 才开始,View 的绘制流程是在 Activity onResume 之后
Log2 打印数值
view.post() 入口
/**
* <p>Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.</p>
*
* @param action The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*
* @see #postDelayed
* @see #removeCallbacks
*/
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
The runnable will be run on the user interface thread -> runnable 会在主线程运行
很明显,这里的 attachInfo 并没有赋值,所以会走 getRunQueue().post() 方法
getRunQueue()
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
获取了 HandlerActionQueue 对象
HandlerActionQueue -> post
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
将 runnable 转换成 HandlerAction 对象,然后缓存起来,大致可以理解为
HandlerAction 是 runnable 的一个封装类。
这里只是将消息缓存,并没有执行。
所以带着 2 点疑问,什么时候会执行 run 方法 ?在哪个位置会触发执行的方法 ?
什么时候会执行 run 方法
getRunQueue() 方法是在 View 中执行的,在 View 中搜索就可找到 dispatchAttachedToWindow 方法
dispatchAttachedToWindow
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
...
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
...
}
mAttachInfo 在 dispatchAttachedToWindow 中才初始化
mRunQueue -> executeActions
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[I];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
解答上面的 2 点疑问,executeActions 会执行 run 方法( 通过 handler ),dispatchAttachedToWindow 会执行 executeActions 方法。
接着 View 的绘制流程 wm.addView() 往下看
wm -> Activity.getWindowManager() -> Window.getWindowManager() -> WindowManagerImpl
wm.addView() -> WindowManagerImpl.addView() -> WindowManagerGlobal.addView()
WindowManagerGlobal -> addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
...
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView, userId);
...
}
ViewRootImpl
public ViewRootImpl(Context context, Display display) {
this(context, display, WindowManagerGlobal.getWindowSession(),
false /* useSfChoreographer */);
}
public ViewRootImpl(Context context, Display display, IWindowSession session) {
this(context, display, session, false /* useSfChoreographer */);
}
public ViewRootImpl(Context context, Display display, IWindowSession session,
boolean useSfChoreographer) {
...
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
...
}
在 ViewRootImpl 的构造方法中初始化了 AttachInfo
root.setView() -> ViewRootImpl.setView()
ViewRootImpl -> setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
...
requestLayout();
...
}
ViewRootImpl -> requestLayout
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
ViewRootImpl -> scheduleTraversals
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
mTraversalRunnable -> run
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
ViewRootImpl -> 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 -> performTraversals
private void performTraversals() {
final View host = mView;
...
host.dispatchAttachedToWindow(mAttachInfo, 0);
getRunQueue().executeActions(mAttachInfo.mHandler);
...
performMeasure();
performLayout();
performDraw();
...
}
wm.addView(DecorView) -> windowManagerGlobal.addView(DecorView) ->
root.setView(DecorView)
所以这里的 host 其实是 DecorView
host.dispatchAttachedToWindow() -> DecorView.dispatchAttachedToWindow(),但是 DecorView 并没有实现这个方法,接着往上走,最终在 ViewGroup 中找到 dispatchAttachedToWindow 方法。
ViewGroup -> dispatchAttachedToWindow
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
super.dispatchAttachedToWindow(info, visibility);
mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[I];
child.dispatchAttachedToWindow(info,
combineVisibility(visibility, child.getVisibility()));
}
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchAttachedToWindow(info,
combineVisibility(visibility, view.getVisibility()));
}
}
ViewGroup 的 dispatchAttachedToWindow 方法会便利子 View 的 dispatchAttachedToWindow,而持有的 AttachInfo 对象是同一个,而 AttachInfo 持有的是 ViewRootImpl 中的 mHandler 对象,所以可以得出一个结论 executeActions 中的消息都是由 ViewRootImpl 的 mHandler 对象统一处理的。
ViewRootImpl -> performTraversals
private void performTraversals() {
final View host = mView;
...
host.dispatchAttachedToWindow(mAttachInfo, 0);
getRunQueue().executeActions(mAttachInfo.mHandler);
...
//View 的绘制流程
performMeasure();
performLayout();
performDraw();
...
}
接着看 performTraversals 方法
View.post() 能拿到数值,而 dispatchAttachedToWindow 方法不是在 View 的绘制流程之前调用的吗 ?这个时候我们可以用结果推断一下,View.post() 应该是在测量流程结束之后才执行的。
这个时候需要了解 Android 的消息机制。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[I];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
handler 先发送的 mTraversalRunnable ,之后才发送的 handlerAction.action (也就是我们 Activity 里的 runnable ),所以会在 View 的绘制流程结束之后执行我们 Activity 中的 runnable。
模仿源码写了一个小例子,更容易理解绘制流程这一块
public class V8Test1 {
private Context context;
private V8Test2.AttachInfoTest attachInfoTest;
private V8Test2 mTest;
Handler handler = new Handler();
public V8Test1(Context context) {
this.context = context;
attachInfoTest = new V8Test2.AttachInfoTest(context,handler);
}
Runnable runnable = new Runnable() {
@Override
public void run() {
Log.d("response","Test1 执行=====");
performTraversals();
}
};
void scheduleTraversals() {
handler.post(runnable);
}
private void performTraversals() {
final V8Test2 host = mTest;
host.dispatchAttachedToWindow(attachInfoTest);
Log.d("response","Test1 执行 Measure");
Log.d("response","Test1 执行 Layout");
Log.d("response","Test1 执行 draw");
}
public void setTest(V8Test2 mTest) {
this.mTest = mTest;
}
}
V8Test1 类对应就是 ViewRootImpl
public class V8Test2 {
AttachInfoTest attachInfoTest;
final static class AttachInfoTest {
final Handler handler;
final Context context;
AttachInfoTest(Context mContext, Handler mHandler) {
context = mContext;
handler = mHandler;
}
}
void dispatchAttachedToWindow(AttachInfoTest info) {
this.attachInfoTest = info;
executeActions(attachInfoTest.handler);
}
public void executeActions(Handler handler) {
synchronized (this) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.d("response","Test2执行=====");
}
}, 0);
}
}
}
V8Test2 类对应就是 View,其中的 executeActions 对应 HandlerActionQueue 的 executeActions 方法
测试代码
V8Test1 v8Test1 = new V8Test1(this);
v8Test1.setTest(new V8Test2());
v8Test1.scheduleTraversals();
new V8Test1 -> 对应 windowManagerGlobal.addView() 方法中的 new ViewRootImpl
v8Test1.setTest -> 对应 windowManagerGlobal.addView() 方法中的 root.setView()
v8Test1.scheduleTraversals 对应 ViewRootImpl 中的 scheduleTraversals
测试结果
response: Test1 执行=====
response: Test1 执行 Measure
response: Test1 执行 Layout
response: Test1 执行 draw
response: Test2执行=====
测试结果和我们预测的结果一致。
接下来就是 View 绘制流程的第一步 measure
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
mView.measure() -> View.measure()
View -> measure
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
View -> onMeasure (这里我们以 LinearLayout 为例子)
public class LinearLayout extends ViewGroup {
...
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
...
}
根据方向处理不同的方法,我们看 measureVertical 方法
LinearLayout -> measureVertical
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
...
for (int i = 0; i < count; ++i) {
...
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
...
}
...
}
LinearLayout -> measureChildBeforeLayout
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
LinearLayout -> measureChildWithMargins
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
调用 child.measure 方法,如果 child 为 LinearLayout,则继续进入 LinearLayout 的 onMeasure 方法。
measure 流程childWidthMeasureSpec, childHeightMeasureSpec 这 2 个参数分别对象宽高,下面记录一下测量模式的计算 -> getChildMeasureSpec()
ViewGroup -> getChildMeasureSpec
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
}
这里拿的是父布局的 Mode 和 size,然后根据父布局的 mode 去决定子 View 的 mode 和 size。所以子 View 的 mode 和 size 是由子 View 和父布局共同决定的。一个一个分析
当父布局 Mode 为 MeasureSpec.EXACTLY ( match_parent,100dp ) 时
子 View childSize > 0,子 View mode = EXACTLY,size = childSize
子 View childSize = MATCH_PARENT,子 View mode = EXACTLY,size = parentSize
子 View childSize = WRAP_CONTENT,子 View mode = AT_MOST,size = parentSize
当父布局 Mode 为 MeasureSpec. AT_MOST ( wrap_content ) 时
子 View childSize > 0,子 View mode = EXACTLY,size = childSize
子 View childSize = MATCH_PARENT,子 View mode = AT_MOST,size = parentSize
子 View childSize = WRAP_CONTENT,子 View mode = AT_MOST,size = parentSize
当父布局 Mode 为 MeasureSpec. UNSPECIFIED ( 基本不会用到 ) 时
子 View childSize > 0,子 View mode = EXACTLY,size = childSize
子 View childSize = MATCH_PARENT,子 View mode = UNSPECIFIED,size = parentSize
测量模式子 View childSize = WRAP_CONTENT,子 View mode = UNSPECIFIED,size = parentSize
确定子 View 的 mode 和 size。
确定子 View 的 mode 和 size 之后,LinearLayout 循环会叠加每个子 View 的高度,最终通过 setMeasuredDimension 方法确定宽高。
LinearLayout -> measureVertical
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
...
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
...
}
如果是自定义View 就需要重写 onMeasure 方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
如果 onMeasure 方法没有设置宽高,那么会调用 getDefaultSize 设置一个默认的宽高
总结
Measure 流程是确定 View 的大小,如果有子 View 会先确定子 View 的大小,最后通过子 View 的大小确定父容器的大小。
网友评论