ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

view.post不执行的坑点

2021-07-28 17:31:18  阅读:756  来源: 互联网

标签:info 坑点 listeners 线程 post null view


view.post没执行,runOnUiThread,Handler

目录

坑点

子线程执行view.post(Runnable) 部分 手机没有效果。

usernameEditText.post(new Runnable() {
                    @Override
                    public void run() {
                        usernameEditText.setText("text set by runnable!");
                    }
                });

处理

  1. 使用handler.post
  2. 使用runOnUiThread

原因

低版本android基于ThreadLocal实现线程与数据关联,遇到多线程使用时,一个线程存储了数据,另一个线程取数据取不到的数据的原因。
子线程调用view.post时候,会构造一个队列存储到对应的线程数据空间,并将runnable加到此队列。当view要显示时候,ui线程会从ui线程的数据空间取出队列,遍历执行队列中的runnable,但是由于ThreadLocal的缘故,ui线程取到的队列肯定不包含子线程存到队列的runnable,所以这个runnable是不被执行的。因为刚才是子线程存的runnable,子线程可以取到,而UI线程并没有存我们期望的runnable,所以取不到。ThreadLocal特点就是线程之间的数据相互隔离,各自使用各自的数据,多线程使用时保证数据的“安全”。
还没明白的话,这样讲一下:
A、B钱包都没钱了,A从银行取了1000 人民币,装入了自己的钱包,B去商店买1000的商品,此时B从自己钱包里面拿钱时,钱包是空的。所以B是买不了商品的。

7.0之前的系统存在这个问题,7.0之后已经被修复了。用的时候小心一点。

经历

给同事写了个程序,当时是一个子线程处理了数据之后调用view.post更新到界面上,自测是没问题的,结果同事那边告知不显示,当时也查看了源代码,同时也是反复验证没有问题,同事那里始终是有问题的,后来同事也没再提说。一段时间之后见了同事,问及此事,他说 只有他手机有问题,其他的手机没问题,所以就没再说这个事情了。让其掏出手机看了下,果真不显示,当即开始加日志调试,结果runnable代码块没有被执行,隐隐约约感觉到view.post不靠谱,直接在外层再加一个runOnUiThread之后,他的设备正常。 虽然问题是过去了,但一直没时间去弄清楚出现这个问题的原因,最近看到项目中有其他小伙伴也写了同样的代码,心里面有点慌。
同时也查了些资料,总结记录之。

复盘

View.post()方法在android7.0之前,在view没有attachToWindow的时候调用该方法可能失效,尤其异步线程,如在onCreate,onBindViewHolder时调用view.post方法,可能会不生效,在异步线程view.post方法不执行的情况居多。建议使用Handler post方法代替。
longlong2015 这里也对次问题进行说明

于是乎,下载了一份6.0版本的sdk源码,以及9.0的源码进行对比,对比情况和引用文章差不多,也进一步对引用文章进行验证。

6.0版本

  1. View的post函数
 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有值,则是用attachInfo中的handler去post这个runnable,如果attachInfo没有值,则是ViewRootImpl.getRunQueue() 去执行post这个runnable。而attachInfo则是分别dispatchAttachedToWindow (首行)赋值的:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        //System.out.println("Attached! " + this);
        mAttachInfo = info;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        mWindowAttachCount++;
        // We will need to evaluate the drawable state at least once.
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        if (mFloatingTreeObserver != null) {
            info.mTreeObserver.merge(mFloatingTreeObserver);
            mFloatingTreeObserver = null;
        }
        if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
            mAttachInfo.mScrollContainers.add(this);
            mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewAttachedToWindow(this);
            }
        }

        int vis = info.mWindowVisibility;
        if (vis != GONE) {
            onWindowVisibilityChanged(vis);
        }

        // Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
        // As all views in the subtree will already receive dispatchAttachedToWindow
        // traversing the subtree again here is not desired.
        onVisibilityChanged(this, visibility);

        if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
            // If nobody has evaluated the drawable state yet, then do it now.
            refreshDrawableState();
        }
        needGlobalAttributesUpdate(false);
    }

