起因:之前一群里的哥们问 Handler.post()
为什么会在 Activity
的 onResume()
之后执行,我找了一遍之后并没有找到原因,后来从这个问题我想起其他的问题 view.post()
为什么在 view.post()
之后为什么可以准确的获取到 view
的宽高。
疑问🤔️:View.post()为什么会准确的获取到View的宽高?
public boolean post(Runnable action) { //注释1:判断attachInfo如果不为空 直接调用attachInfo内部Handler.post()方法 //这样就有一个问题attachInfo在哪里赋值?这个问题先存疑。 final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } //注释2:此时attachInfo是空的 getRunQueue().post(action); return true; } 复制代码
存疑1:attachInfo在哪里赋值?
我们点进去看一下 getRunQueue().post(action);
是何方神圣。
private HandlerActionQueue getRunQueue() { if (mRunQueue == null) { mRunQueue = new HandlerActionQueue(); } return mRunQueue; } 复制代码
上面代码很简单,那么接下来就需要看看 HandlerActionQueue()
是什么玩意了。
public class HandlerActionQueue { private HandlerAction[] mActions; private int mCount; //注释1 public void post(Runnable action) { postDelayed(action, 0); } //注释2 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++; } } public void removeCallbacks(Runnable action) {} //假装不知道这个是执行方法 public void executeActions(Handler handler) {} public int size() {} public Runnable getRunnable(int index) {} public long getDelay(int index) {} private static class HandlerAction {} } 复制代码
先看注释1
可以看到这个post()
方法 其实就是getRunQueue().post(action);
它内部调用了注释2
方法 也就是postDelayed()
注释2
简单看了里面的代码 发现大概逻辑只是对 我们post
的runnable
进行缓存起来,那么我们从哪里真正执行这个runnable
呢? 我发现 我们一路跟进来直到postDelayed()
方法时并没有看到对应的执行方法那么我们就放大招了
影·奥义!时光回溯。
我们看看getRunQueue()
时 返回的mRunQueue在哪里调用的就好了。
//不会上传图片...大小不懂得控制 所以用代码块 private HandlerActionQueue getRunQueue() { if (mRunQueue == null) { mRunQueue = new HandlerActionQueue(); } return mRunQueue; } void dispatchAttachedToWindow(AttachInfo info, int visibility) { mAttachInfo = info; //忽略的一些代码 if (mRunQueue != null) { mRunQueue.executeActions(info.mHandler); mRunQueue = null; } } 复制代码
通过检查对mRunQueue
的引用 可以看到view
代码内只有这两个方法有所使用,那么第一个方法我们以及看过了 所以我们重点看下第二个方法。
从上面第二个方法 可以得知mAttachInfo
由 dispatchAttachedToWindow(AttachInfo info, int visibility)
内的info
赋值 那么 info
又在哪里生成?先继续看下去。
通过对dispatchAttachedToWindow()
的调用关系可以发现以下方法会调用
private void performTraversals() { // cache mView since it is used so much below... final View host = mView; /.../ //注释1 host.dispatchAttachedToWindow(mAttachInfo, 0); /.../ performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//执行测量 performLayout(lp, mWidth, mHeight);//执行布局 performDraw();//执行绘制 } 复制代码
可以看到 host
内会调用这个方法,并且将mAttachInfo
作为参数传入,而这个host
是一个DecorView
为什么是DecorView
? 我们可以反推回去验证 通过源码我们得知 host
是 ViewRootImpl
中的mView
的成员变量 对mView
检查赋值的地方 可以看到以下代码:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view;//对mView 赋值 /.../ } } } 复制代码
那什么时候能调用到setView()
呢? 可以看到setView()
并不是静态方法,所以要调用并需要引用实例才可以。
那么我们可以看看构造方法
public ViewRootImpl(Context context, Display display) { mContext = context; /.../ //注释1 mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context); } 复制代码
可以看到注释1处的代码我们能够得知 mAttachInfo
是在ViewRootImpl
构造器中创建出来的。
我们再对构造器查看调用 可以发现再 WindowManagerGlobal
的addView()
方法 会创建ViewRootImpl
,
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { /.../ root = new ViewRootImpl(view.getContext(), display); } 复制代码
而WindowManagerGlobal
的addView()
方法 又被WindowManagerImpl
的addView()
调用
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); } 复制代码
再对这个方法进行调用检查可以看到
@Override public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); //注释1 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 (r.mPreserveWindow) { a.mWindowAdded = true; r.mPreserveWindow = false; ViewRootImpl impl = decor.getViewRootImpl(); if (impl != null) { impl.notifyChildRebuilt(); } } if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; wm.addView(decor, l); //注释2 } else { a.onWindowAttributesChanged(l); } } } else if (!willBeVisible) { if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set"); r.hideForNow = true; } } 复制代码
可以在注释1
处:会返回一个DecorView
并且在注释2处添加进入。 至此 我们可以得知ViewRootImpl
处的mView
就是 DecorView
。
知识点:并且我们可以从上面代码看出在onResume
时 view
才会被添加在window
内并且执行view
的测量布局绘制 这就是为什么在onCreate()
时获取到view
宽高会是0的原因,因为那时view
都没有添加进window
呢!!!
时间再次穿梭 回到ViewRootImpl
的performTraversals()
处
既然已知mView
就是DecorView
那么这个DecorView
是一个继承于FrameLayout
的ViewGroup
, 我们在DecorView
和FrameLayout
内没有找到对dispatchAttachedToWindow()
方法的处理,就自然而然的来到了ViewGroup
处。
@Override void dispatchAttachedToWindow(AttachInfo info, int visibility) { final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { final View child = children[i]; //遍历调用 这样最终会回到View的dispatchAttachedToWindow() child.dispatchAttachedToWindow(info, combineVisibility(visibility, child.getVisibility())); } } 复制代码
这时候我们又回到了View
的dispatchAttachedToWindow()
方法内的mRunQueue.executeActions(info.mHandler)
;并点击去看源码
public void executeActions(Handler handler) { synchronized (this) { final HandlerAction[] actions = mActions;//注释1 for (int i = 0, count = mCount; i < count; i++) { final HandlerAction handlerAction = actions[i]; handler.postDelayed(handlerAction.action, handlerAction.delay);//注释2 } mActions = null; mCount = 0; } } 复制代码
从注释1
处 我们可以看到mActions
其实是我们用view.post
方法时 传入的runnable
的存储数组。 而注释2
处 就会将runnable
交给handler.post()
方法并且添加进这个Handler
所持有的Looper
内部的MessageQueue
中。
至此我们可以大概的进行一次总结了。
总结:
1:View
内部的mAttachInfo
会在View
的dispatchAttachedToWindow()
方法内赋值 在dispatchDetachedFromWindow()
赋值为null
,并且mAttachInfo
的根本是在 ViewRootImpl
的构造器内创建的,所以我们就可以知道当view
的attchInfo
不为空时 这个 view
是已经被添加进窗口内的,如果为null
就说明view
没有在window
内。
2: 我们能通过view.post()
正确的获取View
的宽高主要得益于Android
内的生命周期是被Handler
所驱动的,所以当ViewRootImpl
在Activity
的onResume()
生命周期内被创建时,其实主线程的Handler
是在执行处理一个Message
的流程中,虽然我们从上面ViewRootImpl
内的performTraversals()
源码中看到 view
缓存的runnable
会在performMeasure(), performLayout(),performDraw()
这些方法前先被post
出去并且添加到MessageQueue
链表中,但是这些runnable
是属于下一个Message
的,而performMeasure(), performLayout(),performDraw()
这三个方法是属于本次Message
的逻辑,只有本次消息处理完成Handler
内部的Looper
才会进行下一次消息的处理,最终保证了 View.post()
能够正确的拿到View
的宽高。
作者:FeanCheng
链接:https://juejin.cn/post/6916024212696236040
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。