美文网首页
View的post()为什么可以获取View的宽高

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

作者: 一行代码 | 来源:发表于2019-05-24 14:05 被阅读0次
    一、View.post()
    • post(Runnable action)
     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消息队列。

    • HandlerActionQueue
    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内。

    • 总结:
      其实我们调用的post方法执行流程有两种,一种是直接使用Handler去执行,第二是将我们的runabale存储到队列中。
    二、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()方法实现。所以分析如下:
    
    • 我们先看一下performTraversals()的执行
    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执行TraversalRunnable

    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

    参考文章

    相关文章

      网友评论

          本文标题:View的post()为什么可以获取View的宽高

          本文链接:https://www.haomeiwen.com/subject/twugzqtx.html