dispatchDetachedFromWindow(倒数第三行)中赋空

void dispatchDetachedFromWindow() {
        AttachInfo info = mAttachInfo;
        if (info != null) {
            int vis = info.mWindowVisibility;
            if (vis != GONE) {
                onWindowVisibilityChanged(GONE);
            }
        }

        onDetachedFromWindow();
        onDetachedFromWindowInternal();

        InputMethodManager imm = InputMethodManager.peekInstance();
        if (imm != null) {
            imm.onViewDetachedFromWindow(this);
        }

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewDetachedFromWindow(this);
            }
        }

        if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) {
            mAttachInfo.mScrollContainers.remove(this);
            mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;
        }

        mAttachInfo = null;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchDetachedFromWindow();
        }
    }
  1. ViewRootImpl.getRunQueue()
    ViewRootImpl 的静态成员 sRunQueues 和静态函数getRunQueue
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;
    }

ui线程执行“存到队列中的任务"

 // Execute enqueued actions on every traversal in case a detached view enqueued an action
        getRunQueue().executeActions(mAttachInfo.mHandler);

根源是sRunQueues.get(),其实也是ThreadLocal的特性。当子线程调用的时候,这里返回的rq 是空的,接着创建一个rt后存入。之后UI线程调用,这里返回的不是子线程创建的rq。

  1. ViewRootImpl.RunQueue.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. ThreadLocal.get()
    再进一步看一下这个ThreadLocal的get实现(get的样子往往容易被忽视)
public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

好的,到这里已经看到取当前的线程做了一系列的事情,因此不同线程返回的自然就不一样。

10.0版本

  1. View.post
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;
    }

可以看到这里以不是用ViewRootImpl.getRunQueue(),而是view内部的函数getRunQueue().

  1. View.getRunQueue()
private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

好家伙,现在的队列是属于view的了,不再是归属于线程,变成了共享变量。
因此子线程向队列里面添加一个runnable之后,ui线程做来取队列就能取到。执行就是我们期望的结果了。

  1. View.dispatchAttachedToWindow
 void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        mWindowAttachCount++;
        // We will need to evaluate the drawable state at least once.
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        if (mFloatingTreeObserver != null) {
            info.mTreeObserver.merge(mFloatingTreeObserver);
            mFloatingTreeObserver = null;
        }

        registerPendingFrameMetricsObservers();

        if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
            mAttachInfo.mScrollContainers.add(this);
            mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
        }
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewAttachedToWindow(this);
            }
        }

        int vis = info.mWindowVisibility;
        if (vis != GONE) {
            onWindowVisibilityChanged(vis);
            if (isShown()) {
                // Calling onVisibilityAggregated directly here since the subtree will also
                // receive dispatchAttachedToWindow and this same call
                onVisibilityAggregated(vis == VISIBLE);
            }
        }

        // Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
        // As all views in the subtree will already receive dispatchAttachedToWindow
        // traversing the subtree again here is not desired.
        onVisibilityChanged(this, visibility);

        if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
            // If nobody has evaluated the drawable state yet, then do it now.
            refreshDrawableState();
        }
        needGlobalAttributesUpdate(false);

        notifyEnterOrExitForAutoFillIfNeeded(true);
        notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
    }

其中下面这段就是UI线程来执行存入需要处理的任务:

 if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }

总结

子线程在onAttachedToWindow之后调用view.post,是有效的。其次是与系统版本有一定关系,出现问题的场景就是子线程处理的完成数据之后调用view.post时,onAttachedToWindow还没有回调,一般是activity onCreate函数中初始化完成view之前这段时间。

标签:info,坑点,listeners,线程,post,null,view
来源: https://blog.csdn.net/lanlangaogao/article/details/119135101

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有