requestLayout竟然涉及到這么多知識(shí)點(diǎn)
1. 背景
最近有個(gè)星標(biāo)?粉絲跟我提了一個(gè)很有深度的問題。
?粉絲:鎖屏后,調(diào)用View.requestLayout()方法后會(huì)不會(huì)postSyncBarrier?
?
乍一看有點(diǎn)超綱了。細(xì)細(xì)一想,沒超綱。我把這個(gè)問題拆分成了兩個(gè)問題,本文我將緊緊圍繞這兩個(gè)問題,講解requestLayout背后的故事。
?「其一:鎖屏后,調(diào)用View.requestLayout(),會(huì)往上層層調(diào)用requestLayout()嗎?」
「其二:鎖屏后,調(diào)用View.requestLayout(),會(huì)觸發(fā)View的測(cè)量和布局操作嗎?」
?
postSyncBarrier我知道,Handler的同步屏障機(jī)制嘛,但是鎖屏之后為什么還要調(diào)用requestLayout()呢?于是我腦補(bǔ)了一個(gè)場(chǎng)景。
?假設(shè)在Activity onResume()中每隔一秒調(diào)用View.requestLayout(),但是在onStop()方法中沒有停止調(diào)用該方法。當(dāng)用戶鎖屏或者按Home鍵時(shí)。
?
我腦補(bǔ)的這個(gè)場(chǎng)景,用羅翔老師的話來講是 「“法律允許,但是不提倡”」。當(dāng)Activity不在前臺(tái)的時(shí)候,就應(yīng)該把requestLayout()方法停掉嘛,「我們知道的,這個(gè)方法會(huì)從調(diào)用的View一層一層往上調(diào)用直到ViewRootImpl.requestLayout()方法,然后會(huì)從上往下觸發(fā)View的測(cè)量和布局甚至繪制方法。非常之浪費(fèi)嘛!錯(cuò)誤非常之低級(jí)!但是果真如此嗎?(偷偷告訴大家,其實(shí)一直調(diào)用也沒關(guān)系,Google大神已經(jīng)考慮到了,不信且看后文)」
電競(jìng)主播蕪湖大司馬,有一句網(wǎng)絡(luò)流行語「你以為我在第一層,其實(shí)我在第十層」。下面我將用層級(jí)來表示對(duì)requestLayout方法的了解程度,層級(jí)越高,表示了解越深刻。
我喜歡用樹形圖來分析Android View源碼。上圖:
「假設(shè)調(diào)用I.requestLayout(),會(huì)觸發(fā)哪些View的requestLayout方法?」
「答:會(huì)依次觸發(fā)I.requestLayout() -> C.requestLayout() -> A.requestLayout() -> ...省略一些View -> ViewRootImpl.requestLayout()」
//View.java
public?void?requestLayout()?{
????//?1.?清除測(cè)量記錄
????if?(mMeasureCache?!=?null)?mMeasureCache.clear();
????//?2.?增加PFLAG_FORCE_LAYOUT給mPrivateFlags
????mPrivateFlags?|=?PFLAG_FORCE_LAYOUT;
????mPrivateFlags?|=?PFLAG_INVALIDATED;
????
????// 3. 如果mParent沒有調(diào)用過requestLayout,則調(diào)用之。換句話說,如果調(diào)用過,則不會(huì)繼續(xù)調(diào)用
????if?(mParent?!=?null?&&?!mParent.isLayoutRequested())?{
????????mParent.requestLayout();
????}
}
該方法作用如下:
- 「清除測(cè)量記錄」
- 「增加PFLAG_FORCE_LAYOUT給mPrivateFlags」
- 「如果mParent沒有調(diào)用過requestLayout,則調(diào)用之。換句話說,如果調(diào)用過,則不會(huì)繼續(xù)調(diào)用」
重點(diǎn)看下mParent.isLayoutRequested()方法,它在View.java中有具體實(shí)現(xiàn)
//View.java
?public?boolean?isLayoutRequested()?{
????return?(mPrivateFlags?&?PFLAG_FORCE_LAYOUT)?==?PFLAG_FORCE_LAYOUT;
}
如果mPrivateFlags增加了PFLAG_FORCE_LAYOUT標(biāo)志位,則認(rèn)為View已經(jīng)請(qǐng)求過布局。由前文可知,在requestLayout的第二步會(huì)增加該標(biāo)志位。「熟悉位操作的朋友就會(huì)知道,有增加操作就會(huì)有對(duì)應(yīng)的清除操作。」 經(jīng)過一番搜索,找到:
//View.java
public?void?layout(int?l,?int?t,?int?r,?int?b)?{?
??//?...?省略代碼
??//在View調(diào)用完layout方法,會(huì)將PFLAG_FORCE_LAYOUT標(biāo)志位清除掉
??mPrivateFlags?&=?~PFLAG_FORCE_LAYOUT;
??mPrivateFlags3?|=?PFLAG3_IS_LAID_OUT;
??//?...?省略代碼
}
「在View調(diào)用完layout方法,會(huì)將PFLAG_FORCE_LAYOUT標(biāo)志位清除掉。當(dāng)View下次再調(diào)用requestLayout方法時(shí),依舊能往上層層調(diào)用。但是如果當(dāng)layout()方法沒有執(zhí)行時(shí),下次再調(diào)用requestLayout方法時(shí),就不會(huì)往上層層調(diào)用了。」
回答文章中的第一個(gè)問題:
?「其一:鎖屏后,調(diào)用View.requestLayout(),會(huì)往上層層調(diào)用requestLayout()嗎?」
「答:鎖屏后,除了第一次調(diào)用會(huì)往上層層調(diào)用,其它的都不會(huì)」
?
?「為什么,只有第一次調(diào)用會(huì)呢?那必定是因?yàn)橹蟮膌ayout方法沒有得到執(zhí)行,導(dǎo)致PFLAG_FORCE_LAYOUT無法被清除。欲探究竟,接著往下看」
?
如果你知道requestLayout調(diào)用是一個(gè)層級(jí)調(diào)用,那么恭喜你,你已經(jīng)處于認(rèn)知的第一層了。送你一張二層入場(chǎng)券。
3. 第二層(ViewRootImpl.requestLayout)我們來看看第一層講到的ViewRootImpl.requestLayout()
//ViewRootImpl.java
@Override
public?void?requestLayout()?{
????if?(!mHandlingLayoutInLayoutRequest)?{
????????checkThread();
????????mLayoutRequested?=?true;
????????scheduleTraversals();
????}
}
void?scheduleTraversals()?{
????if?(!mTraversalScheduled)?{
????????mTraversalScheduled?=?true;
????????//1.?往主線程的Handler對(duì)應(yīng)的MessageQueue發(fā)送一個(gè)同步屏障消息
????????mTraversalBarrier?=?mHandler.getLooper().getQueue().postSyncBarrier();
????????//2.?將mTraversalRunnable保存到Choreographer中
????????mChoreographer.postCallback(
????????????????Choreographer.CALLBACK_TRAVERSAL,?mTraversalRunnable,?null);
????????if?(!mUnbufferedInputDispatch)?{
????????????scheduleConsumeBatchedInput();
????????}
????????notifyRendererOfFramePending();
????????pokeDrawLockIfNeeded();
????}
}
該方法主要作用如下:
- 「往主線程的Handler對(duì)應(yīng)的MessageQueue發(fā)送一個(gè)同步屏障消息」
- 「將mTraversalRunnable保存到Choreographer中」
此處有三個(gè)特別重要的知識(shí)點(diǎn):
- 「mTraversalRunnable」
- 「MessageQueue的同步屏障」
- 「Choreographer機(jī)制」
mTraversalRunnable相對(duì)比較簡(jiǎn)單,它的作用就是從ViewRootImpl 從上往下執(zhí)行performMeasure、performLayout、performDraw。「[重點(diǎn):敲黑板]它的執(zhí)行時(shí)機(jī)是當(dāng)VSync信號(hào)來到時(shí),會(huì)往主線程的Handler對(duì)應(yīng)的MessageQueue中發(fā)送一條異步消息,由于在scheduleTraversals()中給MessageQueue中發(fā)送過一條同步屏障消息,那么當(dāng)執(zhí)行到同步屏障消息時(shí),會(huì)將異步消息取出執(zhí)行」
4. 第三層(TraversalRunnable)當(dāng)VSync信號(hào)量到達(dá)時(shí),Choreographer會(huì)發(fā)送一個(gè)異步消息。當(dāng)異步消息執(zhí)行時(shí),會(huì)觸發(fā)ViewRootImpl.mTraversalRunnable回調(diào)。
final?class?TraversalRunnable?implements?Runnable?{
????@Override
????public?void?run()?{
????????doTraversal();
????}
}
void?doTraversal()?{
????if?(mTraversalScheduled)?{
????????mTraversalScheduled?=?false;
????????mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
????????if?(mProfile)?{
????????????Debug.startMethodTracing("ViewAncestor");
????????}
????????performTraversals();
????????if?(mProfile)?{
????????????Debug.stopMethodTracing();
????????????mProfile?=?false;
????????}
????}
}
它的作用:
- 「移除同步屏障」
- 「執(zhí)行performTraversals方法」
performTraversals()方法特別復(fù)雜,給出偽代碼如下
private?void?performTraversals()?{
????if?(!mStopped?||?mReportNextDraw)?{
??????performMeasure()
????}
????
???final?boolean?didLayout?=?layoutRequested?&&?(!mStopped?||?mReportNextDraw);
????if?(didLayout)?{
??????performLayout(lp,?mWidth,?mHeight);
????}
????
????boolean?cancelDraw?=?mAttachInfo.mTreeObserver.dispatchOnPreDraw()?||?!isViewVisible;
????if?(!cancelDraw?&&?!newSurface)?{
????????performDraw();
????}
}
該方法的作用:
- 「滿足條件的情況下調(diào)用performMeasure()」
- 「滿足條件的情況下調(diào)用performLayout()」
- 「滿足條件的情況下調(diào)用performDraw()」
mStopped表示Activity是否處于stopped狀態(tài)。如果Activity調(diào)用了onStop方法,performLayout方法是不會(huì)調(diào)用的。
//ViewRootImpl.java
private?void?performLayout(WindowManager.LayoutParams?lp,?int?desiredWindowWidth,
????????????int?desiredWindowHeight)?{
//?...?省略代碼
?host.layout(0,?0,?host.getMeasuredWidth(),?host.getMeasuredHeight());??
//?...?省略代碼
}
回答文章中第二個(gè)問題:
?「其二:鎖屏后,調(diào)用View.requestLayout(),會(huì)觸發(fā)View的測(cè)量和布局操作嗎?」
「答:不會(huì),因?yàn)楫?dāng)前Activity處于stopped狀態(tài)了」
?
「至此第一層里面留下的小懸念也得以解開,因?yàn)椴粫?huì)執(zhí)行View.layout()方法,所以PFLAG_FORCE_LAYOUT不會(huì)被清除,導(dǎo)致接下來的requestLayout方法不會(huì)層層往上調(diào)用。」
「至此本文的兩個(gè)問題都已經(jīng)得到了答案。」
當(dāng)我把問題提交給「鴻洋」大佬的wanandroid上時(shí),大佬又給我提了一個(gè)問題。
?鴻洋大佬:既然Activity的onStop會(huì)導(dǎo)致requestLayout layout方法得不到執(zhí)行,那么onResume方法會(huì)不會(huì)讓上一次的requestLayout沒有執(zhí)行的layout方法執(zhí)行一次呢?
?
于是我寫了個(gè)demo來驗(yàn)證,鎖屏后延時(shí)一秒亮屏。
//MyDemoActivity.kt
override?fun?onStop()?{
????super.onStop()
????root.postDelayed(object?:?Runnable?{
????????override?fun?run()?{
????????????root.requestLayout()
????????????println("ChoreographerActivity??reqeustLayout")
????????}
????},?1000)
}
在自定義布局的onLayout方法中打印日志
@Override
protected?void?onLayout(boolean?changed,?int?left,?int?top,?int?right,?int?bottom)?{
????System.out.println("ChoreographerActivity?onLayout");
????super.onLayout(changed,?left,?top,?right,?bottom);
}
鎖屏,日志沒有打印。亮屏,日志打印了。
所以
?鴻洋大佬:既然Activity的onStop會(huì)導(dǎo)致requestLayout layout方法得不到執(zhí)行,那么onResume方法會(huì)不會(huì)讓上一次的requestLayout沒有執(zhí)行的layout方法執(zhí)行一次呢?
我:經(jīng)過demo驗(yàn)證,會(huì)。原因且聽我道來
?
有了demo找原因就很簡(jiǎn)單了。正面不好攻破,那就祭出調(diào)試大法唄。但是斷點(diǎn)放在哪好呢?思考了一番。我覺得斷點(diǎn)放在發(fā)送同步屏障的地方比較好,ViewRootImpl.scheduleTraversals()。為什么斷點(diǎn)放這里?因?yàn)檫@里必經(jīng)之路。那你有可能會(huì)問:必經(jīng)之路不應(yīng)該是onLayout方法么?(那你就得了解同步屏障和VSync刷新機(jī)制了,后文會(huì)講)
亮屏后,發(fā)現(xiàn)斷點(diǎn)執(zhí)行了。從堆棧中可以看出Activity的performRestart()方法執(zhí)行了ViewRootImpl的scheduleTraversals方法。
雖然,亮屏的時(shí)候沒有執(zhí)行View.requestLayout方法,由于鎖屏后1s執(zhí)行了View.requestLayout方法,所以PFLAG_FORCE_LAYOUT標(biāo)記位還是有的。亮屏調(diào)用了performTraversals方法時(shí),會(huì)執(zhí)行Measure、Layout、Draw等操作。
「至此,完美回答了粉絲和鴻洋大佬的問題」
5. 第四層(Handler同步屏障)Handler原理是面試必問的問題。涉及到很多知識(shí)點(diǎn)。線程、Looper、MessageQueue、ThreadLocal、鏈表、底層等技術(shù)。本文我就不展開講了。即使對(duì)Handler不是很了解,也不影響本層次的學(xué)習(xí)。
?A同學(xué):同步屏障。感覺好高大上的樣子?能給我講講嗎?
我:乍一看,是挺高大上的。讓人望而生畏。但是細(xì)細(xì)一想,也不是那么難,說白了就是將Message分成三種不同類型
A同學(xué):此話怎講,愿聞其詳~
我:如下代碼應(yīng)該看得懂吧?
class?Message{
?int?mType;
//同步屏障消息
?public?static?final?int?SYNC_BARRIER?=?0;
//普通消息
?public?static?final?int?NORMAL?=?1;
//異步消息
?public?static?final?int?ASYNCHRONOUS?=?2;
}A同學(xué):這很簡(jiǎn)單呀,平時(shí)開發(fā)中經(jīng)常用不同的值表示不同的類型,但是android中的Message類并沒有這幾個(gè)不同的值呀?
我:Android Message 類確實(shí)沒有用不同的值來表示不同類型的Message。它是通過target和isAsynchronous()組合出三種不同類型的Message。
消息類型 target isAsynchronous() 同步屏障消息 null 無所謂 異步消息 不為null 返回true 普通消息 不為null 返回false A同學(xué):理解了,那么它們有什么區(qū)別呢?
我:世界上本來只有普通消息,但是因?yàn)槭虑橛休p重緩急,所以誕生了同步屏障消息和異步消息。它們兩是配套使用的。當(dāng)消息隊(duì)列中同時(shí)存在這三種消息時(shí),如果碰到了同步屏障消息,那么會(huì)優(yōu)先執(zhí)行異步消息。
A同學(xué):有點(diǎn)暈~
我:別急,且看如下圖解
?

