面试官:View.post() 为什么能够获取到 View 的宽高 ?-阿里云开发者社区
根据 ViewRootImpl 是否已经创建,View.post() 会执行不同的逻辑。如果 ViewRootImpl 已经创建,即 mAttachInfo 已经初始化,直接通过 Handler 发送消息来执行任务。如果 ViewRootImpl 未创建,即 View 尚未开始绘制,会将任务保存为 HandlerAction,暂存在队列 HandlerActionQueue 中,等到 View 开始绘制,执行 performTraversal() 方法时,在 dispatchAttachedToWindow() 方法中通过 Handler 分发 HandlerActionQueue 中暂存的任务。
除了通过 View.post()
获取视图宽高之外,还有两种比较推荐的方式。
第一种,onWindowFocusChanged()
。
第二种,OnGlobalLayoutListener
。
binding.dialog.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener{
override fun onGlobalLayout() {
binding.dialog.viewTreeObserver.removeOnGlobalLayoutListener(this)
...
}
})
对于View.post当View已经attach到window,直接调用UI线程的Handler发送runnable。如果View还未attach到window,将runnable放入ViewRootImpl的RunQueue中,而不是通过MessageQueue。RunQueue的作用类似于MessageQueue,只不过这里面的所有runnable最后的执行时机,是在下一个performTraversals到来的时候,也就是view完成layout之后的第一时间获取宽高,MessageQueue里的消息处理的则是下一次loop到来的时候。
View.postDelayed
package android.view;
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
public boolean postDelayed(Runnable action, long delayMillis) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.postDelayed(action, delayMillis);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().postDelayed(action, delayMillis);
return true;
}
}
Handler.postDelayed
package android.os;
public class Handler {
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
}
View.postDelayed与Handler.postDelayed的区别
当View已经attach到了window,两者是没有区别的,都是调用UI线程的Handler发送runnable到MessageQueue,最后都是由handler进行消息的分发处理。
但是如果View尚未attach到window的话,runnable被放到了ViewRootImpl#RunQueue中,最终也会被处理,但不是通过MessageQueue。
当视图树尚未attach到window的时候,整个视图树是没有Handler的(其实自己可以new,这里指的handler是AttachInfo里的),这时候用RunQueue来实现延迟执行runnable任务,并且runnable最终不会被加入到MessageQueue里,也不会被Looper执行,而是等到ViewRootImpl的下一个performTraversals时候,把RunQueue里的所有runnable都拿出来并执行,接着清空RunQueue。
由此可见RunQueue的作用类似于MessageQueue,只不过,这里面的所有 runnable最后的执行时机,是在下一个performTraversals到来的时候,MessageQueue里的消息处理的则是下一次loop到来的时候。
参考:
View#post与Handler#post的区别,以及导致的内存泄漏分析