Android踩坑经验-View post方法不执行

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的信息。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值