Handler.post vs View.post
code segment 1:
Handler().post({
// do something
})
code segment 2:
someview.post({
// do something
})
此二者有什么区别呢?
Handler.post
通过Handler,post message/runnable都是构造一个msg,插入到对应的looper和messageQueue中,等到msg对象被处理时,从队列中取出并执行。
View.post
常见场景:
- 更新UI操作
- 获取View实际宽高
view.post()内部也是调用Handler, 与Handler post不同之处在于msg调用时机,下面会详细展开View.post源码分析。
比较
- 它们都可以去UI线程中执行某操作
- 它们内部都是通过Handler+msg实现
- BUT,Handler对取msg没有额外的顺序管理,view会确保内部方法在view绘制结束之后调用,所以获取view高宽等用view.post比较合适,可作为一个面试哦
View.post 剖析
在Activity中,View绘制流程开始是在ActivityThread的handleResumeActivity方法中,此方法中首先完成Activity的onResume回调,然后开始View绘制。
也就是说绘制在onResume之后,在onCreate中使用Handler().post是拿不到宽高的,因为他们在同一个线程队列中 - 不考虑ViewTreeObserver或delay。但是onCreate中使用view.post能拿到宽高,why?
- View.post()对任务的运行时机做了调整。
View.post()
翻开 View 源码,找到 View 的 post 方法如下:
public boolean post(Runnable action) {
// 首先判断AttachInfo是否为null
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
// 如果不为null,直接调用其内部Handler的post
return attachInfo.mHandler.post(action);
}
// 否则加入当前View的等待队列
getRunQueue().post(action);
return true;
}
注意 AttachInfo 是 View 的静态内部类,每个 View 都会持有一个 AttachInfo,它默认为 null;
需要先来看下 getRunQueue().post():
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
// getRunQueue() 返回的是 HandlerActionQueue,也就是调用了 HandlerActionQueue 的 post 方法:
public void post(Runnable action) {
// 调用到postDelayed方法,这有点类似于Handler发送消息
postDelayed(action, 0);
}
// 实际调用postDelayed
public void postDelayed(Runnable action, long delayMillis) {
// HandlerAction表示要执行的任务
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
// 创建一个保存HandlerAction的数组
mActions = new HandlerAction[4];
}
// 表示要执行的任务HandlerAction 保存在 mActions 数组中
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
// mActions数组下标位置累加1
mCount++;
}
}
// HandlerAction 表示一个待执行的任务,内部持有要执行的 Runnable 和延迟时间;类声明如下:
private static class HandlerAction {
// post的任务
final Runnable action;
// 延迟时间
final long delay;
public HandlerAction(Runnable action, long delay) {
this.action = action;
this.delay = delay;
}
// 比较是否是同一个任务
// 用于匹配某个 Runnable 和对应的HandlerAction
public boolean matches(Runnable otherAction) {
return otherAction == null && action == null
|| action != null && action.equals(otherAction);
}
}
注意 postDelayed() 创建一个默认长度为 4 的 HandlerAction 数组,用于保存 post() 添加的任务;跟踪到这,大家是否有这样的疑惑:
View.post() 添加的任务没有被执行?
实际上,此时我们要回过头来,重新看下 AttachInfo 的创建过程,先看下它的构造方法:
AttachInfo(IWindowSession session, IWindow window, Display display,
ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer,
Context context) {
mSession = session;
mWindow = window;
mWindowToken = window.asBinder();
mDisplay = display;
// 持有当前ViewRootImpl
mViewRootImpl = viewRootImpl;
// 当前渲染线程Handler
mHandler = handler;
mRootCallbacks = effectPlayer;
// 为其创建一个ViewTreeObserver
mTreeObserver = new ViewTreeObserver(context);
}
注意 AttachInfo 中持有当前线程的 Handler。
翻阅 View 源码,发现仅有两处对 mAttachInfor 赋值操作,一处是为其赋值,另一处是将其置为 null。
mAttachInfo 赋值过程:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
// 给当前View赋值AttachInfo,此时所有的View共用同一个AttachInfo(同一个ViewRootImpl内)
mAttachInfo = info;
// View浮层,是在Android 4.3添加的
if (mOverlay != null) {
// 任何一个View都有一个ViewOverlay
// ViewGroup的是ViewGroupOverlay
// 它区别于直接在类似RelativeLaout/FrameLayout添加View,通过ViewOverlay添加的元素没有任何事件
// 此时主要分发给这些View浮层
mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
}
mWindowAttachCount++;
// ... 省略
if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER) != 0) {
mAttachInfo.mScrollContainers.add(this);
mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
}
// mRunQueue,就是在前面的 getRunQueue().post()
// 实际类型是 HandlerActionQueue,内部保存了当前View.post的任务
if (mRunQueue != null) {
// 执行使用View.post的任务
// 注意这里是post到渲染线程的Handler中
mRunQueue.executeActions(info.mHandler);
// 保存延迟任务的队列被置为null,因为此时所有的View共用AttachInfo
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
// 回调View的onAttachedToWindow方法
// 在Activity的onResume方法中调用,但是在View绘制流程之前
onAttachedToWindow();
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
for (OnAttachStateChangeListener listener : listeners) {
// 通知所有监听View已经onAttachToWindow的客户端,即view.addOnAttachStateChangeListener();
// 但此时View还没有开始绘制,不能正确获取测量大小或View实际大小
listener.onViewAttachedToWindow(this);
}
}
// ... 省略
// 回调View的onVisibilityChanged
// 注意这时候View绘制流程还未真正开始
onVisibilityChanged(this, visibility);
// ... 省略
}
方法最开始为当前 View 赋值 AttachInfo。注意 mRunQueue 就是保存了 View.post() 任务的 HandlerActionQueue;此时调用它的 executeActions 方法如下:
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中,等待执行
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
//此时不在需要,后续的post,将被添加到AttachInfo中
mActions = null;
mCount = 0;
}
}
遍历所有已保存的任务,发送到 Handler 中排队执行;将保存任务的 mActions 置为 null,因为后续 View.post() 直接添加到 AttachInfo 内部的 Handler 。所以不得不去跟踪 dispatchAttachedToWindow() 的调用时机。