<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          requestLayout竟然涉及到這么多知識(shí)點(diǎn)

          共 9142字,需瀏覽 19分鐘

           ·

          2021-04-24 23:52

          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源碼。上圖:8e0c3daa11bd2fced2d6358f47728351.webp

          2. 第一層(往上,層層遍歷)

          「假設(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();
          ????}
          }

          該方法作用如下:

          1. 「清除測(cè)量記錄」
          2. 「增加PFLAG_FORCE_LAYOUT給mPrivateFlags」
          3. 「如果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();
          ????}
          }

          該方法主要作用如下:

          1. 「往主線程的Handler對(duì)應(yīng)的MessageQueue發(fā)送一個(gè)同步屏障消息」
          2. 「將mTraversalRunnable保存到Choreographer中」

          此處有三個(gè)特別重要的知識(shí)點(diǎn):

          1. 「mTraversalRunnable」
          2. 「MessageQueue的同步屏障」
          3. 「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;
          ????????}
          ????}
          }

          它的作用:

          1. 「移除同步屏障」
          2. 「執(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();
          ????}
          }

          該方法的作用:

          1. 「滿足條件的情況下調(diào)用performMeasure()」
          2. 「滿足條件的情況下調(diào)用performLayout()」
          3. 「滿足條件的情況下調(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ì)講)b48e67471f397a3ca226f4fc2f2758d8.webp

          亮屏后,發(fā)現(xiàn)斷點(diǎn)執(zhí)行了。從堆棧中可以看出Activity的performRestart()方法執(zhí)行了ViewRootImpl的scheduleTraversals方法。3f86f9b1a6c7406973759d03a6a4fb67.webp雖然,亮屏的時(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。

          消息類型targetisAsynchronous()
          同步屏障消息null無所謂
          異步消息不為null返回true
          普通消息不為null返回false

          A同學(xué):理解了,那么它們有什么區(qū)別呢?

          我:世界上本來只有普通消息,但是因?yàn)槭虑橛休p重緩急,所以誕生了同步屏障消息和異步消息。它們兩是配套使用的。當(dāng)消息隊(duì)列中同時(shí)存在這三種消息時(shí),如果碰到了同步屏障消息,那么會(huì)優(yōu)先執(zhí)行異步消息。

          A同學(xué):有點(diǎn)暈~

          我:別急,且看如下圖解

          ?
          1880b1b39fe87cfc6b4996643d23d7ec.webp
          1. 「綠色表示普通消息,很守規(guī)矩,按照入隊(duì)順序依次出隊(duì)。」
          2. 「紅色表示異步消息,意味著它比較著急,有優(yōu)先執(zhí)行的權(quán)利。」
          3. 「黃色表示同步屏障消息,它的作用就是警示,后續(xù)只會(huì)讓異步消息出隊(duì),如果沒有異步消息,則會(huì)一直等待。」

          86c0d916f39c3d409379f452a12b9332.webp上圖,消息隊(duì)列中全是普通消息。那么它們會(huì)按照順序,從隊(duì)首依次出隊(duì)列。msg1->msg2->msg3

          22041fca96f723bd4cdd9f464c316838.webp上圖,三種類型消息全部存在,msg1是同步屏障消息。同步屏障消息并不會(huì)真正執(zhí)行,它也不會(huì)主動(dòng)出隊(duì)列,需要調(diào)用MessageQueue的removeSyncBarrier()方法。它的作用就是"警示",后續(xù)優(yōu)先讓紅色的消息出隊(duì)列。

          1. msg3出隊(duì)列26805fa297d447799fed77fe5e9e80e5.webp

          2. msg5出隊(duì)列1780073f30e1943283f509d1d66a6e9f.webp

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

          4. 調(diào)用removeSyncBarrier()方法,將msg1出隊(duì)列73ce0e85afdcfd9ee079f8ad2ee9ad20.webp

          5. 綠色消息按順序出隊(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é)如下圖。fdb27305fe851ae1a828a97fb6b520c1.webp

          瀏覽 59
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天看毛片 | 天天日AV片 | 国产嫩苞又嫩又紧AV在线 | 亚洲av免费在线看 | AV无码国产 |