最近由于我的缘由,致使要离开杭州了,人生就是一次又一次的意外,你不知道忽然会发生什么,你能作到的只有把握好每一次机会java
在Android中,想要获取View的宽高,常见的基本都是接调用View.getWidth或者View.getMeasuredWidth方法来获取宽高,可是每每在oncreate或者onresume中,获得的值都是0,也就能够理解为此时View的绘制流程并无执行完,熟悉Activity启动流程的朋友都知道,Activity的生命周期和页面的绘制流程并非一个串行的状态,没有特定的前后关系,因此也不难理解获取的值是0了android
再次回到主题,那为何View.post(),就能够获取到准确的值呢,不妨猜想一下,首先总体上思考一下,想要实现知道准确的宽高,那就是post的Runnable那确定是在View整个绘制流程结束以后才执行的,主线程又是基于Looper的消息机制的,若是把Runnable直接做为一个消息插入消息队列,那么很明显不能保证这种效果,熟悉View绘制流程的朋友知道,View的绘制是在ViewRootImp中的,但View的绘制其实也是一个Runnable消息,那么咱们可不能够先把post的这个Runnable给缓存起来,等到绘制的Runnable执行完以后,再来通知去执行,这样就可以获取到准确的宽高了。数组
本文源码是API是android-28,不一样版本可能有些差别,须要读者自行注意缓存
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}
复制代码
能够看到方法很简答,主要就是一个attachInfo,若是不为空就直接使用attachInfo.mHandler去执行这个action,若是为空,把Runnable放入一个相似队列的东西里面app
咱们再回头想一想开头说的话,好像还真是这么实现的,这里的mAttachInfo其实能够看作为是否已经绘制好了的一个标志,若是不为空,说明绘制完成,直接handler执行action,若是为空,说明没有绘制完,这时候就把Runnable缓存起来,那么关键点也就来了,这个mAttachInfo是何时被赋值的,全局搜索下赋值ide
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
......
}
复制代码
从这个方法名字,咱们也应该能看出来,绑定到window的时候,此时会进行赋值mAttachInfo,也就意味着绘制完毕,固然,咱们还不知道dispatchAttachedToWindow这个方法是何时调用的,先这么理解着oop
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
复制代码
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;
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++;
}
}
复制代码
能够看到,其实是维护了一个HandlerActionQueue类,内部维护了一个数组,长度竟然是固定为4(问号脸),而后将这些Runnabel给缓存起来。那疑问就来了,既然是缓存起来,那何时执行的,能够看到有个executeActions方法post
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;
}
}
复制代码
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
......
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
....
}
复制代码
经过传递进来的handler,而后将内部缓存的Runnable去执行,ctrl一下,看看哪里调用了,咦。又是dispatchAttachedToWindow这个方法。this
先总结下,View.post(Runnable) 的这些 Runnable 操做,在 View 被 attachedToWindow 以前会先缓存下来,而后在 dispatchAttachedToWindow() 被调用时,就将这些缓存下来的 Runnable 经过 mAttachInfo 的 mHandler 来执行。在这以后再调用 View.post(Runnable) 的话,这些 Runnable 操做就不用再被缓存了,而是直接交由 mAttachInfo 的 mHandler 来执行spa
因此问题最终也就到这个方法这里了,这个方法是何时被调用的,ctrl一下。。竟然没有地方调用,那确定是隐藏类调用了,此时祭出咱们的Source Insight,从源码里面找找。
private void performTraversals() {
.....
final View host = mView;
.....
host.dispatchAttachedToWindow(mAttachInfo, 0)
....
}
final ViewRootHandler mHandler = new ViewRootHandler();
public ViewRootImpl(Context context, Display display) {
...
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
....
}
复制代码
这个方法是否是很眼熟,View的绘制流程就从这个方法开始的,能够看到的是,mAttachInfo这个对象是在ViewRootImpl初始化的时候就赋值了,而且Handler是直接在主线程中建立的,这个就能够说明了为何View.post是能够更新UI的,由于最终的Runnable是在主线程的Handler中去执行的,天然是能够更新UI的。
可是你们有可能还有个疑问,那既然View的绘制也是这个方法执行的,dispatchAttachedToWindow也是在这个方法执行的,那怎么能保证必定是在View的绘制流程完成以后才去执行dispatchAttachedToWindow的呢。
答案也很简单,由于android主线程是基于Looper的消息机制的,不断的从绑定Looper的MessageQueue中去获取message去执行,View的绘制操做其实也是一个Runnable对象,因此在执行performTraversals()方法的时候,调用dispatchAttachedToWindow方法,这个时候,因此子View经过View.post(Runnable)缓存的Runnabel是会经过mAttachInfo.mHandler 的 post() 方法将这些 Runnable 封装到 Message 里发送到 MessageQueue 里。mHandler 咱们上面也分析过了,绑定的是主线程的 Looper,因此这些 Runnable 其实都是发送到主线程的 MessageQueue 里排队的,因此也就能够保证这些 Runnable 操做也就确定会在 performMeasure() 操做以后才执行,宽高也就能够获取到了,咱们也能够在源码中找到些许痕迹
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
.....
performTraversals();
....
}
}
复制代码