先分析一下View.post 和 Handler.post 的区别
在分析之前 先写一个例子
public class MyView extends LinearLayout {
String TAG = "MyView";
Handler handler = new Handler();
public MyView(Context context) {
super(context);
init();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
this.setWillNotDraw(false);
Log.e(TAG, "oncreat");
handler.post(new Runnable() {
@Override
public void run() {
Log.e(TAG, "handler.post run ");
}
});
Runnable runnable = new Runnable() {
@Override
public void run() {
Log.e(TAG, "view.post run ");
}
};
this.post(runnable);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.e(TAG, "onMeasure ");
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
Log.e(TAG, "onLayout ");
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.e(TAG, "onDraw ");
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
Log.e(TAG, "onAttachedToWindow ");
}
}
分别打印View 的若干个声明周期,并使用handler.post 和 view.post 打印两个log
先不说答案,大家可以先想一下
OK 我们来看view.post源码
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;
}
优先调用AttachInfo的Handler post方法
那么mAttachInfo 是在何处赋值的呢
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
。。。。。。
}
是在dispatchAttachedToWindow的时候
而View的dispatchAttachedToWindow其实是在performTraversals里面调用的
那也就是说 如果我们在View初始化的时候 调用view.post方法,这个时候View还没有开始layout ,这个时候mAttachInfo一定是null
于是post方法就走了getRunQueue().post(action);这里
继续看源码
static HandlerActionQueue getRunQueue() {
HandlerActionQueue rq = sRunQueues.get();
if (rq != null) {
return rq;
}
rq = new HandlerActionQueue();
sRunQueues.set(rq);
return rq;
}
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];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
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;
}
}
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);
}
}
}
可以看到 所有的任务被预加载到了 HandlerAction[] mActions; 中保存了起来 外部调用executeActions来执行
再去找执行调用的地方
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
。。。。。。。。。。。。。。
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
。。。。。。。。。。。。。
}
又找到了dispatchAttachedToWindow的地方
现在我们知道了 当dispatchAttachedToWindow被调用的时候 view.post方法中被加入的任务才真正开始执行
而dispatchAttachedToWindow虽然在performMeasure和performLayout之前调用,但是被加入到消息队列中的消息一定会比主线
程中的measure和layout的同步代码调用要晚,所以篇头的那个问题 我们有了答案
log 输出顺序为
符合预期结果
所以我们得出结论
View.post方法与Handler.post方法区别在于
当View还没有初始化完成时,该Runnable会被缓存起来,待View完成layout之后再执行
我们可以用这个机制来准确判断View开始绘制第一帧的时机
最后再来说一下IdleHandler
先看一张从百度上搜过来的图,因为找不到原作者了所以先不贴出处
这是一张讲解Handler的图,从图中可以很容易理解Handler 消息队列的相关逻辑
我们现在把这个图稍微改一下
解释一下
我们把IdleHandler 理解成一个IdleMessage
所有的IdleMessage都被存储在MessageQueue里面
当MessageQueue里面所有的message都被执行完,队列已经清空的时候(这被认为是一种idle状态)
IdleMessage就会被取出执行(注意IdleMeaage不是插入在message队列中间,而是独立存在的)
所以IdleHandler的执行时间是不确定的,如果MessageQueue里面一直有message
那么IdleHandler可能一直都执行不了(当然这种情况不太可能)
所以使用IdleHandler来执行一些启动时的耗时操作是非常NICE的,可以有效的提升启动速度