View.post()方法在android7.0之前,在view没有attachToWindow的时候调用该方法可能失效,尤其异步线程,如在onCreate,onBindViewHolder时调用view.post方法,可能会不生效,在异步线程view.post方法不执行的情况居多。建议使用Handler post方法代替。
看下Android 7.0之前的源码,以4.4版本代码为例:
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;
}
如果在onCreate时调用post方法,此时attachInfo为null,会执行ViewRootImpl.getRunQueue().post(action);,attachInfo的赋值时在dispatchAttachedToWindow中赋值的,代码如下:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
//...
onAttachedToWindow();
//...
}
可以看到赋值是在 dispatchAttachedToWindow 的时候进行的,而dispatchAttachedToWindow 是在 ViewRootImpl 类的performTraversals 调用的,因此,可以得出结论在onAttachedToWindow之后post,必定是有效果的。
那如果mAttachInfo没有被赋值的情况下,会如何处理呢?
看下ViewRootImpl.getRunQueue()实现:
static RunQueue getRunQueue() {
RunQueue rq = sRunQueues.get();
if (rq != null) {
return rq;
}
rq = new RunQueue();
sRunQueues.set(rq);
return rq;
}
sRunQueues 是ThreadLocal的实例,ThreadLocal使用时比较特殊,数据与线程绑定,在线程中set的值需要在相应线程取出,其他线程取值时仍为set之前的数据,从代码看,是把runable放到对应线程的ThreadLocal中去了,注意这个地方有主线程与子线程之分。如果是在主线程调用post方法,则会在ThreadLocal中主线程的数据结构中设置runnable,如果是子线程,则会在ThreadLocal中对应的子线程数据结构中设置runnable。
设置后何时消费呢?通过getRunQueue().executeActions方法消费,而这个方法在老版本中的调用时机只有performTraversals(遍历View tree,并进行View的measure,layout,draw相关操作),看下executeActions方法实现:
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();
}
}
基本到这问题比较清晰了,详细描述一下:
1:View.post会分相中情况处理,如果View没有attachedToWindow是,会将Runnable对象缓存起来,如果View已经attachedToWindow,则会将Runnable对象post到主线程执行
2:如果View.post的runnable对象被缓存起来,而在主线程中执行了post方法, performTraversals方法在主线程执行时,getRunQueue取出来的缓存对象即是主线程的runnable对象,可以正常执行;
3:如果View.post的runnable对象被缓存起来,而在子线程中执行post方法,则会在sRunQueues对应的子线程的数据结构中设置runnable对象,而performTraversals方法会在主线程执行,取出来的是sRunQueues对应主线程的数据结构,根据ThreadLocal执行机制,这个时候主线程取出来的值是空的,因此不执行。
看下Android 7.0之后的版本(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;
}
7.0之后的sdk使用的是getRunQueue().post(action);getRunQueue()的方法是
/**
* Returns the queue of runnable for this view.
*
* @return the queue of runnables for this view
*/
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
这个mRunQueue是View类中一个私有变量。看下执行:
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;
}
调用时机,与Android 7.0之前的版本略有不同,除了performTraversal中会调用外,在View的dispatchAttachedToWindow中也会调用,但Android 7.0之后不管在主线程还是在子线程都可以成功执行view.post内部逻辑,并不是因为增加了调用时机,而是取消了ThreadLocal机制,使得不管在主线程还是子线程调用view.post方法,都会将runnable对象丢到主线程的任务队列中,更新UI或者获取view的信息。