view.requestLayout源碼解析

1. 根view

view的requestLayout

public void requestLayout() {
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
}

從activity的setContentView中可以看出滋恬,activity的根view是DecorView获列,setContentView的view也是add到id為ID_ANDROID_CONTENT的FrameLayout下毕荐。

在ActivityThread中啟動(dòng)activity時(shí)籍铁,會(huì)調(diào)用handleResumeActivity方法堡距,將DecorView 添加到ViewRootImpl中,DecorView的父類是ViewRootImpl媚送。

final Activity a = r.activity;
View decor = r.window.getDecorView();
ViewManager wm = a.getWindowManager();
wm.addView(decor, l);

WindowManagerImpl 是 wm 的實(shí)現(xiàn)類中燥,wm.addView最終調(diào)用了WindowManagerGlobal 的addView

 public void addView(View view, ViewGroup.LayoutParams params,
    Display display, Window parentWindow) {
    ...
    root = new ViewRootImpl(view.getContext(), display);
    ...
    root.setView(view, wparams, panelParentView);//由它去設(shè)置view
...
}

在ViewRootImpl#setView () 會(huì)通過跨進(jìn)程的方式向 WindowManagerService 發(fā)起一個(gè)調(diào)用,將 DecorView 最終添加到 Window 上塘偎。

所以疗涉,調(diào)用父類的requestLayout拿霉,最終會(huì)調(diào)用到根view DecorView,而DecorView的父類是ViewRootImpl咱扣。

2. ViewRootImpl

子view的requestLayout最終還是ViewRootImpl的requestLayout被調(diào)用了绽淘,現(xiàn)在來看requestLayout到底做了什么。

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();  //檢測(cè)線程
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

檢查當(dāng)前線程是否為主線程闹伪,再調(diào)用scheduleTraversals

void scheduleTraversals() {
    //1沪铭、注意這個(gè)標(biāo)志位,多次調(diào)用 requestLayout偏瓤,要這個(gè)標(biāo)志位false才有效
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 2. 同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 3. 向 Choreographer 提交一個(gè)任務(wù)
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        //繪制前發(fā)一個(gè)通知
        notifyRendererOfFramePending();
        //這個(gè)是釋放鎖伦意,先不管
         pokeDrawLockIfNeeded();
    }
}

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        //遍歷繪制的開端
        doTraversal();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  1. 這里先防止多次requestLayout的調(diào)用
  2. 再通過同步屏障停止同步消息,優(yōu)先執(zhí)行異步消息
  3. 向 Choreographer 提交一個(gè)任務(wù)硼补,mTraversalRunnable則是要執(zhí)行繪制的回調(diào)

下面先介紹下同步屏障和mTraversalRunnable的執(zhí)行時(shí)機(jī)驮肉。

2.1 同步屏障

在ViewRootImpl中,主要是通過獲取Message設(shè)置setAsynchronous和調(diào)用MessageQueen的postSyncBarrier()來設(shè)置異步任務(wù)已骇,在MessageQueue的next()方法中离钝,如果設(shè)置了同步屏障,則跳過同步任務(wù)褪储,只執(zhí)行異步任務(wù)卵渴。

//MessageQueue
Message next() {
    for (;;) {
        synchronized (this) {
            Message prevMsg = null;
            Message msg = mMessages;
            //同步屏障,通過target和isAsynchronous來實(shí)現(xiàn)
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            
        }
    }
}

Looper的loop() 中鲤竹,會(huì)調(diào)用MessageQueue的next()來遍歷獲取消息浪读,而next()方法中,當(dāng)msg.target == null且msg.isAsynchronous()為true的Message才會(huì)被獲取到去執(zhí)行辛藻。

  • msg.target == null這個(gè)條件是通過mHandler.getLooper().getQueue().postSyncBarrier()發(fā)送了個(gè)target為空的消息來實(shí)現(xiàn)同步屏障碘橘。
  • msg.isAsynchronous()為true,通過過獲取Message設(shè)置setAsynchronous(true)

二者缺一不可吱肌,少了其中一個(gè)就是無意義的了痘拆。 只有同步任務(wù)沒有異步任務(wù)去設(shè)置同步屏障,或有異步任務(wù)但不設(shè)置同步屏障氮墨,都是沒有意義的纺蛆。

2.2 Choreographer

mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

/**
 * Posts a callback to run on the next frame.
 * <p>
 * The callback runs once then is automatically removed.
 * </p>
 *
 * @param callbackType The callback type.
 * @param action The callback action to run during the next frame.
 * @param token The callback token, or null if none.
 *
 * @see #removeCallbacks
 * @hide
 */
public void postCallback(int callbackType, Runnable action, Object token) {
    postCallbackDelayed(callbackType, action, token, 0);
}

注釋第一句話說的很清楚了,將回調(diào)放在下一幀調(diào)用规揪。接下來簡(jiǎn)單看看具體如何實(shí)現(xiàn)下一幀調(diào)用回調(diào)桥氏。

 private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        //添加任務(wù)到隊(duì)列
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        //上面的postCallback過來,延遲是0
        if (dueTime <= now) {
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}

