1. 程式人生 > >如何優化你的佈局層級結構之RelativeLayout和LinearLayout及FrameLayout效能分析

如何優化你的佈局層級結構之RelativeLayout和LinearLayout及FrameLayout效能分析

工作一段時間後,經常會被領導說,你這個進入速度太慢了,競品的進入速度很快,你搞下優化吧?每當這時,你會怎麼辦?功能實現都有啊,進入時要載入那麼多view,這也沒辦法啊,等等。

先看一些現象吧:用Android studio,新建一個Activity自動生成的佈局檔案都是RelativeLayout,或許你會認為這是IDE的預設設定問題,其實不然,這是由 android-sdk\tools\templates\activities\EmptyActivity\root\res\layout\activity_simple.xml.ftl 這個檔案事先就定好了的,也就是說這是Google的選擇,而非IDE的選擇。那SDK為什麼會預設給開發者新建一個預設的RelativeLayout佈局呢?當然是因為RelativeLayout的效能更優,效能至上嘛。但是我們再看看預設新建的這個RelativeLayout的父容器,也就是當前視窗的頂級View——DecorView,它卻是個垂直方向的LinearLayout,上面是標題欄,下面是內容欄。那麼問題來了,Google為什麼給開發者預設新建了個RelativeLayout,而自己卻偷偷用了個LinearLayout,到底誰的效能更高,開發者該怎麼選擇呢?

View的一些基本工作原理

先通過幾個問題,簡單的瞭解寫android中View的工作原理吧。

View是什麼?

簡單來說,View是Android系統在螢幕上的視覺呈現,也就是說你在手機螢幕上看到的東西都是View。

View是怎麼繪製出來的?

View的繪製流程是從ViewRoot的performTraversals()方法開始,依次經過measure(),layout()和draw()三個過程才最終將一個View繪製出來。

View是怎麼呈現在介面上的?

Android中的檢視都是通過Window來呈現的,不管Activity、Dialog還是Toast它們都有一個Window,然後通過WindowManager來管理View。Window和頂級View——DecorView的通訊是依賴ViewRoot完成的。

View和ViewGroup什麼區別?

不管簡單的Button和TextView還是複雜的RelativeLayout和ListView,他們的共同基類都是View。所以說,View是一種介面層控制元件的抽象,他代表了一個控制元件。那ViewGroup是什麼東西,它可以被翻譯成控制元件組,即一組View。ViewGroup也是繼承View,這就意味著View本身可以是單個控制元件,也可以是多個控制元件組成的控制元件組。根據這個理論,Button顯然是個View,而RelativeLayout不但是一個View還可以是一個ViewGroup,而ViewGroup內部是可以有子View的,這個子View同樣也可能是ViewGroup,以此類推。

RelativeLayout和LinearLayout效能PK

基於以上原理和大背景,我們要探討的效能問題,說的簡單明瞭一點就是:當RelativeLayout和LinearLayout分別作為ViewGroup,表達相同佈局時繪製在螢幕上時誰更快一點。上面已經簡單說了View的繪製,從ViewRoot的performTraversals()方法開始依次呼叫perfromMeasure、performLayout和performDraw這三個方法。這三個方法分別完成頂級View的measure、layout和draw三大流程,其中perfromMeasure會呼叫measure,measure又會呼叫onMeasure,在onMeasure方法中則會對所有子元素進行measure,這個時候measure流程就從父容器傳遞到子元素中了,這樣就完成了一次measure過程,接著子元素會重複父容器的measure,如此反覆就完成了整個View樹的遍歷。同理,performLayout和performDraw也分別完成perfromMeasure類似的流程。通過這三大流程,分別遍歷整棵View樹,就實現了Measure,Layout,Draw這一過程,View就繪製出來了。那麼我們就分別來追蹤下RelativeLayout和LinearLayout這三大流程的執行耗時。
如下圖,我們分別用兩用種方式簡單的實現佈局測試下


LinearLayout

Measure:0.762ms
Layout:0.167ms
draw:7.665ms

RelativeLayout

Measure:2.180ms
Layout:0.156ms
draw:7.694ms
從這個資料來看無論使用RelativeLayout還是LinearLayout,layout和draw的過程兩者相差無幾,考慮到誤差的問題,幾乎可以認為兩者不分伯仲,關鍵是Measure的過程RelativeLayout卻比LinearLayout慢了一大截。

Measure都幹什麼了

