View.post()必知必会

689 阅读4分钟
原文链接: mp.weixin.qq.com

一、post方法分析

看看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线程。这解释,和Handler的作用差不多,然而事实并非如此

源码如下:

/**     * <p>Causes the Runnable to be added to the message queue.     * The runnable will be run on the user interface thread.</p>     *     * @param action The Runnable that will be executed.     *     * @return Returns true if the Runnable was successfully placed in to the     *         message queue.  Returns false on failure, usually because the     *         looper processing the message queue is exiting.     *     * @see #postDelayed     * @see #removeCallbacks     */    public boolean post(Runnable action) {        final AttachInfo attachInfo = mAttachInfo;        if (attachInfo != null) {         // 如果当前View加入到了window中,直接调用UI线程的Handler发送消息            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);//View未加入到window,放入ViewRootImpl的RunQueue中        return true;    }

      分两种情况,当View已经attach到window,直接调用UI线程的Handler发送runnable。如果View还未attach到window,将runnable放入ViewRootImpl的RunQueue中。

      那么post到RunQueue里的runnable什么时候执行呢,又是为何当View还没attach到window的时候,需要post到RunQueue中。

二、View#post与Handler#post的区别

       其实,当View已经attach到了window,两者是没有区别的,都是调用UI线程的Handler发送runnable到MessageQueue,最后都是由handler进行消息的分发处理

      但是如果View尚未attach到window的话,runnable被放到了ViewRootImpl#RunQueue中,最终也会被处理,但不是通过MessageQueue

ViewRootImpl#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 */

        大概意思是当视图树尚未attach到window的时候,整个视图树是没有Handler的(其实自己可以new,这里指的handler是AttachInfo里的),这时候用RunQueue来实现延迟执行runnable任务,并且runnable最终不会被加入到MessageQueue里,也不会被Looper执行,而是等到ViewRootImpl的下一个performTraversals时候,把RunQueue里的所有runnable都拿出来并执行,接着清空RunQueue。

三、既然用到handler会不会引起内存泄漏呢?

      经查看源码验证发现在4.4-5.2的机器才会泄漏,是因为4.4-5.2的系统,View中notifyViewAccessibilityStateChangedIfNeeded方法并没有判断View是否attach到了window,直到google发布的android_6.0系统才修复该问题,该问题可以说是google的问题,因为google官方在Support_v4包中就提供了异步线程加载布局文件的框架,具体参阅:android.support.v4.view.AsyncLayoutInflater     。 

    6以后的版本根据使用情况可以手动释放消息队列,参考方法如下:

/** * 此方法需要在创建View的子线程中调用 */private void resolvePostLeak() {    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {        // 主线程不需要处理        return;    }    try {        Class<?> viewRootImpl = Class.forName("android.view.ViewRootImpl");        Field sRunQueuesField = viewRootImpl.getDeclaredField("sRunQueues");        if (sRunQueuesField != null) {            sRunQueuesField.setAccessible(true);            ThreadLocal threadLocal = (ThreadLocal) sRunQueuesField.get(viewRootImpl);            if (threadLocal != null) {                threadLocal.set(null);            }        }    } catch (Exception e) {        e.printStackTrace();    }}

    

四、总结

1、View.post(Runnable) 内部会自动分两种情况处理,当 View 还没 attachedToWindow 时,会先将这些 Runnable 操作缓存下来;否则就直接通过 mAttachInfo.mHandler 将这些 Runnable 操作 post 到主线程的 MessageQueue 中等待执行。

2、如果 View.post(Runnable) 的 Runnable 操作被缓存下来了,那么这些操作将会在 dispatchAttachedToWindow() 被回调时,通过 mAttachInfo.mHandler.post() 发送到主线程的 MessageQueue 中等待执行。

3、mAttachInfo 是 ViewRootImpl 的成员变量,在构造函数中初始化,Activity View 树里所有的子 View 中的 mAttachInfo 都是 ViewRootImpl.mAttachInfo 的引用。

4、mAttachInfo.mHandler 也是 ViewRootImpl 中的成员变量,在声明时就初始化了,所以这个 mHandler 绑定的是主线程的 Looper,所以 View.post() 的操作都会发送到主线程中执行,那么也就支持 UI 操作了。

5、dispatchAttachedToWindow() 被调用的时机是在 ViewRootImol 的 performTraversals() 中,该方法会进行 View 树的测量、布局、绘制三大流程的操作。

6、Handler 消息机制通常情况下是一个 Message 执行完后才去取下一个 Message 来执行(异步 Message 还没接触),所以 View.post(Runnable) 中的 Runnable 操作肯定会在 performMeaure() 之后才执行,所以此时可以获取到 View 的宽高或者其他的计算逻辑。

7、为了防止内存泄漏在高版上使用还是得注意。

                        喜欢 就关注吧,欢迎投稿!