TextView.getWidth() = 0?

512 阅读2分钟

工作中应该都有碰到过这个问题,解决方案一般有view.post(Runnable)、view.getViewTreeObserver().addOnGlobalLayoutListener(OnGlobalLayoutListener listener)。 昨天仔细了解了一下view.post(Runnable)方法,为什么可以在里面拿到view的宽度呢?

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不为空,调用的是attachInfo.mHandler来处理这个action的,否则getRunQueue().post(action)来处理的。来看一下getRunQueue():

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

这里创建了一个存放HandlerAction的队列(数组),所有之前通过post传进来的任务全部,在mAttachInfo == null时,都存在这里面。队列初始大小为4,可以通过GrowingArrayUtils扩容。HandlerAction是一个简单封装了Runnable和延时delay(long)的类。具体执行队列里面HandlerAction的方法为:

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()具体调用的地方有4个,View中1处,ViewGroup中有3处。在View中调用的位置为:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
       ** mAttachInfo = info**;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        mWindowAttachCount++;
        // We will need to evaluate the drawable state at least once.
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        if (mFloatingTreeObserver != null) {
            info.mTreeObserver.merge(mFloatingTreeObserver);
            mFloatingTreeObserver = null;
        }

        registerPendingFrameMetricsObservers();

        if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
            mAttachInfo.mScrollContainers.add(this);
            mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
        }
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            **mRunQueue**.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();
        ...
}

我们看一下方法名的意思:分发一个将View附加到Window的操作。 为什么在这个方法里面可以调用mRunQueue.executeActions()来执行之前所有存储的任务呢,答案当然是因为mAttachInfo = info在第一行被赋值了,而处理mRunQueue队列中任务的handler就在mAttachInfo中:

/**
* A Handler supplied by a view's {@link android.view.ViewRootImpl}. This
* handler can be used to pump events in the UI events queue.
* 这个handler可以把事件传给UI线程进行处理
*/
        final Handler mHandler;

这里我猜测handler是UI线程给ViewRootImpl,ViewRootImpl再给View的。(如有不对请指出)

ViewGroup中的dispatchAttachedToWindow()也类似,在ViewRootImpl的performTraversals()中递归遍历所有的子view,并把mAttachInfo付给所有的子view,这里ViewGroup和子view的mAttachInfo都是同一个。

在performTraversals()方法里面,performMeasure()在2155行、performLayout()在2200行、performDraw()在2347行;而dispatchAttachedToWindow()在1658行,我们知道view在绘制完成之后才可以拿到宽高,那为什么先执行dispatchAttachedToWindow()却可以拿到宽高呢,因为Android是基于消息驱动的,也就是performTraversals()和dispatchAttachedToWindow()最终都是转化位Message被looper从message里面取出来按顺序执行的。

结论:view.post()需要执行的任务在mAttachInfo == null时,会前被缓存起来,在mAttachInfo被赋值后依次执行;此后如果还有post进来的任务,则会立即执行。

参考文章:www.cnblogs.com/dasusu/p/80…