注意:View.post(Runnable)

探讨了在不同Android版本中,View.post(Runnable)方法的执行条件,尤其是在Android7.0及以前版本中,若View未附着于Window且在子线程调用时,Runnable将不会执行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

View.post(Runnable)调用之后,Runnable不一定会执行,那么什么时候不执行呢?下面来分析一下:

交流 之 把事情讲清楚的技巧:结论先行,分析随后。

先给结论:在Android 7.0之前,View还没有附着到Window,且View.post(Runnable)在子线程执行,那么这个Runnable就不会执行。

首先看看它的源码(Android 7.0):

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

从源码可以看到,如果mAttachInfo存在,就会使用mAttachInfo.mHandler来发送消息,那这样的话 Runnable一般会执行完成。如果mAttachInfo不存在的话,就会走getRunQueue().post()方法,这个getRunQueue其实就是保存了一个Action集合,在需要的时候取出里面的Runnable再执行。

那么我们看看什么时候给mAttachInfo赋值的:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        ...
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();
        ...
    }

可以看到,在调用dispatchAttachedToWindow方法时,会给mAttachInfo赋值,在dispatchAttachedToWindow里面会调用onAttachedToWindow()方法,也就是说如果View附着到Window上,mAttachInfo就存在。

那什么时候去执行getRunQueue()里面保存的Runnable呢?细心的同学应该看到了,在dispatchAttachedToWindow里面有一段:

        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }

传递待定的Runnables,也就是说如果View还没有附着到Window,mAttachInfo就不存在,Runnable就会存储到mRunQueue里面,等到View需要附着到Window后(dispatchAttachedToWindow调用时),就会一一执行。不信?且看:

View.java

private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }
HandlerActionQueue.java

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) {
                //初始容量是4
                mActions = new HandlerAction[4];
            }
            //这里是扩容操作,扩容:x2
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            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;
        }
    }

分析到这里,大家可以看到最终只要View附着到Window,那么Runnable还是会执行。好的,别急,上面看到的代码是Android 7.0或以上的代码。

现在咱们来看一下Android 7.0之前的代码,以Android 5.0为例:

先看一下View.post(Runnable)源码:

public boolean post(Runnable action) {
        Handler handler;
        if (mAttachInfo != null) {
            handler = mAttachInfo.mHandler;
        } else {
            // Assume that post will succeed later
            ViewRoot.getRunQueue().post(action);
            return true;
        }

        return handler.post(action);
    }

这里与Android 7.0的区别在于:

ViewRoot.getRunQueue().post(action);

点进去看看是怎么实现:

static RunQueue getRunQueue() {
        RunQueue rq = sRunQueues.get();
        if (rq != null) {
            return rq;
        }
        rq = new RunQueue();
        sRunQueues.set(rq);
        return rq;
    }

看看sRunQueues是什么数据结构:

static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();

这里RunQueue是用ThreadLocal装的,ThreadLocal是用ThreadLocalMap实现的,而ThreadLocalMap是以key--value存储的,是用Thread来作为key值的。

再看看ViewRoot.getRunQueue()什么时候用的:

ViewRoot.java

private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;

        ...
        if (mFirst) {
            ...
            host.dispatchAttachedToWindow(attachInfo, 0);
            //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn);

        } else {
            desiredWindowWidth = frame.width();
            desiredWindowHeight = frame.height();
            if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
                if (DEBUG_ORIENTATION) Log.v(TAG,
                        "View " + host + " resized to: " + frame);
                fullRedrawNeeded = true;
                mLayoutRequested = true;
                windowResizesToFitContent = true;
            }
        }

        if (viewVisibilityChanged) {
            attachInfo.mWindowVisibility = viewVisibility;
            host.dispatchWindowVisibilityChanged(viewVisibility);
            if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
                if (mUseGL) {
                    destroyGL();
                }
            }
            if (viewVisibility == View.GONE) {
                // After making a window gone, we will count it as being
                // shown for the first time the next time it gets focus.
                mHasHadWindowFocus = false;
            }
        }

        boolean insetsChanged = false;

        if (mLayoutRequested) {
            // Execute enqueued actions on every layout in case a view that was detached
            // enqueued an action after being detached
            getRunQueue().executeActions(attachInfo.mHandler);

            ...
        }
        ...
    }

可以看到View.dispatchAttachedToWindow和ViewRoot.getRunQueue()都在这个方法里面调用了,那也就是说ViewRoot.getRunQueue()会在View附着到Window上的时候调用,然后根据当前线程取出用于存储Runnable的集合。到这里就恍然大悟了,ViewRoot.performTraversals是在UI线程执行的,那么ViewRoot.getRunQueue()里面除了主线程保存的Runnable,其他的都不会得到执行。因此可以得出结论:Android 7.0以下,View没有附着到Window时,在子线程调用View.post(Runnable)方法,会导致Runnable不执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值