在工作中碰到了这样一个问题,在RecyclerView
的onBindViewHolder
里执行了下面的代码:
@Override
public void onBindViewHolder(CategoryFragment.ViewHolder holder, int position) {
......
Task.callInBackground{
......
mView.post(new Runnable() {
@Override
public void run() {
//do something
}
});
}
}
简单说就是在异步线程调用View.post()将某些逻辑分发至主线程执行,然而这个runnable的代码并没有被执行。
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线程执行,好像没什么问题,那为什么这里没有执行呢?我们来看一下它的源码:
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;
}
这下好像发现问题了,他是通过attachInfo
中的Handler
来执行runnable
的,如果这个attachInfo
为空,则将runnable
加入到ViewRootImpl
的RunQueue
中等待,那么这个attachInfo
什么时候被赋值呢?答案是View attach到window上的时候。
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
......
}
也就是说onBindViewHolder
的时候View还没有attach到window,看一下log,确实如此。知道了为什么不执行,那么解决起来就很简单了,直接new一个Handler来执行这些任务就好了。
不过这个runnable不是被加到了一个
RunQueue
里么,Google还写了注释说假设这个runnable一会儿会成功执行,一会儿是多大一会儿呢?继续看源码,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的时候用来执行后续任务的,这些任务会在下一次调用performTraversals
的时候执行,好吧,你赢了。。。
static final class RunQueue {
//储存所有的runnable
private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();
//View没有attach到window,View.post()最终调用到这里
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最终调用到这里
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
}
}
}
//执行所有runnable,在ViewRootImpl.performTraversals()中被调用
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;
......
}
}
谷爹可能也意识到这非常的不科学,让人难以理解,所以。。。爸爸在7.0以上给你修好了,啊哈哈!好吧,我们来看Google爸爸改了什么,其实非常的简单!首先简单优化了一下RunQueue
,改了个名字叫HandlerActionQueue
了,View.post()
的流程也基本没有什么变化,然后最关键的一点来了:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
......
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
......
}
在view attachedToWindow的时候执行RunQueue中所有没执行的任务,就是这么easy。
总结:
View.post()只有在View attachedToWindow的时候才会立即执行。
在attach之前调用的话,低于7.0时可以自己new一个Handler或者自定义View重写dispatchAttachedToWindow自己储存pendingTask并在attached之后执行。