接上篇: Android獲取View的寬和高(一)
其實(shí)除了ViewTreeObserver這個觀察者類告私,還可以通過View.Post()獲取到View寬高margin值的信息,代碼如下:
btn02.post(new Runnable() {
@Override
public void run() {
//可以正常獲取到View寬高 margin
LogUtil.d(" btn02.post" + btn02.getWidth()+btn02.getTop());
}
});
看log日志輸出的時候發(fā)現(xiàn)run方法是在ViewTreeObserver的OnGlobalLayoutListener啦扬,OnPreDrawListener濒翻,OnDrawListener之后執(zhí)行的屁柏,那么它到底什么時候調(diào)用的呢啦膜,點(diǎn)開view的post方法,查看源碼代碼如下:
/**
* <p>Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.</p>
*//把該runnable加入到 mq里,最終在ui線程執(zhí)行
*
*/
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;
}
/**
* Returns the queue of runnable for this view.
*/
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
代碼里包含了一個attachInfo和 HandlerActionQueue淌喻,attachInfo是View在添加到window上的描述信息僧家,HandlerActionQueue姑且看作一個Handler去發(fā)送通知的,繼續(xù)查看AttachInfo的賦值過程裸删,代碼如下:
/**
* @param info the {@link android.view.View.AttachInfo} to associated with
* this view
*/
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
}
registerPendingFrameMetricsObservers();
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<View.OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
for (View.OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
}
needGlobalAttributesUpdate(false);
notifyEnterOrExitForAutoFillIfNeeded(true);
}
mAttachInfo在 dispatchAttachedToWindow中被賦值八拱,它其實(shí)是在ViewRootImpl中構(gòu)造的,(ViewRootImpl 可以理解是一個 Activity 的 ViewTree 的根節(jié)點(diǎn)的實(shí)例涯塔,它是用來管理 DecorView 和 ViewTree)肌稻。在ViewRootImpl構(gòu)造方法為:
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);
而dispatchAttachedToWindow只會在兩種情況下被調(diào)用:
- ViewRootImpl 第一次 performTraversal()時會將整個view tree里所有有view的 dispatchAttachedToWindow() DFS 調(diào)用一遍.
- ViewGroup的 addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout);
這時我們應(yīng)該是第一種情況下的調(diào)用,我們在去查看ViewRootImpl中的 performTraversal()方法:
private void performTraversals () {
// cache mView since it is used so much below...
final View host = mView
host.dispatchAttachedToWindow(mAttachInfo, 0);
//viewTreeObserver也在這個方法中調(diào)用
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
//對每次遍歷操作進(jìn)行排隊(duì)
getRunQueue().executeActions(mAttachInfo.mHandler);
// Ask host how big it wants to be//測量過程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
//onLayout執(zhí)行
performLayout(lp, mWidth, mHeight);
//開始繪制
performDraw();
}
該方法是View整個繪制過程匕荸;其中 getRunQueue()方法如下:
static HandlerActionQueue getRunQueue() {
// sRunQueues是 ThreadLocal<HandlerActionQueue> 對象
HandlerActionQueue rq = sRunQueues.get();
if (rq != null) {
return rq;
}
rq = new HandlerActionQueue();
sRunQueues.set(rq);
return rq;
}
忽然找到了親人爹谭,這不就是post里的HandlerActionQueue那個對象么,到目前他們之間到關(guān)系貌似也漸漸清晰起來了榛搔。
總結(jié)一下:
- 1.View在attachtoWindow之前诺凡,會維護(hù)一個HandlerActionQueue對象,儲存當(dāng)前的runnable 對象践惑,當(dāng)attach to window(ViewRootImpl 執(zhí)行到 performTraversal 方法)的時候交給ViewRootImpl處理腹泌。
- 2.View的dispatchAttachedToWindow方法也會為當(dāng)前view創(chuàng)建一個attachInfo對象,該對象持有 ViewRootImpl 的引用尔觉,當(dāng) View 有此對象后凉袱,后續(xù)的所有 Runnable 都將直接交給 ViewRootImpl 處理。
- 3.ViewRootImpl在執(zhí)行performTraversal方法是通過getRunQueue() 方法在 ThreadLocal中維護(hù)一個HandlerActionQueue對象侦铜,ViewRootImpl使用該對象對runnable進(jìn)行短期維護(hù)专甩。
- 4.但需要注意的是,各個 View 調(diào)用的 post 方法泵额,仍然是由各自的 HandlerActionQueue 對象來入隊(duì)任務(wù)的配深,然后在 View#dispatchAttachedToWindow 的時候轉(zhuǎn)移給 ViewRootImpl 去處理携添。
另外嫁盲,View.post也有不靠譜的地方,API24和23的post方法是有區(qū)別的烈掠,23的代碼如下:
// Android API23 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 來入隊(duì)任務(wù)的
ViewRootImpl.getRunQueue().post(action);
return true;
}
再來看一下 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.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
post方法不靠譜的原因根本上是executeActions() 方法的調(diào)用時機(jī)不同,導(dǎo)致 View 在沒有 mAttachInfo 對象的時候左敌,表現(xiàn)不一樣了瘾蛋。具體可參考這篇日志。
除此以外矫限,ViewRootImpl 使用 ThreadLocal 來存儲隊(duì)列信息哺哼,在某些情境下佩抹,還會導(dǎo)致內(nèi)存泄漏。詳細(xì)信息可以參考: