View.post()必知必会

一、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、为了防止内存泄漏在高版上使用还是得注意。

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

本网站文章均为原创内容,并可随意转载,但请标明本文链接
如有任何疑问可在文章底部留言。为了防止恶意评论,本博客现已开启留言审核功能。但是博主会在后台第一时间看到您的留言,并会在第一时间对您的留言进行回复!欢迎交流!
本文链接: https://leetcode.jp/view-post()必知必会/

此条目发表在Android分类目录。将固定链接加入收藏夹。

发表评论

您的电子邮箱地址不会被公开。