在工作中碰到了這樣一個問題衔彻,在RecyclerView
的onBindViewHolder
里執(zhí)行了下面的代碼:
@Override
public void onBindViewHolder(CategoryFragment.ViewHolder holder, int position) {
......
Task.callInBackground{
......
mView.post(new Runnable() {
@Override
public void run() {
//do something
}
});
}
}
簡單說就是在異步線程調(diào)用View.post()將某些邏輯分發(fā)至主線程執(zhí)行薇宠,然而這個runnable的代碼并沒有被執(zhí)行。
View.post的官方注釋是這樣寫的:Causes the Runnable to be added to the message queue. The runnable will be run on the user interface thread.將runnable添加到消息隊列艰额,runnable將會在UI線程執(zhí)行澄港,好像沒什么問題,那為什么這里沒有執(zhí)行呢柄沮?我們來看一下它的源碼:
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;
}
這下好像發(fā)現(xiàn)問題了回梧,他是通過attachInfo
中的Handler
來執(zhí)行runnable
的废岂,如果這個attachInfo
為空,則將runnable
加入到ViewRootImpl
的RunQueue
中等待狱意,那么這個attachInfo
什么時候被賦值呢湖苞?答案是View attach到window上的時候。
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
......
}
也就是說onBindViewHolder
的時候View還沒有attach到window详囤,看一下log,確實如此财骨。知道了為什么不執(zhí)行,那么解決起來就很簡單了藏姐,直接new一個Handler來執(zhí)行這些任務(wù)就好了隆箩。
不過這個runnable不是被加到了一個
RunQueue
里么,Google還寫了注釋說假設(shè)這個runnable一會兒會成功執(zhí)行包各,一會兒是多大一會兒呢摘仅?繼續(xù)看源碼,RunQueue
的源碼是這樣寫的:
/**
* 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
*/
RunQueue是在View還沒有attach到window上问畅,即沒有handler的時候用來執(zhí)行后續(xù)任務(wù)的娃属,這些任務(wù)會在下一次調(diào)用performTraversals
的時候執(zhí)行,好吧护姆,你贏了矾端。。卵皂。
static final class RunQueue {
//儲存所有的runnable
private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();
//View沒有attach到window秩铆,View.post()最終調(diào)用到這里
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);
}
}
//View沒有attach到window,View.removeCallbacks最終調(diào)用到這里
void removeCallbacks(Runnable action) {
final HandlerAction handlerAction = new HandlerAction();
handlerAction.action = action;
synchronized (mActions) {
final ArrayList<HandlerAction> actions = mActions;
while (actions.remove(handlerAction)) {
// Keep going
}
}
}
//執(zhí)行所有runnable灯变,在ViewRootImpl.performTraversals()中被調(diào)用
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();
}
}
//對runnable的簡單封裝
private static class HandlerAction {
Runnable action;
long delay;
......
}
}
谷爹可能也意識到這非常的不科學(xué)殴玛,讓人難以理解,所以添祸。滚粟。。爸爸在7.0以上給你修好了刃泌,啊哈哈凡壤!好吧,我們來看Google爸爸改了什么耙替,其實非常的簡單亚侠!首先簡單優(yōu)化了一下RunQueue
,改了個名字叫HandlerActionQueue
了俗扇,View.post()
的流程也基本沒有什么變化硝烂,然后最關(guān)鍵的一點來了:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
......
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
......
}
在view attachedToWindow的時候執(zhí)行RunQueue中所有沒執(zhí)行的任務(wù),就是這么easy铜幽。
總結(jié):
View.post()只有在View attachedToWindow的時候才會立即執(zhí)行钢坦。
在attach之前調(diào)用的話究孕,低于7.0時可以自己new一個Handler或者自定義View重寫dispatchAttachedToWindow自己儲存pendingTask并在attached之后執(zhí)行。