最近由於我的緣由,致使要離開杭州了,人生就是一次又一次的意外,你不知道忽然會發生什麼,你能作到的只有把握好每一次機會java
在Android中,想要獲取View的寬高,常見的基本都是接調用View.getWidth或者View.getMeasuredWidth方法來獲取寬高,可是每每在oncreate或者onresume中,獲得的值都是0,也就能夠理解爲此時View的繪製流程並無執行完,熟悉Activity啓動流程的朋友都知道,Activity的生命週期和頁面的繪製流程並非一個串行的狀態,沒有特定的前後關係,因此也不難理解獲取的值是0了android
再次回到主題,那爲何View.post(),就能夠獲取到準確的值呢,不妨猜想一下,首先總體上思考一下,想要實現知道準確的寬高,那就是post的Runnable那確定是在View整個繪製流程結束以後才執行的,主線程又是基於Looper的消息機制的,若是把Runnable直接做爲一個消息插入消息隊列,那麼很明顯不能保證這種效果,熟悉View繪製流程的朋友知道,View的繪製是在ViewRootImp中的,但View的繪製其實也是一個Runnable消息,那麼咱們可不能夠先把post的這個Runnable給緩存起來,等到繪製的Runnable執行完以後,再來通知去執行,這樣就可以獲取到準確的寬高了。數組
本文源碼是API是android-28,不一樣版本可能有些差別,須要讀者自行注意緩存
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}
複製代碼
能夠看到方法很簡答,主要就是一個attachInfo,若是不爲空就直接使用attachInfo.mHandler去執行這個action,若是爲空,把Runnable放入一個相似隊列的東西里面app
咱們再回頭想一想開頭說的話,好像還真是這麼實現的,這裏的mAttachInfo其實能夠看作爲是否已經繪製好了的一個標誌,若是不爲空,說明繪製完成,直接handler執行action,若是爲空,說明沒有繪製完,這時候就把Runnable緩存起來,那麼關鍵點也就來了,這個mAttachInfo是何時被賦值的,全局搜索下賦值ide
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
......
}
複製代碼
從這個方法名字,咱們也應該能看出來,綁定到window的時候,此時會進行賦值mAttachInfo,也就意味着繪製完畢,固然,咱們還不知道dispatchAttachedToWindow這個方法是何時調用的,先這麼理解着oop
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
複製代碼
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++;
}
}
複製代碼
能夠看到,其實是維護了一個HandlerActionQueue類,內部維護了一個數組,長度竟然是固定爲4(問號臉),而後將這些Runnabel給緩存起來。那疑問就來了,既然是緩存起來,那何時執行的,能夠看到有個executeActions方法post
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;
}
}
複製代碼
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
......
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
....
}
複製代碼
經過傳遞進來的handler,而後將內部緩存的Runnable去執行,ctrl一下,看看哪裏調用了,咦。又是dispatchAttachedToWindow這個方法。this
先總結下,View.post(Runnable) 的這些 Runnable 操做,在 View 被 attachedToWindow 以前會先緩存下來,而後在 dispatchAttachedToWindow() 被調用時,就將這些緩存下來的 Runnable 經過 mAttachInfo 的 mHandler 來執行。在這以後再調用 View.post(Runnable) 的話,這些 Runnable 操做就不用再被緩存了,而是直接交由 mAttachInfo 的 mHandler 來執行spa
因此問題最終也就到這個方法這裏了,這個方法是何時被調用的,ctrl一下。。竟然沒有地方調用,那確定是隱藏類調用了,此時祭出咱們的Source Insight,從源碼裏面找找。
private void performTraversals() {
.....
final View host = mView;
.....
host.dispatchAttachedToWindow(mAttachInfo, 0)
....
}
final ViewRootHandler mHandler = new ViewRootHandler();
public ViewRootImpl(Context context, Display display) {
...
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
....
}
複製代碼
這個方法是否是很眼熟,View的繪製流程就從這個方法開始的,能夠看到的是,mAttachInfo這個對象是在ViewRootImpl初始化的時候就賦值了,而且Handler是直接在主線程中建立的,這個就能夠說明了爲何View.post是能夠更新UI的,由於最終的Runnable是在主線程的Handler中去執行的,天然是能夠更新UI的。
可是你們有可能還有個疑問,那既然View的繪製也是這個方法執行的,dispatchAttachedToWindow也是在這個方法執行的,那怎麼能保證必定是在View的繪製流程完成以後纔去執行dispatchAttachedToWindow的呢。
答案也很簡單,由於android主線程是基於Looper的消息機制的,不斷的從綁定Looper的MessageQueue中去獲取message去執行,View的繪製操做其實也是一個Runnable對象,因此在執行performTraversals()方法的時候,調用dispatchAttachedToWindow方法,這個時候,因此子View經過View.post(Runnable)緩存的Runnabel是會經過mAttachInfo.mHandler 的 post() 方法將這些 Runnable 封裝到 Message 裏發送到 MessageQueue 裏。mHandler 咱們上面也分析過了,綁定的是主線程的 Looper,因此這些 Runnable 其實都是發送到主線程的 MessageQueue 裏排隊的,因此也就能夠保證這些 Runnable 操做也就確定會在 performMeasure() 操做以後才執行,寬高也就能夠獲取到了,咱們也能夠在源碼中找到些許痕跡
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
.....
performTraversals();
....
}
}
複製代碼