RelativeLayout的onMeasure()方法
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mDirtyHierarchy) {
            mDirtyHierarchy = false;
            sortChildren();
        }

        int myWidth = -1;
        int myHeight = -1;

        int width = 0;
        int height = 0;

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        // Record our dimensions if they are known;
        if (widthMode != MeasureSpec.UNSPECIFIED) {
            myWidth = widthSize;
        }

        if (heightMode != MeasureSpec.UNSPECIFIED) {
            myHeight = heightSize;
        }

        if (widthMode == MeasureSpec.EXACTLY) {
            width = myWidth;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = myHeight;
        }

        View ignore = null;
        int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
        final boolean horizontalGravity = gravity != Gravity.START && gravity != 0;
        gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0;

        int left = Integer.MAX_VALUE;
        int top = Integer.MAX_VALUE;
        int right = Integer.MIN_VALUE;
        int bottom = Integer.MIN_VALUE;

        boolean offsetHorizontalAxis = false;
        boolean offsetVerticalAxis = false;

        if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) {
            ignore = findViewById(mIgnoreGravity);
        }

        final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY;
        final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY;

        // We need to know our size for doing the correct computation of children positioning in RTL
        // mode but there is no practical way to get it instead of running the code below.
        // So, instead of running the code twice, we just set the width to a "default display width"
        // before the computation and then, as a last pass, we will update their real position with
        // an offset equals to "DEFAULT_WIDTH - width".
        final int layoutDirection = getLayoutDirection();
        if (isLayoutRtl() && myWidth == -1) {
            myWidth = DEFAULT_WIDTH;
        }

        View[] views = mSortedHorizontalChildren;
        int count = views.length;

        for (int i = 0; i < count; i++) {
            View child = views[i];
            if (child.getVisibility() != GONE) {
                LayoutParams params = (LayoutParams) child.getLayoutParams();
                int[] rules = params.getRules(layoutDirection);

                applyHorizontalSizeRules(params, myWidth, rules);
                measureChildHorizontal(child, params, myWidth, myHeight);

                if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
                    offsetHorizontalAxis = true;
                }
            }
        }

        views = mSortedVerticalChildren;
        count = views.length;
        final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;

        for (int i = 0; i < count; i++) {
            final View child = views[i];
            if (child.getVisibility() != GONE) {
                final LayoutParams params = (LayoutParams) child.getLayoutParams();

                applyVerticalSizeRules(params, myHeight, child.getBaseline());
                measureChild(child, params, myWidth, myHeight);
                if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
                    offsetVerticalAxis = true;
                }

                if (isWrapContentWidth) {
                    if (isLayoutRtl()) {
                        if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
                            width = Math.max(width, myWidth - params.mLeft);
                        } else {
                            width = Math.max(width, myWidth - params.mLeft - params.leftMargin);
                        }
                    } else {
                        if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
                            width = Math.max(width, params.mRight);
                        } else {
                            width = Math.max(width, params.mRight + params.rightMargin);
                        }
                    }
                }

                if (isWrapContentHeight) {
                    if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
                        height = Math.max(height, params.mBottom);
                    } else {
                        height = Math.max(height, params.mBottom + params.bottomMargin);
                    }
                }

                if (child != ignore || verticalGravity) {
                    left = Math.min(left, params.mLeft - params.leftMargin);
                    top = Math.min(top, params.mTop - params.topMargin);
                }

                if (child != ignore || horizontalGravity) {
                    right = Math.max(right, params.mRight + params.rightMargin);
                    bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
                }
            }
        }

        // Use the top-start-most laid out view as the baseline. RTL offsets are
        // applied later, so we can use the left-most edge as the starting edge.
        View baselineView = null;
        LayoutParams baselineParams = null;
        for (int i = 0; i < count; i++) {
            final View child = views[i];
            if (child.getVisibility() != GONE) {
                final LayoutParams childParams = (LayoutParams) child.getLayoutParams();
                if (baselineView == null || baselineParams == null
                        || compareLayoutPosition(childParams, baselineParams) < 0) {
                    baselineView = child;
                    baselineParams = childParams;
                }
            }
        }
        mBaselineView = baselineView;

        if (isWrapContentWidth) {
            // Width already has left padding in it since it was calculated by looking at
            // the right of each child view
            width += mPaddingRight;

            if (mLayoutParams != null && mLayoutParams.width >= 0) {
                width = Math.max(width, mLayoutParams.width);
            }

            width = Math.max(width, getSuggestedMinimumWidth());
            width = resolveSize(width, widthMeasureSpec);

            if (offsetHorizontalAxis) {
                for (int i = 0; i < count; i++) {
                    final View child = views[i];
                    if (child.getVisibility() != GONE) {
                        final LayoutParams params = (LayoutParams) child.getLayoutParams();
                        final int[] rules = params.getRules(layoutDirection);
                        if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
                            centerHorizontal(child, params, width);
                        } else if (rules[ALIGN_PARENT_RIGHT] != 0) {
                            final int childWidth = child.getMeasuredWidth();
                            params.mLeft = width - mPaddingRight - childWidth;
                            params.mRight = params.mLeft + childWidth;
                        }
                    }
                }
            }
        }

        if (isWrapContentHeight) {
            // Height already has top padding in it since it was calculated by looking at
            // the bottom of each child view
            height += mPaddingBottom;

            if (mLayoutParams != null && mLayoutParams.height >= 0) {
                height = Math.max(height, mLayoutParams.height);
            }

            height = Math.max(height, getSuggestedMinimumHeight());
            height = resolveSize(height, heightMeasureSpec);

            if (offsetVerticalAxis) {
                for (int i = 0; i < count; i++) {
                    final View child = views[i];
                    if (child.getVisibility() != GONE) {
                        final LayoutParams params = (LayoutParams) child.getLayoutParams();
                        final int[] rules = params.getRules(layoutDirection);
                        if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
                            centerVertical(child, params, height);
                        } else if (rules[ALIGN_PARENT_BOTTOM] != 0) {
                            final int childHeight = child.getMeasuredHeight();
                            params.mTop = height - mPaddingBottom - childHeight;
                            params.mBottom = params.mTop + childHeight;
                        }
                    }
                }
            }
        }

        if (horizontalGravity || verticalGravity) {
            final Rect selfBounds = mSelfBounds;
            selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight,
                    height - mPaddingBottom);

            final Rect contentBounds = mContentBounds;
            Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds,
                    layoutDirection);

            final int horizontalOffset = contentBounds.left - left;
            final int verticalOffset = contentBounds.top - top;
            if (horizontalOffset != 0 || verticalOffset != 0) {
                for (int i = 0; i < count; i++) {
                    final View child = views[i];
                    if (child.getVisibility() != GONE && child != ignore) {
                        final LayoutParams params = (LayoutParams) child.getLayoutParams();
                        if (horizontalGravity) {
                            params.mLeft += horizontalOffset;
                            params.mRight += horizontalOffset;
                        }
                        if (verticalGravity) {
                            params.mTop += verticalOffset;
                            params.mBottom += verticalOffset;
                        }
                    }
                }
            }
        }

        if (isLayoutRtl()) {
            final int offsetWidth = myWidth - width;
            for (int i = 0; i < count; i++) {
                final View child = views[i];
                if (child.getVisibility() != GONE) {
                    final LayoutParams params = (LayoutParams) child.getLayoutParams();
                    params.mLeft -= offsetWidth;
                    params.mRight -= offsetWidth;
                }
            }
        }

        setMeasuredDimension(width, height);
    }