將任務(wù)加入隊(duì)列mCallbackQueues中猛铅,再執(zhí)行scheduleFrameLocked方法

private void scheduleFrameLocked(long now) {
    if (isRunningOnLooperThreadLocked()) {
        scheduleVsyncLocked();
    } else {
        Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtFrontOfQueue(msg);
    }
}

private void scheduleVsyncLocked() {
    mDisplayEventReceiver.scheduleVsync();
}

字支。如果運(yùn)行在Looper線程上,則立即調(diào)度vsync;否則祥款,通過Handler發(fā)一個(gè)異步消息到消息隊(duì)列,最終也是到主線程處理月杉,這個(gè)消息時(shí)異步消息刃跛,在先前同步屏障的作用下,會(huì)優(yōu)先執(zhí)行苛萎。

mDisplayEventReceiver = USE_VSYNC
    ? new FrameDisplayEventReceiver(looper, vsyncSource)
    : null;

FrameDisplayEventReceiver是繼承DisplayEventReceiver

/**
 * Schedules a single vertical sync pulse to be delivered when the next
 * display frame begins.
 */
public void scheduleVsync() {
    if (mReceiverPtr == 0) {
        Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                + "receiver has already been disposed.");
    } else {
        nativeScheduleVsync(mReceiverPtr);
    }
}

// Called from native code.
@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
    onVsync(timestampNanos, builtInDisplayId, frame);
}

還是看注釋桨昙,scheduleVsync方法是調(diào)用JNI方法nativeScheduleVsync請(qǐng)求vsync信號(hào),在下個(gè)vsync信號(hào)來的時(shí)候調(diào)用回調(diào)方法dispatchVsync腌歉。

onVsync則是FrameDisplayEventReceiver中實(shí)現(xiàn)的

 @Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
    mTimestampNanos = timestampNanos;
    mFrame = frame;
    Message msg = Message.obtain(mHandler, this);
    msg.setAsynchronous(true);
    mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
    mHavePendingVsync = false;
    doFrame(mTimestampNanos, mFrame);
}

onVsync發(fā)送了個(gè)異步消息蛙酪,消息傳了個(gè)callback Runnable用于回調(diào),回調(diào)中調(diào)用了doFrame方法翘盖。

doFrame方法會(huì)調(diào)用doCallbacks桂塞,根據(jù)回調(diào)類型從mCallbackQueues取出CallbackRecord,如果回調(diào)類型是Choreographer.CALLBACK_TRAVERSAL馍驯,則執(zhí)行回調(diào)任阁危,也就是ViewRootImpl的mTraversalRunnable

void doFrame(long frameTimeNanos, int frame) {
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
    
        mFrameInfo.markInputHandlingStart();
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
    
        mFrameInfo.markAnimationsStart();
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
    
        mFrameInfo.markPerformTraversalsStart();
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
    
        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    } finally {
        AnimationUtils.unlockAnimationClock();
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}
void doCallbacks(int callbackType, long frameTimeNanos) {
    //1.根據(jù)獲取任務(wù)
    callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
    //2.最后一個(gè)類型回調(diào)汰瘫,提交這一幀的時(shí)間
     if (callbackType == Choreographer.CALLBACK_COMMIT) {
        final long jitterNanos = now - frameTimeNanos;
        Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
        if (jitterNanos >= 2 * mFrameIntervalNanos) {
            final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
                    + mFrameIntervalNanos;
            if (DEBUG_JANK) {
                Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
                        + " ms which is more than twice the frame interval of "
                        + (mFrameIntervalNanos * 0.000001f) + " ms!  "
                        + "Setting frame time to " + (lastFrameOffset * 0.000001f)
                        + " ms in the past.");
                mDebugPrintNextFrameTimeDelta = true;
            }
            frameTimeNanos = now - lastFrameOffset;
            mLastFrameTimeNanos = frameTimeNanos;
        }
    }
    //3.執(zhí)行回調(diào)任務(wù)
    for (CallbackRecord c = callbacks; c != null; c = c.next) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "RunCallback: type=" + callbackType
                    + ", action=" + c.action + ", token=" + c.token
                    + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
        }
        c.run(frameTimeNanos);
    }
}
image

圖中 doCallbacks 從 frameTimeNanos2 開始執(zhí)行狂打,執(zhí)行到進(jìn)入 CALLBACK_COMMIT 時(shí),經(jīng)過了2.2幀混弥。

mTraversalRunnable執(zhí)行就是在draw這步趴乡。

2.3 doTraversal

又回到了ViewRootImpl中,上面介紹了TraversalRunnable這個(gè)方法實(shí)際調(diào)用的是doTraversal()蝗拿。

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        //移除同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        //執(zhí)行繪制
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

同步屏障是為了給Choreographer中的異步消息讓步晾捏,當(dāng)回調(diào)到這里時(shí),可以關(guān)閉同步屏障哀托,執(zhí)行后續(xù)的方法performTraversals了粟瞬。

