View的post()为什么可以获取View的宽高

2019-05-24  本文已影响0人  一行代码
一、View.post()
 public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        getRunQueue().post(action);
        return true;
    }

从上面代码可以知道,当调用post()方法时,首先会判断mAttachInfo是否为空,如果不为空,则调用Handler处理消息,否则,将将消息放入到RunQueue消息队列。

View.java
  private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }
HandlerActionQueue.java

private HandlerAction[] mActions;
 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++;
        }
    }

从上面代码知道, getRunQueue()创建一个HandlerActionQueue,并将我们使用的runable放入到mActions内。

二、runable队列被执行
在HandlerActionQueue中我们知道,有一个executeActions(handler)方法。方法源码如下:
HandlerActionQueue.java

   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;
        }
    }

从上面源码我们知道,executeActions内部通过for循环的方式,将消息队列的Runable取出,使用Handler发送到主线程。

三、HandlerActionQueue的executeActions方法合适被执行。
在View的源码中 Ctrl+F 我们搜索mRunQueue.executeActions。中我们找到只有在View的dispatchAttachedToWindow方法中执行。dispatchAttachedToWindow的核心代码如下:
  void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        ...
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();

        ...
    }

所以当View执行dispatchAttachedToWindow方法时,才将我们最开始发送的Runable对象发送到主线程处理。

但是,当我们在View内部搜索何时调用dispatchAttachedToWindow时,并没有找到。但是View的绘制、测量、布局,都由有父布局开始,所以我们在父布局的中查找调用的地方。

四、ViewGroup的dispatchAttachedToWindow
在ViewGroup的内部我们找到两个地方调用子View的dispatchAttachedToWindow,一是在addViewInner内调用,addViewInner使用addView调用的,这就是我们在手动创建View时,
没有调用父View的addView()方法将View添加进父View时,我们添加的View的post方法不执行的原因。二是在dispatchAttachedToWindow方法内。
 void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        ...
        super.dispatchAttachedToWindow(info, visibility);
        ...
        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()));
        }
        ...
    }

ViewGroup的dispatchAttachedToWindow()内主要是调用父类的dispatchAttachedToWindow()方法,然后循环子View,调用子View的dispatchAttachedToWindow()。

而ViewGroup的的dispatchAttachedToWindow()方法什么时候被调用呢?从上面我们知道,子View的dispatchAttachedToWindow()是有父View的dispatchAttachedToWindow()调用。而DecorView是我们所有布局的父布局。所以最终我们的View的dispatchAttachedToWindow()调用是由DecorView的dispatchAttachedToWindow()方法发起的。但是DecorView的dispatchAttachedToWindow()什么时候调用呢。由于我们之前学习了View的绘制流程【http://note.youdao.com/noteshare?id=e58f9423ec333f21ad673f971d340f5b&sub=9E9245E12A244C5395734561C7359B37】,我们知道,View的测量、布局、绘制都是从DecorView开始,都是在ViewRootImpl内的performTraversals()内调用,所以我们接下来看一下performTraversals()核心代码

五、ViewRootImpl的 performTraversals()
performTraversals的核心代码如下
 private void performTraversals() {
    //是DecorView
     final View host = mView;
     ...
     host.dispatchAttachedToWindow(mAttachInfo, 0);
     ...
     performMeasure();
     ...
     performLayout();
     ...
     performDraw();
     ...
 }

从上面我们知道了,原来dispatchAttachedToWindow()的调用是由、ViewRootImpl的 performTraversals() 发起的,但是我们注意到,dispatchAttachedToWindow()的发起是在performMeasure();之前。但是那为什么我们能够在View.post()内获取View的宽高呢。

六、为什么dispatchAttachedToWindow()的发起是在performMeasure(),而我们我们能够在View.post()内获取View的宽高?
由于之前我们分析过ViewRootImpl的performTraversals()执行是在ViewRootImpl的TraversalRunnable内部类中执行。而TraversalRunnable是一个实现Runable的ViewRootImpl的内部类。而scheduleTraversals()的执行是由scheduleTraversals()方法实现。所以分析如下:
ViewRootImpl.java
    //1、执行mTraversalRunnable
     void scheduleTraversals() {
            ...
            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
           ...
    }
   // 2、执行doTraversal();
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
  // 3、执行performTraversals();
   void doTraversal() {
        ...
        performTraversals();
        ...
    }

从上面我们知道,TraversalRunnable内最终执行了performTraversals()。而TraversalRunnable的执行是由mChoreographer来实现的,那mChoreographer怎么实现执行的呢。

Choreographer的核心源码如下:

Choreographer.java

public final class Choreographer {
      private final Looper mLooper;
    private final FrameHandler mHandler;
        //保证没个线程内都是不同的Choreographer实例
        private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };
    //主线程的Choreographer
    private static volatile Choreographer mMainInstance;
    //构造函数
     private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;
        mHandler = new FrameHandler(looper);
        ...
    }
    
    //处理Runable
      public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }
      @TestApi
    public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
        ...
        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }
    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        ...

        synchronized (mLock) {
           ...
                使用Handler将Runable发送出去处理。
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            ...
        }
    }
    private final class FrameHandler extends Handler {
        public FrameHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_DO_FRAME:
                    doFrame(System.nanoTime(), 0);
                    break;
                case MSG_DO_SCHEDULE_VSYNC:
                    doScheduleVsync();
                    break;
                case MSG_DO_SCHEDULE_CALLBACK:
                    doScheduleCallback(msg.arg1);
                    break;
            }
        }
    }
}

从上面知道,在View的绘制过程也是基于消息机制实现,在View的加载过程中是在主线程实现的,所以Choreographer的Looper是主线程,内部的Handler也是在主线程处理消息。所以最终View的绘制流程是Choreographer内部主线程的Handler发送消息并处理实现。

到这里,我们回头看一下,我们使用View.post()方法时,最终也是有主线程的Handler发送并处理消息实现。由于之前我们学习的Handler机制【http://note.youdao.com/noteshare?id=8670d5fcdde6bf53683fa58f489ca1d3&sub=ECF647152E3B4D42BF4DA272B156E6FB
在Looper内循环取出消息的方式来处理消息,当一个消息处理完之后再取出另一个消息,由Handler处理。既然这样,我们的View的绘制加载和View.post()都是又主线Handler来,所以只有当View的绘制加载的消息完成之后,才会处理我们View.pos()发送来的消息,所以我们才够在View.post()内获取到View的宽高。

最后附上一张流程图便于理解

image

参考文章

上一篇下一篇

猜你喜欢

热点阅读