根據原始碼我們發現RelativeLayout會對子View做兩次measure。這是為什麼呢?首先RelativeLayout中子View的排列方式是基於彼此的依賴關係,而這個依賴關係可能和佈局中View的順序並不相同,在確定每個子View的位置的時候,就需要先給所有的子View排序一下。又因為RelativeLayout允許A,B 2個子View,橫向上B依賴A,縱向上A依賴B。所以需要橫向縱向分別進行一次排序測量。

LinearLayout的onMeasure()方法
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
      measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
      measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
  }

與RelativeLayout相比LinearLayout的measure就簡單明瞭的多了,先判斷線性規則,然後執行對應方向上的測量。隨便看一個吧。

for (int i = 0; i < count; ++i) {
      final View child = getVirtualChildAt(i);

      if (child == null) {
        mTotalLength += measureNullChild(i);
        continue;
      }

      if (child.getVisibility() == View.GONE) {
       i += getChildrenSkipCount(child, i);
       continue;
      }

      if (hasDividerBeforeChildAt(i)) {
        mTotalLength += mDividerHeight;
      }

      LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

      totalWeight += lp.weight;

      if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
        // Optimization: don't bother measuring children who are going to use
        // leftover space. These views will get measured again down below if
        // there is any leftover space.
        final int totalLength = mTotalLength;
        mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
      } else {
        int oldHeight = Integer.MIN_VALUE;

        if (lp.height == 0 && lp.weight > 0) {
          // heightMode is either UNSPECIFIED or AT_MOST, and this
          // child wanted to stretch to fill available space.
          // Translate that to WRAP_CONTENT so that it does not end up
          // with a height of 0
          oldHeight = 0;
          lp.height = LayoutParams.WRAP_CONTENT;
        }

        // Determine how big this child would like to be. If this or
        // previous children have given a weight, then we allow it to
        // use all available space (and we will shrink things later
        // if needed).
        measureChildBeforeLayout(
           child, i, widthMeasureSpec, 0, heightMeasureSpec,
           totalWeight == 0 ? mTotalLength : 0);

        if (oldHeight != Integer.MIN_VALUE) {
         lp.height = oldHeight;
        }

        final int childHeight = child.getMeasuredHeight();
        final int totalLength = mTotalLength;
        mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
           lp.bottomMargin + getNextLocationOffset(child));

        if (useLargestChild) {
          largestChildHeight = Math.max(childHeight, largestChildHeight);
        }
      }

