我们知道,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)