- 「綠色表示普通消息,很守規(guī)矩,按照入隊(duì)順序依次出隊(duì)。」
- 「紅色表示異步消息,意味著它比較著急,有優(yōu)先執(zhí)行的權(quán)利。」
- 「黃色表示同步屏障消息,它的作用就是警示,后續(xù)只會(huì)讓異步消息出隊(duì),如果沒有異步消息,則會(huì)一直等待。」
上圖,消息隊(duì)列中全是普通消息。那么它們會(huì)按照順序,從隊(duì)首依次出隊(duì)列。msg1->msg2->msg3
上圖,三種類型消息全部存在,msg1是同步屏障消息。同步屏障消息并不會(huì)真正執(zhí)行,它也不會(huì)主動(dòng)出隊(duì)列,需要調(diào)用MessageQueue的removeSyncBarrier()方法。它的作用就是"警示",后續(xù)優(yōu)先讓紅色的消息出隊(duì)列。
msg3出隊(duì)列

msg5出隊(duì)列

此刻msg2并不會(huì)出隊(duì)列,隊(duì)列中已經(jīng)沒有了紅色消息,但是存在黃色消息,所以會(huì)一直等紅色消息,綠色消息得不到執(zhí)行機(jī)會(huì)

調(diào)用removeSyncBarrier()方法,將msg1出隊(duì)列

