记一次错误的使用View.post(Runnable)

96
轻微
2016.03.24 19:33* 字数 382

我的应用场景是这样子的
我在一个异步操作中用EventBus 发送了一个事件.
在另外一个Activity 中对这个事件进行接收.
因为是异步所以我使用了View.post方法企图让它回到主线程更新这些数据.(此时我的View 处于Detach 状态)
但是事情并没有这么美好.
我的Runnable方法没有触发Run()方法.
问题出在哪里?

我排查了很久.很久,很久....

我们进入post方法:

/**
 * <p>Causes the Runnable to be added to the message queue.
 * The runnable will be run on the user interface thread.</p>
 *
 * @param action The Runnable that will be executed.
 *
 * @return Returns true if the Runnable was successfully placed in to the
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 *
 * @see #postDelayed
 * @see #removeCallbacks
 */
public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }
    // Assume that post will succeed later
    ViewRootImpl.getRunQueue().post(action);
    return true;
}

因为我的View 处于Detach状态. 所以attachInfo 为空.所以我们进入了
ViewRootImpl.getRunQueue().post(action);



/**
 * The run queue is used to enqueue pending work from Views when no Handler is
 * attached.  The work is executed during the next call to performTraversals on
 * the thread.
 * @hide
 */
static final class RunQueue {
    private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();

    void post(Runnable action) {
        postDelayed(action, 0);
    }

    void postDelayed(Runnable action, long delayMillis) {
        HandlerAction handlerAction = new HandlerAction();
        handlerAction.action = action;
        handlerAction.delay = delayMillis;

        synchronized (mActions) {
            mActions.add(handlerAction);
        }
    }

 
    void executeActions(Handler handler) {
        synchronized (mActions) {
            final ArrayList<HandlerAction> actions = mActions;
            final int count = actions.size();

            for (int i = 0; i < count; i++) {
                final HandlerAction handlerAction = actions.get(i);
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            actions.clear();
        }
    }

正常流程是我们用post 方法加入mActions 中,在performTraversals 中调用executeActions()对这些信息进行消耗.
到这里看起来一切都是那么美好.
问题出在哪里?


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

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

sRunQueues 是一个ThreadLocal 类型,也就是说你上面加入的mActions跟主线程中的mActions 不是同一个.

总结:
当你使用View 处于Detach 状态.
调用线程为非主线程.
View.post并不能把你的runnable带到主线程执行.

BTW:
我们可以看到RunQueue 上面有这样的注释.The work is executed during the next call to performTraversals on the thread. 也就是说当你正确使用了post 方法(你的Runnable 正确加入到主线程的mActions ) 那么他也是要在下一个performTraversals方法中执行.如果 performTraversals 不到来,你的Runnable 是不会执行的.什么时候确保这个快速的到来呢,使用View.invalidate()

android
Web note ad 1