IT虾米网

View.post(Runnble)的一点小问题详解

developer 2021年06月07日 手机开发 254 0

今天刚好看到一个问题,为什么onCreate() 中使用 View.post(Runnable)可以拿取到View的宽高,第一想法就是内部利用handler将Runnbale加入主线程MessageQueue,执行完测量任务之后再执行Runnable。

点开源码之后发现好像没那么简单,不过也没那么难

public boolean post(Runnable action) {
    
    final AttachInfo attachInfo = mAttachInfo; 
    if (attachInfo != null) {
    
        return attachInfo.mHandler.post(action); 
    } 
    getRunQueue().post(action); 
    return true; 
} 

post()方法中,如果有attachInfo,直接attachInfo.mHandler.post(),若没有就getRunQueue().post();

看下第一种情况,attachInfo是什么时候不为null,还有mHandler是哪里的Handler?
// View 
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    
    mAttachInfo = info; 
    ....  
    if (mRunQueue != null) {
    
        mRunQueue.executeActions(info.mHandler); 
        mRunQueue = null; 
    } 
} 

mAttachInfo 是在diapatchAttachedToWindow()赋值的,ViewRootImpl的performTraversals()会调用ViewGroup 的 diapatchAttachedToWindow(),最后会调用子View的diapatchAttachedToWindow()

// ViewRootImpl 
private void performTraversals() {
    
	// 如果是首次绘制 
	if (mFirst) {
    
		host.dispatchAttachedToWindow(mAttachInfo, 0); 
	} 
} 
 
// ViewRootImpl 
final ViewRootHandler mHandler = new ViewRootHandler(); 
 
// ViewRootImpl 
public ViewRootImpl(Context context, Display display) {
    
	mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context); 
} 

从上面代码可以看出,mAttachInfo在ViewRootImpl构造,并且performTraversals(),若i是首次绘制,会把mAttachInfo赋给View。

View里面的Handler其实就是ViewRootImpl的ViewRootHandler 。

ViewRootHandler直接在主线程构造,所以是消息处理都是在主线程中。

小结:

diapatchAttachedToWindow()后,mAttachInfo不为null,就可以直接通过ViewRootHandler处理Runnable,最后能够获取View宽高。

但是不是 diapatchAttachedToWindow()之前,就已经测量好宽高了呢?


再看下第二种情况,getRunQueue().post(action);
// View.java 
private HandlerActionQueue mRunQueue; 
 
private HandlerActionQueue getRunQueue() {
    
    if (mRunQueue == null) {
    
        mRunQueue = new HandlerActionQueue(); 
    } 
    return mRunQueue; 
} 
 
// HandlerActionQueue  
public class HandlerActionQueue {
    
    private HandlerAction[] mActions; 
    private int mCount; 
 
    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为null的时候,View.post()会将Runnable保存在HandlerActionQueue中,等待dispatchAttachedToWindow() 的时候,mRunQueue.executeActions(info.mHandler) 会执行保存在HandlerActionQueue的Runnbale。

可见,我没有考虑到测量绘制还没开始的情况,如果测量还没开始就需要一个数组将Runnable存起来,在测量结束后执行。


但是都同样存在一个问题:是不是 diapatchAttachedToWindow()之前,就已经测量好宽高了呢?

// ViewRootImpl 
private void performTraversals() { 
	host.dispatchAttachedToWindow(mAttachInfo, 0); 
	 
	performMeasure(); 
	 
	performLayout(); 
 
	performDraw(); 
} 

上面的伪代码指出,performTraversals()中是先执行dispatchAttachedToWindow(),后面才执行measure()layout()等测量,这个怎么保证post.view()肯定能拿到宽高?

后面终于搞清楚了!

原来无论是perfromTraversals,还是后面的Runnable,都是保存在MessageQueue,Looper会执行完当前的任务,再从MessageQueue取出下个任务接着执行;

第一种情况,attachInfo.mHandler.post(action),和第二种情况,在dispatchAttachedToWindow executeActions(),都是增加任务到在主线程的MessageQueue中,而Looper只会处理完当前的测量任务才会去执行View.post的任务。

可以看下面的示意图,当前还在执行performTraversals(),后续的任务还在MessageQueue等待执行。

在这里插入图片描述


发布评论
IT虾米网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

基于Android-28 的源码 Activity 启动流程分析详解
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。