View.post()的工作原理

前言:

        如果你对Handler的工作原理比较了解的话,那么对于post() 就比较好了解了,因为post() 的内部用的就是Handler,但是它里面是如何实现的,这就是本节的重点。如果你对这个方法有个清晰的了解的话,那么在开发的时候,有时候就不必自己去维护一个handler了,当然这里使用不当也是会有内存泄露的,就和自己维护的Handler一样,这也是我们在使用的时候需要注意的。对于Handler处理消息回调的接口总共有三个并且是有优先级的,post()使用的就是Message中callback,如果你对Handler还不了解,可以看一看Handler原理

源码分析:

        先来看一下View.post()方法:

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        //使用attachInfo中的Handler的post()方法,handler用过的人应该看下就明白了,
        //不明白的应该就是attachInfo这个到底是什么,后面会重点分析
        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.
    //这里做的就是当attchInfo为null时,将action暂时保存在一个数组中,当attachInfo不为null时,
    // 再交由Handler处理
    getRunQueue().post(action);
    return true;
}

看到这里,你可能会有两个点不太明白:

        一个是attachInfo是如何赋值的;

        二是getRunqueue.post()是如何暂时保存任务,注意,这里用的是暂时。

那么我们先来看看getRunqueue()给我们返回的什么:

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

那我们再去看看HandlerActionQueue这个类是如何实现:

public class HandlerActionQueue {
    private HandlerAction[] mActions;
    private int mCount;

    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            //GrowingArrayUtils是一个工具类,这里的作用只是对数组进行判断,
            // 如果数组到达容量后,会对数值进行扩容后再将任务放进去
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

    public void removeCallbacks(Runnable action) {
        synchronized (this) {
            final int count = mCount;
            int j = 0;

            final HandlerAction[] actions = mActions;
            for (int i = 0; i < count; i++) {
                if (actions[i].matches(action)) {
                    // Remove this action by overwriting it within
                    // this loop or nulling it out later.
                    continue;
                }

                if (j != i) {
                    // At least one previous entry was removed, so
                    // this one needs to move to the "new" list.
                    actions[j] = actions[i];
                }

                j++;
            }

            // The "new" list only has j entries.
            mCount = j;

            // Null out any remaining entries.
            for (; j < count; j++) {
                actions[j] = null;
            }
        }
    }

    //这里就是将我们保存的任务循环遍历交给传进来的Handler进行处理,现在我们可以大胆的猜测一下,
    // attachInfo不为null的时候,应该就是将Handler从这里传进来的,这里先揭个密,还真是。下面马上分析到
    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;
        }
    }

    public int size() {
        return mCount;
    }

    public Runnable getRunnable(int index) {
        if (index >= mCount) {
            throw new IndexOutOfBoundsException();
        }
        return mActions[index].action;
    }

    public long getDelay(int index) {
        if (index >= mCount) {
            throw new IndexOutOfBoundsException();
        }
        return mActions[index].delay;
    }

    //对传进来的任务和时间进行封装的一个类
    private static class HandlerAction {
        final Runnable action;
        final long delay;

        public HandlerAction(Runnable action, long delay) {
            this.action = action;
            this.delay = delay;
        }

        public boolean matches(Runnable otherAction) {
            return otherAction == null && action == null
                    || action != null && action.equals(otherAction);
        }
    }
}

这个类还是比较简单的,很容易就看懂,它主要是将传进来的任务进行封装后在保存在数组中。当需要将这些任务交由handler处理的时候,我们看到,这里提供了一个方法executeActions(Handler h),就是将任务循环遍历交由Handler处理,HandlerActionQueue看完了,接下来就该去找找attachInfo了。

        在View中我们搜素一下给mAttachInfo赋值的地方,很快就发现了是在dispatchAttachToWindow()中对mAttachInfo进行赋值的:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mAttachInfo = info;
    // Transfer all pending runnables.
    if (mRunQueue != null) {
        mRunQueue.executeActions(info.mHandler);
        mRunQueue = null;
    }
}

这是dispatchAttachToWindow()方法中的一部分,可以看到暂时保存起来的任务通过executeActions()将任务交给了Handler处理,到这,我们唯一还要知道就是dispatchAttachToWindow()这个是在什么时候调用。但当我们再次进行搜索的时候发现找不到了,这下该怎么继续呢?我们知道,view的分发肯定是从ViewGroup中来的,那我们就该去ViewGroup中找一找,还真有:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
    super.dispatchAttachedToWindow(info, visibility);
    mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;

    final int count = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < count; i++) {
        final View child = children[i];
        child.dispatchAttachedToWindow(info,
                combineVisibility(visibility, child.getVisibility()));
    }
    final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
    for (int i = 0; i < transientCount; ++i) {
        View view = mTransientViews.get(i);
        view.dispatchAttachedToWindow(info,
                combineVisibility(visibility, view.getVisibility()));
    }
}
这里就是如果ViewGroup有子view,循环调用子view的dispatchAttachToWindow()方法,还是挺简单的。

        到ViewGroup后,我们就应该想到这个应该是从View树的最顶层传下来的,而对于View树最终会在resume的时候传递到ViewRootImpl中去,所有,现在就该去ViewRootImpl中找找了,我们可以在performTranversals()中看到:

host.dispatchAttachedToWindow(mAttachInfo, 0);

终于找到了,原来就是从这里对view的mAttachInfo进行赋值的,并且一个view树共用的是同一个AttachInfo,在ViewRootImpl的构造方法中:

mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);

到此,整个流程就全清楚了。

总结:

        1、首先得明确performTranversals()这个方法是在Activity的resume这个时期调用的,也就是mAttachInfo也是在这个时候进行赋值的;

        2、有时候我们可能会在Activity的onCreate()利用view.post()去获取view的宽高,这个是怎么做到的呢?从上面的整个流程下来,我们知道,一开始mAttachInfo是null,那么,post()就会将传进来的的任务暂时保存起来,等到resume的时候通过dispatchAttachToWindow()对mAttachInfo进行赋值,并将缓存起来的任务全部交由mAttachInfo中的Handler处理,这里有一点需要清楚,Android是基于消息驱动来处的,Activity的生命周期也是作为Handler的消息进行分发的,所以这些交给Handler处理的任务要等到目前的消息回调事件处理完才会触发,也就是说,这些任务执行的时间实在view的的测量、布局和绘制的后面,所以在post()中可以拿到view的宽高,并且也一定是在UI线程中执行的。

        3、这里在提一次,使用的时候注意可能存在的内存泄露。


        有哪里看不懂的欢迎留言!!!


        

猜你喜欢

转载自blog.csdn.net/tangedegushi/article/details/80191888
今日推荐