注意:View.post(Runnable)

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不執行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章