[Android源码解析] View.post到底干了啥

Android Handler:手把手带你深入分析 Handler机制源码中我们说到了在子线程中更新UI的几种方法,其中就有一种是使用view.post,那么到底view.post做了什么呢?我们今天就从源码的角度分析一下(基于Android10的源码,各个版本的源码大同小异)。

为什么view.post可以更新UI?

  • 首先我们看看源码部分:
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
    
        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }
    
  • 这里首先判断attachInfo是否为null,我们先看为空的情况下,就会执行getRunQueue().post(action), getRunQueue返回的是一个HandlerActionQueue,这里我们看下它的源码:
    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++;
            }
        }
    }
    
  • 我们看到这里其实是将我们post进入的runnable包装成HandlerAction存储起来,并没有真正的执行它。并且原始数组能存储4个runnable,如果需要的话会扩充的。
  • 接着我们看,如果不为null就会执行attachInfo.mHandler.post(action), 查看源码很容易得知这里使用的就是Handler机制,也比较容易理解。但是这里的attachInfo是什么呢?官方解释:A set of information given to a view when it is attached to its parent window, 意思是当一个view被attach到它的父类window上的时候,被赋予的一系列的信息。这里我们关注的是这个attachInfo什么时候被赋值,也就是什么时候不为空?查找源码attachInfo赋值的地方:
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        ...
        // Transfer all pending runnables.
        onAttachedToWindow();
    }
    
  • 我们发现在dispatchAttachedToWindow开始的时候mAttachInfo被赋值, 然后调用我们常用的onAttachedToWindow()方法做一些处理工作,那么这个地方又是哪里调用赋值的呢?熟悉View绘制流程的小伙伴就很清楚了,是在ViewRootImpl.performTraversals()中调用的,ViewRootImplViewRoot的实现类,它是连接WindowManagerDecorView的桥梁,是通过调用windowManager.addView方法的时候,紧接着调用了viewRoot.setView(view)触发了联系的。这里的dispatchAttachedToWindow经过调查发现只在ViewRootImplperformTraversals中有调用,下面我们看源码部分:
    private void performTraversals() {
    	final View host = mView;
    	...
    	host.dispatchAttachedToWindow(mAttachInfo, 0);
    	...
    	getRunQueue().executeActions(mAttachInfo.mHandler);
    	...
    	performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    	...
    	performLayout(lp, mWidth, mHeight);
    	...
    	performDraw();
    }
    
  • 我们看到确实在performTraversals中调用了dispatchAttachedToWindow方法,对mAttachInfo赋值,然后调用了executeActions去执行, 我们查看如下源码部分得知,其实走的是Handler机制:
    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;
        }
    }
    
  • 其实到这里我们对view.post的源码分析结束了,也解决了最开始提出的问题,为什么view.post可以更新UI?, 但是对这个方法的深入研究还没有结束。我们知道在Activity.onCreate()中直接调用view.getWidth得到的往往是0, 而通过view.post得到的值却不是0,这又是为何呢?

为什么view.post可以更新得到UI控件的宽高?

  • 想要回答上面的问题,我们就得从上面ViewRootImpl.performTraversals()方法说起,源码如下:
    private void performTraversals() {
    	final View host = mView;
    	...
    	host.dispatchAttachedToWindow(mAttachInfo, 0);
    	...
    	getRunQueue().executeActions(mAttachInfo.mHandler);
    	...
    	performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    	...
    	performLayout(lp, mWidth, mHeight);
    	...
    	performDraw();
    }
    
  • 我们知道Handler机制Android Handler:手把手带你深入分析 Handler机制源码中的MessageQueue是以一种队列的形式来存取的,所以先加入队列中的任务先执行,而且这里executeActions(mAttachInfo.mHandler);中的handler其实指的是主线程的handler,这个mAttachInfo是从哪里赋值的吗?我们在ViewRootImpl的构造方法中看到源码如下:
     final ViewRootHandler mHandler = new ViewRootHandler();
     public ViewRootImpl(Context context, Display display) {
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, 
        this,context)
     }
    
  • 我们看到这里的handler实际是ViewRootHandler, 所以实际的代码是在主线程执行的。
  • 从上面performTraversals的源码中看到是先执行了executeActions(mAttachInfo.mHandler);,然后依次执行了performMeasure,performLayout,performDraw方法,我们知道executeActions其实就是去执行我们post的runnable, 所以会给人一个感觉就是先执行runnable,然后执行的performMeasure,这样理解当然是不对的,如果真是这样的话获取控件的宽高肯定是0。所以实际是什么样呢 ?
  • 其实performTraversals()方法也是使用handler.post执行的runnable中的一个方法,源码如下:
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    void doTraversal() {
        if (mTraversalScheduled) {
            ...
            performTraversals();
            ...
        }
    }
    
  • performTraversals中的executeActions(mAttachInfo.mHandler);,其实是将要执行的runnable再一次的入队列,源码如下:
    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;
        }
    }
    
  • 所以实际上通过view.post(runnable)执行的runnable实际是在performMeasure,performLayout,performDraw之后执行的,所以才能够获取到真正的view的宽高。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值