綠色消息按順序出隊(duì)
?postSyncBarrier()和removeSyncBarrier()必須成對(duì)出現(xiàn),否則會(huì)導(dǎo)致消息隊(duì)列出現(xiàn)假死情況。
?
同步屏障就介紹到這,如果意猶未盡的話,歡迎關(guān)注公眾號(hào),留言探討。
6. 第五層(Choreographer VSync機(jī)制)?B同學(xué):VSync機(jī)制感覺好高大上的樣子?能給我講講嗎
我:這個(gè)東西比較底層了,理解難度比較大,但是有一個(gè)比較取巧的理解方式。
B同學(xué):說來聽聽。
我:可以從觀察者模式角度來理解,VSync信號(hào)是由底層發(fā)出的。APP層會(huì)監(jiān)聽VSync的信號(hào),當(dāng)接收到信號(hào)時(shí),就會(huì)通過Choreographer向消息隊(duì)列發(fā)送異步消息,這個(gè)消息的作用之一就是通知ViewRootImpl去執(zhí)行測(cè)量,布局,繪制操作。
?
//Choreographer.java
private?final?class?FrameDisplayEventReceiver?extends?DisplayEventReceiver
????????????implements?Runnable?{
????private?boolean?mHavePendingVsync;
????private?long?mTimestampNanos;
????private?int?mFrame;
????@Override
????public?void?onVsync(long?timestampNanos,?int?builtInDisplayId,?int?frame)?{
????????
????????//...省略其他代碼
????????long?now?=?System.nanoTime();
????????if?(timestampNanos?>?now)?{
????????????Log.w(TAG,?"Frame?time?is?"?+?((timestampNanos?-?now)?*?0.000001f)
????????????????????+?"?ms?in?the?future!??Check?that?graphics?HAL?is?generating?vsync?"
????????????????????+?"timestamps?using?the?correct?timebase.");
????????????timestampNanos?=?now;
????????}
????????if?(mHavePendingVsync)?{
????????????Log.w(TAG,?"Already?have?a?pending?vsync?event.??There?should?only?be?"
????????????????????+?"one?at?a?time.");
????????}?else?{
????????????mHavePendingVsync?=?true;
????????}
????????mTimestampNanos?=?timestampNanos;
????????mFrame?=?frame;
????????Message?msg?=?Message.obtain(mHandler,?this);
????????msg.setAsynchronous(true);
????????mHandler.sendMessageAtTime(msg,?timestampNanos?/?TimeUtils.NANOS_PER_MS);
}
7. 第六層(繪制機(jī)制)ViewRootImpl和Choreographer是繪制機(jī)制的兩大主角。他們負(fù)責(zé)功能如下。具體的源代碼就不貼了,總結(jié)如下圖。
