一分预、View.post()
- post(Runnable action)
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}
從上面代碼可以知道,當(dāng)調(diào)用post()方法時(shí)放闺,首先會(huì)判斷mAttachInfo是否為空胀糜,如果不為空颅拦,則調(diào)用Handler處理消息,否則教藻,將將消息放入到RunQueue消息隊(duì)列距帅。
- HandlerActionQueue
View.java
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
HandlerActionQueue.java
private HandlerAction[] mActions;
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++;
}
}
從上面代碼知道, getRunQueue()創(chuàng)建一個(gè)HandlerActionQueue括堤,并將我們使用的runable放入到mActions內(nèi)碌秸。
- 總結(jié):
其實(shí)我們調(diào)用的post方法執(zhí)行流程有兩種,一種是直接使用Handler去執(zhí)行悄窃,第二是將我們的runabale存儲(chǔ)到隊(duì)列中讥电。
二、runable隊(duì)列被執(zhí)行
在HandlerActionQueue中我們知道轧抗,有一個(gè)executeActions(handler)方法恩敌。方法源碼如下:
HandlerActionQueue.java
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;
}
}
從上面源碼我們知道,executeActions內(nèi)部通過for循環(huán)的方式横媚,將消息隊(duì)列的Runable取出纠炮,使用Handler發(fā)送到主線程。
三灯蝴、HandlerActionQueue的executeActions方法合適被執(zhí)行恢口。
在View的源碼中 Ctrl+F 我們搜索mRunQueue.executeActions。中我們找到只有在View的dispatchAttachedToWindow方法中執(zhí)行穷躁。dispatchAttachedToWindow的核心代碼如下:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
...
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
...
}
所以當(dāng)View執(zhí)行dispatchAttachedToWindow方法時(shí)耕肩,才將我們最開始發(fā)送的Runable對(duì)象發(fā)送到主線程處理。
但是,當(dāng)我們?cè)赩iew內(nèi)部搜索何時(shí)調(diào)用dispatchAttachedToWindow時(shí)猿诸,并沒有找到婚被。但是View的繪制、測量两芳、布局摔寨,都由有父布局開始,所以我們?cè)诟覆季值闹胁檎艺{(diào)用的地方怖辆。
四、ViewGroup的dispatchAttachedToWindow
在ViewGroup的內(nèi)部我們找到兩個(gè)地方調(diào)用子View的dispatchAttachedToWindow删顶,一是在addViewInner內(nèi)調(diào)用竖螃,addViewInner使用addView調(diào)用的,這就是我們?cè)谑謩?dòng)創(chuàng)建View時(shí)逗余,
沒有調(diào)用父View的addView()方法將View添加進(jìn)父View時(shí)特咆,我們添加的View的post方法不執(zhí)行的原因。二是在dispatchAttachedToWindow方法內(nèi)录粱。
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
...
super.dispatchAttachedToWindow(info, visibility);
...
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()));
}
...
}
ViewGroup的dispatchAttachedToWindow()內(nèi)主要是調(diào)用父類的dispatchAttachedToWindow()方法腻格,然后循環(huán)子View,調(diào)用子View的dispatchAttachedToWindow()啥繁。
而ViewGroup的的dispatchAttachedToWindow()方法什么時(shí)候被調(diào)用呢菜职?從上面我們知道,子View的dispatchAttachedToWindow()是有父View的dispatchAttachedToWindow()調(diào)用旗闽。而DecorView是我們所有布局的父布局酬核。所以最終我們的View的dispatchAttachedToWindow()調(diào)用是由DecorView的dispatchAttachedToWindow()方法發(fā)起的。但是DecorView的dispatchAttachedToWindow()什么時(shí)候調(diào)用呢适室。由于我們之前學(xué)習(xí)了View的繪制流程【http://note.youdao.com/noteshare?id=e58f9423ec333f21ad673f971d340f5b&sub=9E9245E12A244C5395734561C7359B37】嫡意,我們知道,View的測量捣辆、布局蔬螟、繪制都是從DecorView開始,都是在ViewRootImpl內(nèi)的performTraversals()內(nèi)調(diào)用汽畴,所以我們接下來看一下performTraversals()核心代碼
五旧巾、ViewRootImpl的 performTraversals()
performTraversals的核心代碼如下
private void performTraversals() {
//是DecorView
final View host = mView;
...
host.dispatchAttachedToWindow(mAttachInfo, 0);
...
performMeasure();
...
performLayout();
...
performDraw();
...
}
從上面我們知道了,原來dispatchAttachedToWindow()的調(diào)用是由整袁、ViewRootImpl的 performTraversals() 發(fā)起的菠齿,但是我們注意到,dispatchAttachedToWindow()的發(fā)起是在performMeasure();之前坐昙。但是那為什么我們能夠在View.post()內(nèi)獲取View的寬高呢绳匀。
六、為什么dispatchAttachedToWindow()的發(fā)起是在performMeasure(),而我們我們能夠在View.post()內(nèi)獲取View的寬高疾棵?
由于之前我們分析過ViewRootImpl的performTraversals()執(zhí)行是在ViewRootImpl的TraversalRunnable內(nèi)部類中執(zhí)行戈钢。而TraversalRunnable是一個(gè)實(shí)現(xiàn)Runable的ViewRootImpl的內(nèi)部類。而scheduleTraversals()的執(zhí)行是由scheduleTraversals()方法實(shí)現(xiàn)是尔。所以分析如下:
- 我們先看一下performTraversals()的執(zhí)行
ViewRootImpl.java
//1殉了、執(zhí)行mTraversalRunnable
void scheduleTraversals() {
...
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
// 2、執(zhí)行doTraversal();
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
// 3拟枚、執(zhí)行performTraversals();
void doTraversal() {
...
performTraversals();
...
}
從上面我們知道薪铜,TraversalRunnable內(nèi)最終執(zhí)行了performTraversals()。而TraversalRunnable的執(zhí)行是由mChoreographer來實(shí)現(xiàn)的恩溅,那mChoreographer怎么實(shí)現(xiàn)執(zhí)行的呢隔箍。
- Choreographer執(zhí)行TraversalRunnable
Choreographer的核心源碼如下:
Choreographer.java
public final class Choreographer {
private final Looper mLooper;
private final FrameHandler mHandler;
//保證沒個(gè)線程內(nèi)都是不同的Choreographer實(shí)例
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
//主線程的Choreographer
private static volatile Choreographer mMainInstance;
//構(gòu)造函數(shù)
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
mHandler = new FrameHandler(looper);
...
}
//處理Runable
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
@TestApi
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
...
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
...
synchronized (mLock) {
...
使用Handler將Runable發(fā)送出去處理。
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
...
}
}
private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
}
}
從上面知道脚乡,在View的繪制過程也是基于消息機(jī)制實(shí)現(xiàn)蜒滩,在View的加載過程中是在主線程實(shí)現(xiàn)的,所以Choreographer的Looper是主線程奶稠,內(nèi)部的Handler也是在主線程處理消息俯艰。所以最終View的繪制流程是Choreographer內(nèi)部主線程的Handler發(fā)送消息并處理實(shí)現(xiàn)。
到這里锌订,我們回頭看一下竹握,我們使用View.post()方法時(shí),最終也是有主線程的Handler發(fā)送并處理消息實(shí)現(xiàn)瀑志。由于之前我們學(xué)習(xí)的Handler機(jī)制【http://note.youdao.com/noteshare?id=8670d5fcdde6bf53683fa58f489ca1d3&sub=ECF647152E3B4D42BF4DA272B156E6FB】
在Looper內(nèi)循環(huán)取出消息的方式來處理消息涩搓,當(dāng)一個(gè)消息處理完之后再取出另一個(gè)消息,由Handler處理劈猪。既然這樣昧甘,我們的View的繪制加載和View.post()都是又主線Handler來,所以只有當(dāng)View的繪制加載的消息完成之后战得,才會(huì)處理我們View.pos()發(fā)送來的消息充边,所以我們才夠在View.post()內(nèi)獲取到View的寬高。