開發(fā)的過(guò)程中,我們有時(shí)候需要在Activity
啟動(dòng)以后第一時(shí)間獲取某個(gè)View的寬高颜凯,并作相應(yīng)處理继效,當(dāng)我們?cè)?code>onCreate中通過(guò)View.getWidth
(或getMeasuredWidth
)和View.getHeight
(或getMeasuredHeight
)方法獲取的時(shí)候,你會(huì)發(fā)現(xiàn)它們都返回0装获。我們猜測(cè)是因?yàn)槿鹦牛@個(gè)時(shí)候View還沒有布局完成。解決的辦法有很多種穴豫,比如在View
的OnGlobalLayoutListener
中獲取凡简,在onLayout
中獲取等等,具體請(qǐng)參考StackOverflow:
https://stackoverflow.com/questions/18861585/get-content-view-size-in-oncreate
有另外一個(gè)方法精肃,有些人應(yīng)該也知道秤涩,在onCreate
中通過(guò)View.post
,在其中的Runnable
中就能獲取到正確的寬高司抱。那么筐眷,這里的原理是什么,很多人認(rèn)為這里就是一個(gè)時(shí)間差习柠,等View
的measure
和layout
完了就有寬和高了匀谣,有些人甚至使用postDelay
延遲幾秒來(lái)確保萬(wàn)無(wú)一失。那么這里面到底會(huì)不會(huì)有不穩(wěn)定的因素呢资溃?這篇文章告訴你答案武翎。
本篇文章分析的是Android 6.0系統(tǒng)的源碼。閱讀之前溶锭,請(qǐng)先了解Android中Handler
宝恶,Looper
和MessageQueue
等概念,推薦文章:http://blog.csdn.net/lmj623565791/article/details/38377229/
詳細(xì)分析:
請(qǐng)看View.post
源碼,
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}
在onCreate
的方法中執(zhí)行的時(shí)候垫毙,attachInfo==null
(為什么霹疫?請(qǐng)自行驗(yàn)證),所以post方法執(zhí)行到了
ViewRootImpl.getRunQueue().post(action)综芥。
這個(gè)方法是將action
先保存到ViewRootImpl
中的一個(gè)靜態(tài)隊(duì)列中更米,保存起來(lái)什么時(shí)候用呢?
在ViewRootImpl.perfromTravesals
方法中可以看到這個(gè)隊(duì)列調(diào)用的代碼毫痕,
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
大家都知道performTraversals
是布局遍歷的方法征峦,所以我們知道了,post的Runnable
是在布局遍歷的時(shí)候執(zhí)行的消请。然而栏笆,它是在performMeasure、performLayout之前執(zhí)行的臊泰。也就是說(shuō)蛉加,這個(gè)Runnable
是在View的測(cè)量和布局之前執(zhí)行。View測(cè)量和布局完成之前是獲取不到寬高的缸逃,那我們的Runnable
是怎么獲取到寬高的呢针饥。
事實(shí)上,getRunQueue().executeActions(mAttachInfo.mHandler)
也不是直接執(zhí)行這些Runnable
需频,而是往主線程消息隊(duì)列添加對(duì)應(yīng)的消息進(jìn)去丁眼,那么我們的這個(gè)消息執(zhí)行的時(shí)機(jī)是怎么保證在View布局完成之后呢?研究過(guò)performTraversals
源碼的話昭殉,應(yīng)該知道苞七,performTraversals
這個(gè)方法也是被附到一個(gè)消息上添加到消息隊(duì)列等待執(zhí)行的。下面看分析挪丢。
首先從View布局的終極方法performTraversals
開始分析蹂风,這個(gè)方法到底是怎么執(zhí)行的,在哪執(zhí)行的乾蓬。一路追過(guò)去惠啄,
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
perfromTraversals
由doTraversal
調(diào)用,
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
而doTraversal
方法在TraversalRunnable
調(diào)用任内,那么這個(gè)Runnable
的對(duì)象在哪執(zhí)行的呢撵渡?
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
可以看到,mTraversalRunnable
出現(xiàn)在這個(gè)方法scheduleTraversals
里:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
這里面mTraversalRunnable
也是被post了族奢,這個(gè)post方法叫做postCallback
姥闭,猜測(cè)是不是也是加到消息隊(duì)列去了丹鸿?
繼續(xù)看mChoreographer.postCallback
方法越走,這個(gè)方法最終會(huì)到下面這個(gè)方法中:
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
注意到熟悉的一句代碼:mHandler.sendMessageAtTime
,這個(gè)方法就是將消息加入到消息隊(duì)列,而這個(gè)Handler
對(duì)應(yīng)的消息隊(duì)列是什么廊敌?
我們從 mHandler
這個(gè)對(duì)象著手铜跑,看看它是怎么來(lái)的。
private Choreographer(Looper looper) {
mLooper = looper;
mHandler = new FrameHandler(looper);
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}
在這個(gè)構(gòu)造函數(shù)中骡澈,可以看到mHandler
構(gòu)造的時(shí)候傳入一個(gè)Looper
對(duì)象锅纺,那就要看下這個(gè)Looper
是從哪里來(lái)的。
// Thread local storage for the choreographer.
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!");
}
return new Choreographer(looper);
}
};
這里又看到熟悉的代碼了Looper looper = Looper.myLooper();
獲取了當(dāng)前線程(主線程)的Looper
對(duì)象肋殴。
所以囤锉,performTraversals
方法實(shí)際上也是被加入到消息隊(duì)列中去執(zhí)行的,而我們最初post的Runnable
护锤,是在performTraversals
方法中將其附加到一個(gè)消息上并加入到消息隊(duì)列里官地。所以,我們post的Runnable
的消息烙懦,肯定是在performTraversals
的消息之后執(zhí)行的驱入,也就是我們post的Runnable
肯定是在performTraversals
之后執(zhí)行,而performTraversals
之后氯析,View的寬和高便計(jì)算出來(lái)了亏较。
需要注意的是, 在onCreate
(以及onStart
, onResume
)方法中必須保證是在主線程調(diào)用View.post
方法掩缓,因?yàn)?code>ViewRootImpl.getRunQueue()在不同線程獲取到的是不同的對(duì)象(為什么雪情?請(qǐng)研究getRunQueue()
的類型),所以在onCreate
方法內(nèi)新建線程并在里面post的時(shí)候你辣,這個(gè)post是成功不了的旺罢。在Android 7.0中修復(fù)了這個(gè)問(wèn)題,也就說(shuō)7.0系統(tǒng)中绢记,在onCreate
方法中新建線程并在里面post扁达,這個(gè)Runnable
是可以被執(zhí)行到的。感興趣的看源碼蠢熄。
一句話總結(jié):
在onCreate
中調(diào)用View.post
的時(shí)候跪解,post的Runnable
被保存起來(lái),在下一次遍歷布局的時(shí)候重新加到消息隊(duì)列里(Android 7.0是在attach window的時(shí)候重新加到消息隊(duì)列里)签孔,而且布局遍歷本身也是加到消息隊(duì)列執(zhí)行的叉讥,所以我們post的Runnable
所在的消息肯定是在布局遍歷之后,所以在Runnable
里必定可以獲取到正確的寬高饥追。