父檢視在對子檢視進行measure操作的過程中,使用變數mTotalLength儲存已經measure過的child所佔用的高度,該變數剛開始時是0。在for迴圈中呼叫measureChildBeforeLayout()對每一個child進行測量,該函式實際上僅僅是呼叫了measureChildWithMargins(),在呼叫該方法時,使用了兩個引數。其中一個是heightMeasureSpec,該引數為LinearLayout本身的measureSpec;另一個引數就是mTotalLength,代表該LinearLayout已經被其子檢視所佔用的高度。 每次for迴圈對child測量完畢後,呼叫child.getMeasuredHeight()獲取該子檢視最終的高度,並將這個高度新增到mTotalLength中。在本步驟中,暫時避開了lp.weight>0的子檢視,即暫時先不測量這些子檢視,因為後面將把父檢視剩餘的高度按照weight值的大小平均分配給相應的子檢視。原始碼中使用了一個區域性變數totalWeight累計所有子檢視的weight值。處理lp.weight>0的情況需要注意,如果變數heightMode是EXACTLY,那麼,當其他子檢視佔滿父檢視的高度後,weight>0的子檢視可能分配不到佈局空間,從而不被顯示,只有當heightMode是AT_MOST或者UNSPECIFIED時,weight>0的檢視才能優先獲得佈局高度。最後我們的結論是:如果不使用weight屬性,LinearLayout會在當前方向上進行一次measure的過程,如果使用weight屬性,LinearLayout會避開設定過weight屬性的view做第一次measure,完了再對設定過weight屬性的view做第二次measure。由此可見,weight屬性對效能是有影響的,而且本身有大坑,請注意避讓。

本文出自逆流的魚:http://blog.csdn.net/hejjunlin/article/details/51159419

小結

從原始碼中我們似乎能看出,我們先前的測試結果中RelativeLayout不如LinearLayout快的根本原因是RelativeLayout需要對其子View進行兩次measure過程。而LinearLayout則只需一次measure過程,所以顯然會快於RelativeLayout,但是如果LinearLayout中有weight屬性,則也需要進行兩次measure,但即便如此,應該仍然會比RelativeLayout的情況好一點。

RelativeLayout另一個性能問題

對比到這裡就結束了嘛?顯然沒有!我們再看看View的Measure()方法都幹了些什麼?

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

    if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
        widthMeasureSpec != mOldWidthMeasureSpec ||
        heightMeasureSpec != mOldHeightMeasureSpec) {
                     ......
      }
       mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;

    mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
        (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
  }
View的measure方法裡對繪製過程做了一個優化,如果我們或者我們的子View沒有要求強制重新整理,而父View給子View的傳入值也沒有變化(也就是說子View的位置沒變化),就不會做無謂的measure。但是上面已經說了RelativeLayout要做兩次measure,而在做橫向的測量時,縱向的測量結果尚未完成,只好暫時使用myHeight傳入子View系統,假如子View的Height不等於(設定了margin)myHeight的高度,那麼measure中上面程式碼所做得優化將不起作用,這一過程將進一步影響RelativeLayout的繪製效能。而LinearLayout則無這方面的擔憂。解決這個問題也很好辦,如果可以,儘量使用padding代替margin。

FrameLayout和LinearLayout效能PK


FrameLayout
LinearLayout

Measure:2.058ms
Layout:0.296ms
draw:3.857ms

FrameLayout

Measure:1.334ms
Layout:0.213ms
draw:3.680ms
從這個資料來使用LinearLayout,僅巢狀一個LinearLayou,在onMeasure就相關2倍時間和FrameLayout相比,layout和draw的過程兩者相差無幾,考慮到誤差的問題,幾乎可以認為兩者不分伯仲

