首先寫這篇文章之前貼上二篇分析較好的博客,非常感謝博主的奉獻(xiàn)精神
http://blog.csdn.net/scnuxisan225/article/details/49815269
http://mp.weixin.qq.com/s/laR3_Xvr0Slb4oR8D1eQyQ
大家都遇到過在android開發(fā)時(shí),在Activity中的onCreate方法中通過控件的getMeasureHeight/getHeight或者getMeasureWidth/getWidth方法獲取到的寬高大小都是0,我相信大家遇到這種問題時(shí)首先會(huì)想到打開度娘然后一搜,常見的二種解決方案就出來了。
1.通過監(jiān)聽Draw/Layout事件:ViewTreeObserver
1 view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
2 @Override
3 public void onGlobalLayout() {
4 mScrollView.post(new Runnable() {
5 public void run() {
6 view.getHeight(); //height is ready
7 }
8 });
9 }
10 });
當(dāng)我們注冊(cè)這個(gè)監(jiān)聽時(shí),控件經(jīng)過 onMeasure->onLayout->onDraw一系列方法渲染完成后會(huì)去回調(diào)這個(gè)注冊(cè)的監(jiān)聽,我們自然能拿到控件的寬高。
2.第二種方法我比較喜歡,只要用View.post()一個(gè)runnable就可以了
1 ...
2 view.post(new Runnable() {
3 @Override
4 public void run() {
5 view.getHeight(); //height is ready
6 }
7 });
8 ...
我們一般這么用總能拿到控件的寬高大小,方法是非常好用,但是本著十萬個(gè)為什么態(tài)度,我決定把這個(gè)方法的原理整理一遍。
我們先回憶下,android中每個(gè)界面是個(gè)Activity,每個(gè)Activity最頂層是一個(gè)DecorView,它包裹我們自定義的布局.
好接下來我們看下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;
}
這里面判斷mAttachInfo是不是為空,如果不為null時(shí),直接取出mAttachInfo中存放的Handler對(duì)象去post 我們的Runnable任務(wù),如果為null的話我們看看getRunQueue()方法會(huì)做什么
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
它會(huì)去創(chuàng)建一次HandlerActionQueue對(duì)象然后把這個(gè)對(duì)象返回,好我們點(diǎn)進(jìn)這個(gè)對(duì)象看一下
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++;
}
}
....
我們之前代碼可以看到如果mAttachInfo為null的話會(huì)去調(diào)用HandlerActionQueue對(duì)象中的post方法傳遞我們Runnable任務(wù),接著會(huì)調(diào)用postDelayed方法,這個(gè)方法會(huì)把我們的Runnable任務(wù)和需要延遲的時(shí)間都封裝到HandlerAction對(duì)象中然后加入到下面的HandlerAction[]數(shù)組中,點(diǎn)進(jìn)去也會(huì)發(fā)現(xiàn)HandlerAction 就是一個(gè)簡單的封裝類。
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);
}
}
現(xiàn)在我們回過頭思考一下就會(huì)有疑惑了,情景回到post方法中,我們根據(jù)mAttachInfo這個(gè)值判斷是直接post發(fā)送任務(wù)還是把任務(wù)放入隊(duì)列,那么這個(gè)值是什么時(shí)候被賦值的呢?
答案就在ViewRootImpl類中,在ViewRootImpl構(gòu)造中有這么端代碼創(chuàng)建了AttachInfo對(duì)象。
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
然后在performTraversals()方法中會(huì)去調(diào)用這么段代碼 ,這段代碼執(zhí)行在大家很熟悉的 performMeasure、performLayout、performDraw方法之前。
host.dispatchAttachedToWindow(mAttachInfo, 0);
host就是DecorView,它是一個(gè)ViewGroup,所以我們先看看ViewGroup中的dispatchAttachedToWindow方法
@Override
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
super.dispatchAttachedToWindow(info, visibility);
mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
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()));
}
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchAttachedToWindow(info,
combineVisibility(visibility, view.getVisibility()));
}
}
可以看到主要這個(gè)方法中會(huì)調(diào)用自己和所有child父類也就是View中的dispatchAttachedToWindow方法,那我們看看View中的dispatchAttachedToWindow方法究竟干了些什么事情吧?
/**
* @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);
}
mWindowAttachCount++;
// We will need to evaluate the drawable state at least once.
mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
if (mFloatingTreeObserver != null) {
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
registerPendingFrameMetricsObservers();
if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
mAttachInfo.mScrollContainers.add(this);
mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
}
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
}
int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(vis);
if (isShown()) {
// Calling onVisibilityChanged directly here since the subtree will also
// receive dispatchAttachedToWindow and this same call
onVisibilityAggregated(vis == VISIBLE);
}
}
// Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
// As all views in the subtree will already receive dispatchAttachedToWindow
// traversing the subtree again here is not desired.
onVisibilityChanged(this, visibility);
if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
// If nobody has evaluated the drawable state yet, then do it now.
refreshDrawableState();
}
needGlobalAttributesUpdate(false);
}
這段代碼比較長但是我們一眼就可以看到mAttachInfo 正是在這里被賦值的,而被賦值調(diào)用的地方正是在上面ViewRootImpl中的performTraversals()方法中!
然后我們接著看這個(gè)方法
mRunQueue.executeActions(info.mHandler);
調(diào)用了這句代碼,執(zhí)行了我們前面提到的HandlerAction類中的executeActions方法,我們看下這個(gè)方法做了些什么事情
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;
}
}
ok,一切思路都那么清晰了,這個(gè)方法會(huì)用我們傳遞進(jìn)來的mAttachInfo中的Handler去遍歷我們View.post儲(chǔ)存的所有Runnable任務(wù)。
至此貌似所有的流程都分析完畢了,但是如果有細(xì)心的同學(xué)會(huì)發(fā)現(xiàn)前面的分析有漏洞,哪里呢?就是在執(zhí)行ViewRootImpl中的performTraversals()方法的時(shí)候,
host.dispatchAttachedToWindow(mAttachInfo, 0);
調(diào)用這個(gè)方法明明執(zhí)行在測(cè)量,布局,繪制三個(gè)方法之前,也就是說調(diào)用這個(gè)方法后就會(huì)拿到我們傳遞mAttachInfo中Handler去執(zhí)行View.post中的Runnable,然后才會(huì)去調(diào)用測(cè)量,布局,繪制三個(gè)方法,那理論上還是拿不到寬高值啊,這個(gè)時(shí)候還沒執(zhí)行測(cè)量啊,可是為什么結(jié)果可以拿到呢???
沒關(guān)系,我一開始也是這么認(rèn)為的并且陷入了很長一段時(shí)間的困惑當(dāng)中,這是由于不了解android消息機(jī)制導(dǎo)致的,Android的運(yùn)行其實(shí)是一個(gè)消息驅(qū)動(dòng)模式,也就是說在Android主線程中默認(rèn)是創(chuàng)建了一個(gè)Handler的,并且這個(gè)主線程中創(chuàng)建了一個(gè)Looper去循環(huán)遍歷執(zhí)行隊(duì)列中的Message,這個(gè)執(zhí)行是同步的,也就是說執(zhí)行完一個(gè)Message后才會(huì)去繼續(xù)執(zhí)行下一個(gè),調(diào)用performTraversals()這個(gè)方法是通過主線程的Looper遍歷執(zhí)行的,這個(gè)方法還沒執(zhí)行結(jié)束,然后我們?cè)谶@個(gè)方法中通過mAttachInfo中Handler去執(zhí)行View.post中的Runnable,mAttachInfo中Handler也是創(chuàng)建在主線程,所以它會(huì)在上一個(gè)消息執(zhí)行結(jié)束后才會(huì)被執(zhí)行,也就是說會(huì)在測(cè)量,布局,繪制執(zhí)行后才執(zhí)行,這樣自然而然能拿到控件的寬和高啦。
我不禁敬佩,Android團(tuán)隊(duì)的這個(gè)機(jī)制設(shè)計(jì)的太巧妙了。