Android開發——LinearLayout和RelativeLayout的性能對比


0. 前言

我們都知道新建一個Android項目自動生成的Xml布局文件的根節點默認是RelativeLayout,這不是IDE默認設置,而是由android-sdk\tools\templates\activities\EmptyActivity\root\res\layout\activity_simple.xml.ftl這個文件事先就定好了的,在我們的理解里貌似LinearLayout的性能是要比RelativeLayout更優的,那SDK為什么會默認給開發者新建一個RelativeLayout呢?

同時作為頂級ViewDecorView卻是個垂直方向的LinearLayout,上面是標題欄,下面是內容欄,我們常用的setContentView()方法就是給內容欄設置布局。那么LinearLayoutRelativeLayout誰的性能更高呢,好吧其實我們都知道前者性能更高,那原因是什么呢?

 

1.   性能對比

問題的核心在於,當RelativeLayoutLinearLayout分別作為ViewGroup表達相同布局時誰的繪制過程更快一點。

Hierarchy Viewer是隨Android SDK發布的工具,位於Android SDK/tools/hierarchyviewer.bat,使用它可以來檢測View繪制的三大過程的耗時。如果不清楚View繪制的三大過程的,可以參考我之前寫過的 Android開發——View繪制過程源碼解析中詳細的介紹過了,這里就不再贅述了。

通過網上的很多實驗結果我們得之,兩者繪制同樣的界面時layoutdraw的過程時間消耗相差無幾,關鍵在於measure過程RelativeLayoutLinearLayout慢了一些。我們知道ViewGroup是沒有onMeasure方法的,這個方法是交給子類自己實現的。因為不同的ViewGroup子類布局都不一樣,那么onMeasure索性就全部交給他們自己實現好了。

所以我們就分別來追蹤下RelativeLayoutLinearLayoutonMeasure過程來探索耗時問題的根源。本文原創,為保證錯誤及時更新請認准原創鏈接SEU_Calvin的博客


1.1    RelativeLayoutonMeasure分析

@Override  
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
//...
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();
         applyHorizontalSizeRules(params, myWidth);
         measureChildHorizontal(child, params, myWidth, myHeight);
         if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
               offsetHorizontalAxis = true;
         }
    }
}

views = mSortedVerticalChildren;
count = views.length;
for (int i = 0; i < count; i++) {
     View child = views[i];
     if (child.getVisibility() != GONE) {
           LayoutParams params = (LayoutParams) child.getLayoutParams();
           applyVerticalSizeRules(params, myHeight);
           measureChild(child, params, myWidth, myHeight);
           if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
                 offsetVerticalAxis = true;
           }
           if (isWrapContentWidth) {
                 width = Math.max(width, params.mRight);
           }
           if (isWrapContentHeight) {
                 height = Math.max(height, params.mBottom);
           }
           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);
           }
       }
  }
  //...
}

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


同時需要注意的是View.measure()方法存在以下優化:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {
        ...
        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
}

即如果我們或者我們的子View沒有要求強制刷新,而父View給子View傳入的值也沒有變化(也就是說子View的位置沒變化),就不會做無謂的測量RelativeLayoutonMeasure中做橫向測量時,縱向的測量結果尚未完成,只好暫時使用myHeight傳入子View系統。這樣會導致在子View的高度和RelativeLayout的高度不相同時(設置了Margin),上述優化會失效,在View系統足夠復雜時,效率問題就會很明顯。


1.2  LinearLayoutonMeasure過程

@Override  
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  if (mOrientation == VERTICAL) {  
    measureVertical(widthMeasureSpec, heightMeasureSpec);  
  } else {  
    measureHorizontal(widthMeasureSpec, heightMeasureSpec);  
  }  
}  
//LinearLayout會先做一個簡單橫縱方向判斷,我們選擇縱向這種情況繼續分析
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
//...
for (int i = 0; i < count; ++i) {  
      final View child = getVirtualChildAt(i);  
      //... child為空、Gone以及分界線的情況略去
     //累計權重
      LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();  
      totalWeight += lp.weight;  
      //計算
      if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {  
            //精確模式的情況下,子控件layout_height=0dp且weight大於0無法計算子控件的高度
            //但是可以先把margin值合入到總值中,后面根據剩余空間及權值再重新計算對應的高度
            final int totalLength = mTotalLength;  
            mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);  
      } else {  
           if (lp.height == 0 && lp.weight > 0) {  
            //如果這個條件成立,就代表 heightMode不是精確測量以及wrap_conent模式
            //也就是說布局是越小越好,你還想利用權值多分剩余空間是不可能的,只設為wrap_content模式
                 lp.height = LayoutParams.WRAP_CONTENT;  
           }  
  
          // 子控件測量
          measureChildBeforeLayout(child, i, widthMeasureSpec,0, heightMeasureSpec,totalWeight== 0 ? mTotalLength :0);         
          //獲取該子視圖最終的高度,並將這個高度添加到mTotalLength中
          final int childHeight = child.getMeasuredHeight();  
          final int totalLength = mTotalLength;  
          mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); 
          } 
        //...
}

源碼中已經標注了一些注釋,需要注意的是在每次對child測量完畢后,都會調用child.getMeasuredHeight()獲取該子視圖最終的高度,並將這個高度添加到mTotalLength中。但是getMeasuredHeight暫時避開了lp.weight>0的子View,因為后面會將把剩余高度按weight分配給相應的子View。因此可以得出以下結論:

1)如果我們在LinearLayout不使用weight屬性,將只進行一次measure的過程

2)如果使用了weight屬性,LinearLayout在第一次測量時避開設置過weight屬性的子View,之后再對它們做第二次measure。由此可見,weight屬性對性能是有影響的


3.   總結論

1RelativeLayout慢於LinearLayout是因為它會讓子View調用2次measure過程,而后者只需一次,但是有weight屬性存在時,后者同樣會進行兩次measure

2RelativeLayout子View如果高度和RelativeLayout不同,會引發效率問題,可以使用padding代替margin以優化此問題。

3)在不響應層級深度的情況下,使用Linearlayout而不是RelativeLayout


結論中的第三條也解釋了文章前言中的問題:DecorView的層級深度已知且固定的,上面一個標題欄,下面一個內容欄,采用RelativeLayout並不會降低層級深度,因此這種情況下使用LinearLayout效率更高。

而為開發者默認新建RelativeLayout是希望開發者能采用盡量少的View層級,很多效果是需要多層LinearLayout的嵌套,這必然不如一層的RelativeLayout性能更好。因此我們應該盡量減少布局嵌套,減少層級結構使用比如viewStubinclude等技巧。可以進行較大的布局優化。具體技巧的使用后面會繼續寫文總結。


最后希望大家多多點贊支持~



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM