View.Post () 的身世大揭秘
作者:Insane 時(shí)間: 2018.7.31
View.post( ),大家肯定都用過,也就不陌生了。一般使用View.Post ( ) 的場(chǎng)景最常見的就是
1.子線程更UI,
2.獲取View的寬高
那就讓我們?cè)賻е鴨栴}去看看原因咯。
public boolean post(Runnable action) {//判斷 attachInfo 是否為空,而進(jìn)行不同的操作//那么其實(shí)就是要知道 mAttachInfo 是在哪里被賦值的?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;} 復(fù)制代碼mAttachInfo的賦值
我們會(huì)發(fā)現(xiàn)他兩個(gè)被賦值的地方,分別為 dispatchAttachedToWindow,dispatchDetachedFromWindow
//dispatchAttachedToWindow: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.//緩存不為空的是時(shí)候去執(zhí)行 緩存的 actionif (mRunQueue != null) {mRunQueue.executeActions(info.mHandler);mRunQueue = null;}performCollectViewAttributes(mAttachInfo, visibility);//當(dāng)對(duì)應(yīng)的 Activity 被添加到 Window的時(shí)候調(diào)用,只調(diào)用一次onAttachedToWindow();// .......省略代碼 //dispatchDetachedFromWindow:void dispatchDetachedFromWindow() {AttachInfo info = mAttachInfo;if (info != null) {int vis = info.mWindowVisibility;if (vis != GONE) {onWindowVisibilityChanged(GONE);if (isShown()) {// Invoking onVisibilityAggregated directly here since the subtree// will also receive detached from windowonVisibilityAggregated(false);}}}onDetachedFromWindow();onDetachedFromWindowInternal();InputMethodManager imm = InputMethodManager.peekInstance();if (imm != null) {imm.onViewDetachedFromWindow(this);}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.onViewDetachedFromWindow(this);}}if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) {mAttachInfo.mScrollContainers.remove(this);mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;}//這里賦空值mAttachInfo = null;if (mOverlay != null) {mOverlay.getOverlayView().dispatchDetachedFromWindow();}notifyEnterOrExitForAutoFillIfNeeded(false);} 復(fù)制代碼但是會(huì)發(fā)現(xiàn),到這里的時(shí)候我們無法再追蹤這兩個(gè)方法在哪里被調(diào)用了,于是我們可以通過網(wǎng)上那些Android源碼閱讀的網(wǎng)站,或者自己有下載Android源碼的來找一找看看 究竟在什么地方被調(diào)用的: 推薦一個(gè):http://androidxref.com/ 搜索 dispatchAttachedToWindow,可以發(fā)現(xiàn)如下:
可以發(fā)現(xiàn),在 ViewGroup 和 ViewRootImpl 均有被調(diào)用,那么我們就去看看。
ViewGroup
@Overridevoid dispatchAttachedToWindow(AttachInfo info, int visibility) {mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;// super.dispatchAttachedToWindow(info, visibility); 這句話就是說明他執(zhí)行了父類的方法//也就是我們一開始看到的 View 的dispatchAttachedToWindow()的方法。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];//這里又會(huì)把 mAttachInfo 作為參數(shù)傳遞進(jìn)去,分別讓自己的子類去執(zhí)行 dispatchAttachedToWindow () 方法,//讓自己的子類 分別給 mAttachInfo 賦值。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()));}} /*但是這樣一來我們還是不知道 mAttachInfo 是在那里被賦值的發(fā),只是知道 ViewGroup 會(huì)去執(zhí)行 View 類和掉用子 View 的 dispatchAttachedToWindow () 方法。 方法。*/復(fù)制代碼繼續(xù)看看 ViewGroup 里面還有什么地方調(diào)用了: addViewInner()方法是 viewGroup addView( )內(nèi)部都會(huì)調(diào)用的一個(gè)方法
private void addViewInner(View child, int index, LayoutParams params,boolean preventRequestLayout) {if (mTransition != null) {// Don't prevent other add transitions from completing, but cancel remove// transitions to let them complete the process before we add to the containermTransition.cancel(LayoutTransition.DISAPPEARING);}//判斷View是否被添加if (child.getParent() != null) {throw new IllegalStateException("The specified child already has a parent. " +"You must call removeView() on the child's parent first.");}if (mTransition != null) {mTransition.addChild(this, child);}if (!checkLayoutParams(params)) {params = generateLayoutParams(params);}if (preventRequestLayout) {child.mLayoutParams = params;} else {child.setLayoutParams(params);}if (index < 0) {index = mChildrenCount;}//添加到 ViewGroupaddInArray(child, index);// tell our childrenif (preventRequestLayout) {child.assignParent(this);} else {child.mParent = this;}final boolean childHasFocus = child.hasFocus();if (childHasFocus) {requestChildFocus(child, child.findFocus());}//這里判斷 mAttachInfo 的對(duì)象是否為空,如果不為空就把 mAttachInfo 作為參數(shù)調(diào)用子類的 dispatchAttachedToWindow ( ),那么//還是回到了 View 的 dispatchAttachedToWindow (),我們還是不知道 mAttachInfo 再哪里給賦值的.AttachInfo ai = mAttachInfo;if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {boolean lastKeepOn = ai.mKeepScreenOn;ai.mKeepScreenOn = false;child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));if (ai.mKeepScreenOn) {needGlobalAttributesUpdate(true);}ai.mKeepScreenOn = lastKeepOn;}.......省略 復(fù)制代碼既然ViewGroup沒有,那么我們就去看看 ViewRootImpl。
ViewRootImpl
我們?cè)?ViewRootImpl 的 performTraversals(),發(fā)現(xiàn)了dispatchAttachedToWindow()被調(diào)用,而 performTraversals() 作用就是遍歷整個(gè)View樹,并且按照要求進(jìn)行measure,layout和draw流程。
private void performTraversals() {// cache mView since it is used so much below...final View host = mView;//判斷是不是第一次if (mFirst) {.....//這里調(diào)用了 dispatchAttachedToWindow,并且把 mAttachInfo 給子viewhost.dispatchAttachedToWindow(mAttachInfo, 0);mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);dispatchApplyInsets(host);//Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);..... } mFirst=false...// Execute enqueued actions on every traversal in case a detached view enqueued an actiongetRunQueue().executeActions(mAttachInfo.mHandler);...performMeasure();...performLayout();...performDraw();... 復(fù)制代碼上面的代碼,等下我們?cè)倩貋砜?#xff0c;我們先找找在 ViewRootImpl 里面 mAttachInfo 是在哪被賦值的
...final View.AttachInfo mAttachInfo;...// mAttachInfo 就是在這里被賦值了,其中在多個(gè)參數(shù)之中,我們發(fā)現(xiàn)了 mHandler。mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);//繼續(xù)看看 mHandler 是在哪被初始化的。final ViewRootHandler mHandler = new ViewRootHandler(); /* 通過這句代碼我們就可以知道。這里 new 的時(shí)候是無參構(gòu)造函數(shù),那默認(rèn)綁定的就是當(dāng)前線程的 Looper,而這句 new 代碼是在主線程中執(zhí)行的,所以這個(gè) Handler 綁定的也就是主線程的 Looper */ 再結(jié)合:getRunQueue().executeActions(mAttachInfo.mHandler);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;}} 復(fù)制代碼為什么能更新UI:
總結(jié)回顧一下: 我們知道了 mAttachInfo 是在 ViewRootImpl 初始化的,再結(jié)合剛說等下回去看的 performTraversals 的方法,可以知道ViewRootImpl 會(huì)調(diào)用子view的 dispatchAttachedToWindow。我們還可以知道為什么 View.post(Runnable),可以更新UI了,因?yàn)檫@些 Runnable 操作都通過 ViewRootImpl 的 mHandler 切到主線程來執(zhí)行了。
為什么能獲取寬高
那么我們?cè)俅位氐?一開始的的地方,我們知道 View 里面的 mAttachInfo 是在 View 的 dispatchAttachedToWindow 被賦值,那么 dispatchAttachedToWindow()是在什么時(shí)候執(zhí)行的呢?我們上面分析的是在哪調(diào)用了他,和 mAttachInfo的初始化,細(xì)心的朋友,會(huì)發(fā)現(xiàn)在View的 dispatchAttachedToWindow() 有 onAttachedToWindow();,那么我們就可簡(jiǎn)單寫個(gè)測(cè)試。
class TestView : TextView {constructor(context: Context) : super(context) {}constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {}constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}override fun onAttachedToWindow() {Log.e("TAG---AttachedToWindow", "onAttachedToWindow");super.onAttachedToWindow();} } //MainActivity : class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)Log.e("TAG---沒有Post", "mButton width : " + tv_test.getMeasuredWidth() + " - height : " + tv_test.getMeasuredHeight());tv_test.post {Log.e("TAG---Post", "mButton width : " + tv_test.getMeasuredWidth() + " - height : " + tv_test.getMeasuredHeight());}} }//結(jié)果: 07-29 17:17:24.201 31814-31814/? E/TAG---沒有Post: mButton width : 0 - height : 0 07-29 17:17:24.261 31814-31814/? E/TAG---AttachedToWindow: onAttachedToWindow 07-29 17:17:24.351 31814-31814/? E/TAG---Post: mButton width : 84 - height : 57 復(fù)制代碼那么結(jié)果就出來了,在 onCreate 中獲取寬高,AttachedToWindow ( ) 是還沒執(zhí)行的,那就說明一開始的時(shí)候 mAttachInfo 是為空值的,那么我們?cè)倏撮_頭的第一段代碼:
public boolean post(Runnable action) {// mAttachInfo 為空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;} 那么他就會(huì)執(zhí)行: getRunQueue().post(action);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++;}} 復(fù)制代碼我們post()傳進(jìn)來的 Runnable 會(huì)先經(jīng)過 HandlerAction 包裝一下,然后再緩存起來。HandlerActionQueue 是通過一個(gè)默認(rèn)大小為4的數(shù)組保存這些 Runnable 操作的,如果數(shù)組不夠時(shí),就會(huì)通過 GrowingArrayUtils 來擴(kuò)充數(shù)組。那么既然是被緩存起來的,那么他是什么時(shí)候執(zhí)行呢?我又會(huì)發(fā)現(xiàn) 他執(zhí)行的方法還是在 dispatchAttachedToWindow 里面:
dispatchAttachedToWindow ://緩存不為空的是時(shí)候去執(zhí)行 緩存的 actionif (mRunQueue != null) {mRunQueue.executeActions(info.mHandler);mRunQueue = null;}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;}} 復(fù)制代碼既然我們知道了 post()傳進(jìn)來的 Runnable 會(huì)在 dispatchAttachedToWindow 執(zhí)行,結(jié)合我們上面的分析,我們就可以知道,post 的操作是要經(jīng)過 ViewRootImpl 的 performTraversals(),而它的作用就是遍歷整個(gè)View樹,并且按照要求進(jìn)行measure,layout和draw流程。但是仔細(xì)看看代碼,我們會(huì)發(fā)現(xiàn):
private void performTraversals() {// cache mView since it is used so much below...final View host = mView;//判斷是不是第一次if (mFirst) {.....//這里調(diào)用了 dispatchAttachedToWindow,明顯是在 performMeasure 之前,//為什么在測(cè)量之前調(diào)用還能得到寬高呢?host.dispatchAttachedToWindow(mAttachInfo, 0);mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);dispatchApplyInsets(host);//Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);..... } mFirst=false...// Execute enqueued actions on every traversal in case a detached view enqueued an actiongetRunQueue().executeActions(mAttachInfo.mHandler);...performMeasure();...performLayout();...performDraw();... 復(fù)制代碼那么 為什么明明測(cè)量 performMeasure(); 的是在 dispatchAttachedToWindow 之后執(zhí)行,但是我們卻能得到測(cè)量后的寬高?請(qǐng)看下面的代碼:
final class TraversalRunnable implements Runnable {@Overridepublic void run() {// doTraversal 這里執(zhí)行doTraversal();}}final TraversalRunnable mTraversalRunnable = new TraversalRunnable();void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;//向Looper中移除了Barrier(監(jiān)控器),同步的消息可以執(zhí)行mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);if (mProfile) {Debug.startMethodTracing("ViewAncestor");}//performTraversals() 在這里被執(zhí)行performTraversals();if (mProfile) {Debug.stopMethodTracing();mProfile = false;}}} 復(fù)制代碼mTraversalBarrier 是什么東東?
為了讓View能夠有快速的布局和繪制,android中定義了一個(gè)Barrier的概念,當(dāng)View在繪制和布局時(shí)會(huì)向Looper中添加了Barrier(監(jiān)控器),這樣后續(xù)的消息隊(duì)列中的同步的消息將不會(huì)被執(zhí)行,以免會(huì)影響到UI繪制,但是只有異步消息才能被執(zhí)行。 所謂的異步消息也只是體現(xiàn)在這,添加了Barrier后,消息還可以繼續(xù)被執(zhí)行,不會(huì)被推遲運(yùn)行。 如何使用異步消息,只有在創(chuàng)建Handler(構(gòu)造方法的參數(shù)上標(biāo)識(shí)是否異步消息)的時(shí)候或者在發(fā)送Message(Mesasge#setAsynchronous(true))時(shí)進(jìn)行設(shè)置。而異步消息應(yīng)用層是無法設(shè)置,因?yàn)橄嚓P(guān)設(shè)置的方法均是Hide的。
那就是什么意思呢? 首先,我們搞清楚 mTraversalScheduled 這個(gè)對(duì)象是在哪被賦值。
void scheduleTraversals() {if (!mTraversalScheduled) {//這里mTraversalScheduled = true;//向Looper中添加了Barrier(監(jiān)控器),這樣后續(xù)的消息隊(duì)列中的同步的消息將不會(huì)被執(zhí)行,//以免會(huì)影響到UI繪制,但是只有異步消息才能被執(zhí)行mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//這里又調(diào)用了 mTraversalRunnable。mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();}}void unscheduleTraversals() {if (mTraversalScheduled) {//這里mTraversalScheduled = false;//向Looper中移除了Barrier(監(jiān)控器),同步的消息可以執(zhí)行mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);//這里又調(diào)用了 mTraversalRunnable。mChoreographer.removeCallbacks(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);}} 復(fù)制代碼scheduleTraversals ( ) 方法是在 requestLayout( )被調(diào)用的, requestLayout( )是什么?額,這里就不解釋了,不然又要寫一大堆,哈哈。只要知道 第一次調(diào)用requestLayout( ) 就是引起整個(gè) View 的繪制流程
@Overridepublic void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {// 檢查當(dāng)前線程checkThread();mLayoutRequested = true;// 調(diào)用繪制scheduleTraversals(); 復(fù)制代碼那么就是 scheduleTraversals( )——》TraversalRunnable ( )——》doTraversal( )——》performTraversals( ),而且 doTraversal ( )中向Looper中移除了Barrier(監(jiān)控器),同步的消息可以執(zhí)行, 并且我們知道了 ViewRootImp 的 Handler 就是主線程的 ,而我們一開始 post 進(jìn)來的 Runnable 也是在主線程的,而主線程的 Handler 是同步的,就是執(zhí)行完一個(gè) Message 才回繼續(xù)往下執(zhí)行。那么 我們?cè)俅位氐?performTraversals( )
// 往下看的時(shí)候就會(huì)發(fā)現(xiàn),在下面 又會(huì)再一次執(zhí)行 scheduleTraversals( ),也就是代表會(huì)再一次執(zhí)行 performTraversals( ),if (!cancelDraw && !newSurface) {if (mPendingTransitions != null && mPendingTransitions.size() > 0) {for (int i = 0; i < mPendingTransitions.size(); ++i) {mPendingTransitions.get(i).startChangingAnimations();}mPendingTransitions.clear();}performDraw();} else {if (isViewVisible) {// Try againscheduleTraversals();} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {for (int i = 0; i < mPendingTransitions.size(); ++i) {mPendingTransitions.get(i).endChangingAnimations();}mPendingTransitions.clear();}} 復(fù)制代碼再一次執(zhí)行的時(shí)候,已經(jīng)是在 performMeasure( )之后了,那當(dāng)執(zhí)行到 我們 Post 的時(shí)候 自然而然就能得到寬高了。
總結(jié)
以上是生活随笔為你收集整理的View.Post () 的身世大揭秘的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 正则表达式 特殊符号系列 通配符系列
- 下一篇: MySQL 数据库常用存储引擎的特点