看下FrameLayout的原始碼,做了什麼?

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        //當FrameLayout的寬和高,只有同時設定為match_parent或者指定的size,那麼這個
 
        //measureMatchParentChlidren = false,否則為true。下面會用到這個變數
         
        mMatchParentChildren.clear();
        int maxHeight = 0;     
        int maxWidth = 0;
        int childState = 0;    //寬高的期望型別
 
        for (int i = 0; i < count; i++) {    //一次遍歷每一個不為GONE的子view
     
            final View child = getChildAt(i);    
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                //去掉FrameLayout的左右padding,子view的左右margin,這時候,再去
 
                //計運算元view的期望的值
                 
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                 
                 
                /*maxWidth找到子View中最大的寬,高同理,為什麼要找到他,因為在這裡,FrameLayout是wrap
                -content.他的寬高肯定受子view的影響*/
                 
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                
                /*下面的判斷,只有上面的FragLayout的width和height都設定為match_parent 才不會執行
                此處的mMatchParentChlidren的list裡存的是設定為match_parent的子view。
                結合上面兩句話的意思,當FrameLayout設定為wrap_content,這時候要把所有寬高設定為
                match_parent的子View都記錄下來,記錄下來幹什麼呢?
                這時候FrameLayout的寬高同時受子View的影響*/
                    
                 if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }
         
        // Account for padding too
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
         
        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
        // Check against our foreground's minimum height and width
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }
 
        //設定測量過的寬高
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
        count = mMatchParentChildren.size();//這個大小就是子view中設定為match_parent的個數
 
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                //這裡看上去重新計算了一遍
 
                final View child = mMatchParentChildren.get(i);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
                int childWidthMeasureSpec;
                int childHeightMeasureSpec;
                /*如果子view的寬是match_parent,則寬度期望值是總寬度-padding-margin
                 如果子view的寬是指定的比如100dp,則寬度期望值是padding+margin+width
                 這個很容易理解,下面的高同理*/
                if (lp.width == LayoutParams.MATCH_PARENT) {
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
                            getPaddingLeftWithForeground() - getPaddingRightWithForeground() -
                            lp.leftMargin - lp.rightMargin,
                            MeasureSpec.EXACTLY);
                } else {
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,
                            lp.width);
                }
                 
                if (lp.height == LayoutParams.MATCH_PARENT) {
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
                            getPaddingTopWithForeground() - getPaddingBottomWithForeground() -
                            lp.topMargin - lp.bottomMargin,
                            MeasureSpec.EXACTLY);
                } else {
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,
                            lp.height);
                }
                //把這部分子view重新計算大小
 
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }
本文出自逆流的魚:http://blog.csdn.net/hejjunlin/article/details/51159419
加了一個巢狀,onMeasure時間,多了將近一倍,原因在於:LinearLayout在某一方向onMeasure,發現還存在LinearLayout。將觸發 
 if (useLargestChild && (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {

            mTotalLength = 0;
            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    mTotalLength += measureNullChild(i);
                    continue;
                }
                if (child.getVisibility() == GONE) {
                    i += getChildrenSkipCount(child, i);
                    continue;
                }
}
因為二級LinearLayout父類是Match_parent,所以就存在再層遍歷。在時間就自然存在消耗。

結論

1.RelativeLayout會讓子View呼叫2次onMeasure,LinearLayout 在有weight時,也會呼叫子View2次onMeasure
2.RelativeLayout的子View如果高度和RelativeLayout不同,則會引發效率問題,當子View很複雜時,這個問題會更加嚴重。如果可以,儘量使用padding代替margin。
3.在不影響層級深度的情況下,使用LinearLayout和FrameLayout而不是RelativeLayout。
最後再思考一下文章開頭那個矛盾的問題,為什麼Google給開發者預設新建了個RelativeLayout,而自己卻在DecorView中用了個LinearLayout。因為DecorView的層級深度是已知而且固定的,上面一個標題欄,下面一個內容欄。採用RelativeLayout並不會降低層級深度,所以此時在根節點上用LinearLayout是效率最高的。而之所以給開發者預設新建了個RelativeLayout是希望開發者能採用儘量少的View層級來表達佈局以實現效能最優,因為複雜的View巢狀對效能的影響會更大一些。

4.能用兩層LinearLayout,儘量用一個RelativeLayout,在時間上此時RelativeLayout耗時更小。另外LinearLayout慎用layout_weight,也將會增加一倍耗時操作。由於使用LinearLayout的layout_weight,大多數時間是不一樣的,這會降低測量的速度。這只是一個如何合理使用Layout的案例,必要的時候,你要小心考慮是否用layout weight。總之減少層級結構,才是王道,讓onMeasure做延遲載入,用viewStub,include等一些技巧。

第一時間獲得部落格更新提醒,以及更多android乾貨,原始碼分析,歡迎關注我的微信公眾號,掃一掃下方二維碼,即可關注。 
這裡寫圖片描述