首頁 > 軟體

onMeasure被執行兩次原理解析

2023-02-09 06:01:45

什麼情況下會onMeasure會執行?

進入Viewmeasure方法:

void measure(){
    boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
        || heightMeasureSpec != mOldHeightMeasureSpec;
    boolean isSepcExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
        && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
    boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
        && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
    final boolean needsLayout = specChanged
        && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
    if(forceLayout || needLayout){
        int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            long value = mMeasureCache.valueAt(cacheIndex);
            setMeasuredDimensionRaw((int) (value >> 32), (int) value);
            mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
   }
}

什麼時候forceLayout=true:

  • 呼叫requestLayout
  • 呼叫forceRequestLayout

什麼時候needsLayout=true:

  • 當長寬發生改變

什麼時候呼叫了onMeasure>方法:

  • forceLayouy=true
  • 或者mMeasureCache沒有當前的快取

總結:

當呼叫了requestLayout一定會測發重測過程.當forceLayout=false的時候會去判斷mMeasureCache值.現在研究下這個mMeasureCache

class View{
    LongSparseLongArray mMeasureCache;
    void measure(widthSpec,heightSpec){
        ---
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
        if(cacheIndex<0){
            onMeasure(widthSpec,heightSpec);
        }
        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
        mMeasureCache.put(key,widhSpec|heightSpec);
        ---
    }
}

這裡可以看到oldWidthMeasureSpecmMeasureCache都是快取上一次的值,那他們有什麼不同呢?不同點就是,oldWidthMeasureSpec>不僅僅快取了測量的spec模式而且快取了size.但是mMeasureCache只快取了size.從這行程式碼可以看出:

long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;

這裡一同運算就為了排除掉spec造成的影響.

//不信你可以試下下面的程式碼
public class Test {
    public static void main(String[] args) {
        long widthMeasureSpec = makeMeasureSpec(10,0);
        long heightMeasureSpec =  makeMeasureSpec(20,0);
        long ss = widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        System.out.println("=========="+ss);
    }
    private static final int MODE_MASK = 0x3 << 30;
    public static int makeMeasureSpec(int size,
                                      int mode) {
        return (size & ~MODE_MASK) | (mode & MODE_MASK);
    }
}
//42949672980
//42949672980
//42949672980

什麼時候mPrivateFlags會被賦值PFLAG_FORCE_LAYOUT.

view viewGrouup的建構函式裡面會主動賦值一次,然後在ViewGroup.addView時候會給當前ViewmProvateFlags賦值PFLAG_FORCE_LAYOUT.

為什麼onMeasure會被執行兩次?

void measure(int widthMeasureSpec,int heightMeasureSpec){
    ----
    boolean forceLayout = (mPrivateFlags &amp; PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; 
    if(forceLayout | needsLayout){
        onMeasure()
    }
    ----
}
public void layout(int l, int t, int r, int b){
    ---
    mPrivateFlags &amp;= ~PFLAG_FORCE_LAYOUT;
    ---
}

在第一次觸發到measure方法時,forceLayoyt=true needsLayout=true,但是layout方法還沒觸發到.
在第二次觸發到measure>方法時,forceLayout=true needsLayout=false,所以還是會進入onMeasure方法.這次會執行layout方法.然後我們在下次的時候forceLayout就等於false了.上面的這一段分析是分析的measure內部如何防止多次呼叫onMeasure.

分析外部是如何多次呼叫measure方法的

Activity執行到onResume生命週期的時候,會執行WindowManager.addView操作,WindowManager的具體實現類是WindowManagerImpl然後addView操作交給了代理類WindowManagerGlobal,然後在WindowManagerGlobaladdView裡面執行了ViewRootImpl.setView操作(ViewRootImpl物件也是在這個時候建立的),在ViewRootImpl會主動呼叫一次requestLayout,也就開啟了第一次的檢視 測量 佈局 繪製.

setView的時候主動呼叫了一次ViewRootImpl.requestLayout,注意這個requestLayoutViewRootImpl的內部方法,和view viewGroup那些requestLayout不一樣.在ViewRootImpl.requestLayout內部呼叫了performTraversals方法:

class ViewRootImpl{
    void performTraversals(){
        if(layoutResuested){
        //標記1
            windowSizeMayChanged |= measureHierarchy(host,lp,res,desiredWindowWidth,desiredWindowHeight);
        }
        //標記2
        performMeasure()
        performLayout()
    }
    void measureHierarchy(){
        performMeasure()
    }
}

ViewRootImpl的執行邏輯你可以看出,在執行performLayout之前,他自己就已經呼叫了兩次performMeasure方法.所以你現在就知道為啥了.

以上就是onMeasure被執行兩次原理解析的詳細內容,更多關於onMeasure被執行兩次的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com