view.post 获取宽高,源码解析

400 阅读2分钟

1.view.post

文中涉及Activity启动相关

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    //view获取宽高
    view.post{
        val measuredWidth = view.measuredWidth
    }
}

2. View.java post()

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        //当mAttachInfo!=null时,
        //向主线程的mHandler里发生消息,
        //这里view已经绘制完成,自然可以拿到view的宽高
        return attachInfo.mHandler.post(action);
    }

    //在HandlerActionQueue里保存runnable
    getRunQueue().post(action);
    return true;
}

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++;
        }
    }
    
    //拿来存放的消息,向主线程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;
        }
    }

源码分析

AttachInfo 不为空时,
view已经绘制完成,自然可以拿到view的宽高。

AttachInfo 什么情况下不为空呢?
不为空时为什么会获取到狂高呢?
dispatchAttachedToWindow()

View.java

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    //mAttachInfo赋值
    mAttachInfo = info;
    ...
    //执行mRunQueue里的runnable
    if (mRunQueue != null) {
        mRunQueue.executeActions(info.mHandler);
        mRunQueue = null;
    }

ViewGroup.java

@Override
@UnsupportedAppUsage
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
    super.dispatchAttachedToWindow(info, visibility);
    ...
    for (int i = 0; i < transientCount; ++i) {
        View view = mTransientViews.get(i);
        //赋值给子view
        view.dispatchAttachedToWindow(info,
                combineVisibility(visibility, view.getVisibility()));
    }
}
Activity视图结构
我们知道Activity的视图结构 
PhoneWindow->DecorView(ViewRootImpl)->setContentView()
ViewRootImpl是在onResume时创建和DecorView关联,
ViewRootImpl.java
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
        boolean useSfChoreographer) {
   
   //创建mAttachInfo,
   ...
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
            context);
    ...
    //创建Choreographer
    mChoreographer = useSfChoreographer
            ? Choreographer.getSfInstance() : Choreographer.getInstance();
    ...
}


public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
    synchronized (this) {
        if (mView == null) {
        
            //decorView 
            mView = view;
            //执行绘制流程
            requestLayout();
            //设置decorview 的parent
            view.assignParent(this);
            
      }      
      
    //执行绘制  
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

scheduleTraversals
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        //设置同步屏障,只能处理异步消息 msg.target==null
        mHandler.getLooper().getQueue().postSyncBarrier();
        //mainHandler 发送mTraversalRunnable
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        //执行doTraversal
        doTraversal();
    }
}

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
         //移除同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
        //执行绘制流程
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

performTraversals
private void performTraversals() {
    //decorView 
    final View host = mView;
    //给所有的view设置mAttachInfo
    host.dispatchAttachedToWindow(mAttachInfo, 0);
    ...
    //测量布局绘制
    performMeasure()
    perforLayout()
    perforDraw()
    ...
    
dispatchAttachedToWindow()的执行
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mAttachInfo = info;
    // Transfer all pending runnables.
    //向handler发送view.post的消息
    if (mRunQueue != null) {
        mRunQueue.executeActions(info.mHandler);
        mRunQueue = null;
    }
 }

3. 总结:

1.AttachInfo不为空时,view的测量已经完成,所有能获取到宽高。 2.getRunQueue().post(action);怎么获取的宽高

源码执行流程:
    mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    ->dispatchAttachedToWindow()->mRunQueue.executeActions(info.mHandler);
    ->handler.postDelayed(handlerAction.action, handlerAction.delay);
    ->performMeasure()
    
  明明是先发送的获取宽高的消息,在执行的测量,为何还能获取到宽高呢?
  
  首先
   mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
   
   向主线程的Handler发送《测量绘制的异步任务》,
   然后才向主线程Handler发送的《获取宽高任务》,Hanlder消息是排队执行的,
   所以《测量绘制消息》的任务执行完毕,才会执行《获取宽高消息》的任务。
   自然可以获取到宽高。

1684251346213.png

main-Handler当消息1在执行中,handler又添加了消息2,
  只有消息1执行完毕,才会执行消息2,
  所以始终是先测量完,在去获取的宽高。