private void performTraversals() {
    //mView就是setContentView的view
    final View host = mView;
    
    // mAttachInfo 賦值給View
    host.dispatchAttachedToWindow(mAttachInfo, 0);
    
    if (!mStopped || mReportNextDraw) {
        boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
        if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                updatedConfiguration) {
            //開始第一次測(cè)量大小
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);    
                    
             //如果有設(shè)置權(quán)重,比如LinearLayout設(shè)置了weight萤捆,需要測(cè)量?jī)纱?            if (measureAgain) {
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
        
    }
    //布局
     performLayout(lp, mWidth, mHeight);
     
     if (triggerGlobalLayoutListener) {
        mAttachInfo.mRecomputeGlobalAttributes = false;
        //調(diào)用dispatchOnGlobalLayout
        mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
    }
    
    //繪制
    performDraw();
}

分別調(diào)用了performMeasure裙品,performLayout和performDraw,這三個(gè)方法開始遍歷子view進(jìn)行測(cè)量俗或,布局和繪制市怎。

 private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        //開始測(cè)量
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

如何實(shí)現(xiàn)測(cè)量,可以看看Measure要知道的點(diǎn)

總結(jié)

View.requestLayout -> ViewRootImpl.requestLayout -> ViewRootImpl.scheduleTraversals -> Choreographer.postCallback -> DisplayEventReceiver.scheduleVsync() -> Choreographer.doFrame -> Choreographer.doCallbacks ->ViewRootImpl.doTraversal -> ViewRootImpl.performTraversals

從子view的requestLayout辛慰,到ViewRootImpl的requestLayout区匠,在給Choreographer添加任務(wù),Choreographer通過DisplayEventReceiver來監(jiān)聽vsync信號(hào),回調(diào)執(zhí)行ViewRootImpl的doTraversal開始進(jìn)行繪制驰弄。

參考

ViewRootImpl 和 DecorView 分析

Handler麻汰,MessageQueue,Looper戚篙,你所不知道的Asynchronous

面試官又來了:你的app卡頓過嗎五鲫?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市岔擂,隨后出現(xiàn)的幾起案子位喂,更是在濱河造成了極大的恐慌,老刑警劉巖乱灵,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件塑崖,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡痛倚,警方通過查閱死者的電腦和手機(jī)规婆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蝉稳,“玉大人聋呢,你說我怎么就攤上這事〉咔” “怎么了削锰?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)毕莱。 經(jīng)常有香客問我器贩,道長(zhǎng),這世上最難降的妖魔是什么朋截? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任蛹稍,我火速辦了婚禮,結(jié)果婚禮上部服,老公的妹妹穿的比我還像新娘唆姐。我一直安慰自己,他們只是感情好廓八,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布奉芦。 她就那樣靜靜地躺著,像睡著了一般剧蹂。 火紅的嫁衣襯著肌膚如雪声功。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天宠叼,我揣著相機(jī)與錄音先巴,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛伸蚯,可吹牛的內(nèi)容都是我干的摩渺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼剂邮,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼摇幻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抗斤,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤囚企,失蹤者是張志新(化名)和其女友劉穎丈咐,沒想到半個(gè)月后瑞眼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡棵逊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年伤疙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辆影。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡徒像,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蛙讥,到底是詐尸還是另有隱情锯蛀,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布次慢,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏败玉。R本人自食惡果不足惜茄蚯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望闻妓。 院中可真熱鬧菌羽,春花似錦、人聲如沸由缆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽均唉。三九已至氓轰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浸卦,已是汗流浹背署鸡。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人靴庆。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓时捌,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親炉抒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奢讨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

推薦閱讀更多精彩內(nèi)容

  • ??前面寫了一篇文章專門的分析了View的measure拿诸、layout和draw三大流程,在那篇文章中塞茅,多次提到了...
    瓊珶和予閱讀 4,076評(píng)論 8 18
  • 上一篇文章面試官:說說多線程并發(fā)問題閱讀量和點(diǎn)贊數(shù)超出我的想象亩码,這周帶來這個(gè)系列第二篇。 故事開始 面試官:平時(shí)開...
    藍(lán)師傅_Android閱讀 3,922評(píng)論 2 27
  • 1野瘦、概述 不論電腦描沟,電視,手機(jī)鞭光,我們看到的畫面都是由一幀幀的畫面組成的吏廉。FPS是圖像領(lǐng)域中的定義,是指畫面每秒傳輸...
    高丕基閱讀 12,067評(píng)論 6 34
  • “奶奶佩伤,我真誠(chéng)地告訴你——我要玩游戲十五分鐘,請(qǐng)你答應(yīng)我卦睹,好嗎畦戒?”當(dāng)我聽到這句話的時(shí)候,真的是感動(dòng)得熱淚盈眶结序,連忙...
    小圣奶奶閱讀 330評(píng)論 1 4
  • 公司:揚(yáng)州市方圓建筑工程有限公司 姓名:季龍【日精進(jìn)打卡第129天】 【知~學(xué)習(xí)】 《六項(xiàng)精進(jìn)》3遍共383遍 《...
    86b795cb8e88閱讀 112評(píng)論 0 0