View.postDelayed()/post() 原理(1)

我们知道,Handler有postDelayed()/post()等API,在UI线程中,通过默认构造方法new Handler(),会创建一个与当前线程的Looper绑定的Handler对象,UI线程的消息循环是由框架层打开(Looper.loop()),APP开发者无需关注。维护一个挂在UI线程的Handler成员变量用以发消息/处理消息,是惯常的代码风格。
当然,还有另外一类API:View.postDelayed()/post()。Android官方文档介绍这类API也是向UI线程发消息,Runnable执行在UI线程中。与Handler.postDelayed()/post()一样,View.postDelayed()/post()的API Level是1,是非常古老的API。

1. View.postDelayed()/post()在 框架层的实现原理

本文源代码分析基于Android 7.1。从View的源代码说起:

    /**
     * <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) {
            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;
    }

很清楚的两路逻辑:
(1)如果View的成员变量mAttachInfo不为null,直接post到mAttachInfo.mHandler。
(2)如果View的成员变量mAttachInfo为null,post到自身的一个runnable队列中。

1.1 post到mAttachInfo.mHandler

View的成员变量mAttachInfo对应的类是View的一个内部final类:


    /**
     * A set of information given to a view when it is attached to its parent
     * window.
     */
    final static class AttachInfo { ... }

在Android的框架层设计中,View需要附着在某个Window上,AttachInfo这个类显然是用于处理View的附着相关的信息。从它的构造方法可以看到关键的一些信息成员变量,其中也看到了mHandler

        /**
         * Creates a new set of attachment information with the specified
         * events handler and thread.
         *
         * @param handler the events handler the view must use
         */
        AttachInfo(IWindowSession session, IWindow window, Display display,
                ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
            mSession = session;
            mWindow = window;
            mWindowToken = window.asBinder();
            mDisplay = display;
            mViewRootImpl = viewRootImpl;
            mHandler = handler;
            mRootCallbacks = effectPlayer;
        }

View的成员变量mAttachInfo在什么地方被赋值?很清晰,在View.dispatchAttachedToWindow()/dispatchDetachedFromWindow():

    /**
     * @param info the {@link android.view.View.AttachInfo} to associated with
     *        this view
     */
    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);
    }

    void dispatchDetachedFromWindow() {
        AttachInfo info = mAttachInfo;
        if (info != null) {
            int vis = info.mWindowVisibility;
            if (vis != GONE) {
                onWindowVisibilityChanged(GONE);
                if (isShown()) {
                    // Invoking onVisibilityAggregated directly here since the subtree
                    // will also receive detached from window
                    onVisibilityAggregated(false);
                }
            }
        }

        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();
        }
    }

View.dispatchAttachedToWindow()/dispatchDetachedFromWindow()为包内可调用,即为框架层内部调用。二者分别调用了View.onAttachedToWindow()/onDetachedFromWindow()。View.onAttachedToWindow()/onDetachedFromWindow()为protected,可由View的各子类根据自己的业务逻辑重写。

    /**
     * This is called when the view is attached to a window.  At this point it
     * has a Surface and will start drawing.  Note that this function is
     * guaranteed to be called before {@link #onDraw(android.graphics.Canvas)},
     * however it may be called any time before the first onDraw -- including
     * before or after {@link #onMeasure(int, int)}.
     *
     * @see #onDetachedFromWindow()
     */
    @CallSuper
    protected void onAttachedToWindow() {
        if ((mPrivateFlags & PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
            mParent.requestTransparentRegion(this);
        }

        mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;

        jumpDrawablesToCurrentState();

        resetSubtreeAccessibilityStateChanged();

        // rebuild, since Outline not maintained while View is detached
        rebuildOutline();

        if (isFocused()) {
            InputMethodManager imm = InputMethodManager.peekInstance();
            if (imm != null) {
                imm.focusIn(this);
            }
        }
    }
    /**
     * This is called when the view is detached from a window.  At this point it
     * no longer has a surface for drawing.
     *
     * @see #onAttachedToWindow()
     */
    @CallSuper
    protected void onDetachedFromWindow() {
    }

众所周知,安卓的View是可以定义到XML文件中的。从XML文件中的一段文本,到手机屏幕上可以看到的视图,从View的角度,经历四个过程:inflate(从XML文件加载到内存中的一个View对象)、measure(计算大小)、layout(确定位置)、draw(绘制)。从以上两个方法的注释说明看到,与draw有关,确定draw对应的Surface。
下面看dispatchAttachedToWindow()的调用逻辑。有两处使用:
第一、ViewGroup.java
在添加子View的内部方法addViewInner(),实际上是ViewGroup把自己的mAttachInfo对象给了子View,共享同一个对象:


    private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        // 省略无关代码

        AttachInfo ai = mAttachInfo;
        if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {
            boolean lastKeepOn = ai.mKeepScreenOn;
            ai.mKeepScreenOn = false;
            child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
            if (ai.mKeepScreenOn) {
                needGlobalAttributesUpdate(true);
            }
            ai.mKeepScreenOn = lastKeepOn;
        }

        // 省略无关代码
    }

此外,在ViewGroup自己的dispatchAttachedToWindow()也会手动调用子View的dispatchAttachedToWindow(),仍然是共享同一个AttachInfo对象:

    @Override
    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()));
        }
    }

第二、ViewRootImpl.java
ViewRootImpl是Android框架内部的一个hide类,是View层次的根节点,同时,起到WindowManager与View之间的桥梁作用。参见类定义:

/**
 * The top of a view hierarchy, implementing the needed protocol between View
 * and the WindowManager.  This is for the most part an internal implementation
 * detail of {@link WindowManagerGlobal}.
 *
 * {@hide}
 */
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks

在ViewRootImpl的构造方法中,创建了mAttachInfo对象,其中看到了mHandler作为参数给了mAttachInfo:

public ViewRootImpl(Context context, Display display) {
        // ignore...
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
        // ignore...
    }

mHandler的来源:

final ViewRootHandler mHandler = new ViewRootHandler();

ViewRootHandler是ViewRootImpl的一个内部类,用于处理ViewRootImpl内部的消息相关业务逻辑:

final class ViewRootHandler extends Handler

从ViewRootHandler的定义以及mHandler的创建可以看到这是一个挂在UI线程的Handler。
介绍完了ViewRootImpl上述背景知识,回头看它对于dispatchAttachedToWindow()的使用。在私有方法ViewRootImpl.performTraversals()的第一次调用中,有对于成员变量mView调用dispatchAttachedToWindow(),并将自己的mAttachInfo作为参数下发:

host.dispatchAttachedToWindow(mAttachInfo, 0);

即通过View层级的根节点逐层下发共享了这个mAttachInfo对象,同时也就共享了mHandler对象。
所以,总结一下,第一路逻辑post到mAttachInfo.mHandler,也就是post到一个挂在UI线程的Handler中。
View.postDelayed()/post() 原理(1)
View.postDelayed()/post() 原理(2)

发布了111 篇原创文章 · 获赞 48 · 访问量 21万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 深蓝海洋 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览