Handler机制(View.post(Runnable action)原理)

Handler机制(View.post(Runnable action)原理)

场景

实际开发的过程中,我们一定会遇到.当在Activity 的onCreate() 中,通过 view.getTop()/ View.getWidth() 等方法来获取,控件的长宽等距离属性的时候。往往,事与愿违,获取的结果为0:。

原因: 当我们在获取的时候,View 并没有完成,测量,布局等操作。因此,通过这些方法获取到的参数为0。

这里我们只讨论一下,为什么在 View. post() 方法中可以正确获取呢?

//View.java

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
  //用来存储预处理的Runnable ,直到,View被attach 到window 且拥有handler
 private HandlerActionQueue mRunQueue;

 //发送一个runnable
  public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        //1.attachInfo 中的handler 发送Runnable 
        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.
        //2.将Runnable 添加到 HandlerActionQueue 中
        getRunQueue().post(action);
        return true;
    }

  /**
     * Returns the queue of runnable for this view.
     *
     * @return the queue of runnables for this view
     */
    private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

}
attachInfo.mHandler.post(action)

AttachInfo :视图附加到其父窗口时所得到的一组信息。里面也包括了UI 线程的Handler。

//View.java
 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);
    }

当View 添加到父容器的时候,就会调用 dispatchAttachedToWindow(AttachInfo info, int visibility)方法,接下来会获取当前view 的 mRunQueue ,交给 handler 处理

HandlerActionQueue
//HandlerActionQueue.java
public class HandlerActionQueue { 
 // 保存HandlerAction 的数组
    private HandlerAction[] mActions;
    //数组长度
    private int mCount;

  //HandlerAction 对象持有 runnable 和 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);
        }
    }


    //预处理Runnable 加入mActions 数组中
    public void post(Runnable action) {
        postDelayed(action, 0);
    }
    //预处理Runnable 加入mActions 数组中
    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            //将action追加到mActions尾部(当mActions已经满的话,则自动扩充数组),有兴趣可以看下 GrowingArrayUtils 这个类
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }
    //移除runnable
    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;
                }
                //将要移除的runnable  后面的runnable往前移动,导致,最后要移除的runnable 排到了数组末尾
                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.
            //将要移除的runnable 置空
            for (; j < count; j++) {
                actions[j] = null;
            }
        }
    }
   //将mActions 中保存的runnable 交给 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;
    }
  
}

当View 发送Runnable的时候:

  1. 如果 view ,还未attach 到父容器时,没有attachInfo ,则会先将runnable 加入到 HandlerActionQueue 中,等到attach 成功后,则会调用主线程的Hander去处理 这些 runnable
  2. 如果,view已经attach到父容器,则直接用 attachInfo 中的handler ,去发送Runnable

由此可见,view.post( ) 方法是一个异步的过程。View 在添加到 父容器的时候,已经完成了自己的测量,布局,绘制等操作,因此,在post的runnable 中 ,可以正确获得,它的高,宽,坐标等数值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值