原文:http://blog.csdn.net/hejjunlin/article/details/51159419android
工做一段時間後,常常會被領導說,你這個進入速度太慢了,競品的進入速度很快,你搞下優化吧?每當這時,你會怎麼辦?功能實現都有啊,進入時要加載那麼多view,這也沒辦法啊,等等。app
先看一些現象吧:用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,到底誰的性能更高,開發者該怎麼選擇呢?ide
先經過幾個問題,簡單的瞭解寫android中View的工做原理吧。函數
簡單來講,View是Android系統在屏幕上的視覺呈現,也就是說你在手機屏幕上看到的東西都是View。佈局
View的繪製流程是從ViewRoot的performTraversals()方法開始,依次通過measure(),layout()和draw()三個過程才最終將一個View繪製出來。性能
Android中的視圖都是經過Window來呈現的,無論Activity、Dialog仍是Toast它們都有一個Window,而後經過WindowManager來管理View。Window和頂級View——DecorView的通訊是依賴ViewRoot完成的。測試
無論簡單的Button和TextView仍是複雜的RelativeLayout和ListView,他們的共同基類都是View。因此說,View是一種界面層控件的抽象,他表明了一個控件。那ViewGroup是什麼東西,它能夠被翻譯成控件組,即一組View。ViewGroup也是繼承View,這就意味着View自己能夠是單個控件,也能夠是多個控件組成的控件組。根據這個理論,Button顯然是個View,而RelativeLayout不可是一個View還能夠是一個ViewGroup,而ViewGroup內部是能夠有子View的,這個子View一樣也多是ViewGroup,以此類推。優化
基於以上原理和大背景,咱們要探討的性能問題,說的簡單明瞭一點就是:當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這三大流程的執行耗時。
以下圖,咱們分別用兩用種方式簡單的實現佈局測試下ui
Measure:0.762ms
Layout:0.167ms
draw:7.665msthis
Measure:2.180ms
Layout:0.156ms
draw:7.694ms
從這個數據來看不管使用RelativeLayout仍是LinearLayout,layout和draw的過程二者相差無幾,考慮到偏差的問題,幾乎能夠認爲二者不分伯仲,關鍵是Measure的過程RelativeLayout卻比LinearLayout慢了一大截。
@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。因此須要橫向縱向分別進行一次排序測量。
@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屬性對性能是有影響的,並且自己有大坑,請注意避讓。
從源碼中咱們彷佛能看出,咱們先前的測試結果中RelativeLayout不如LinearLayout快的根本緣由是RelativeLayout須要對其子View進行兩次measure過程。而LinearLayout則只需一次measure過程,因此顯然會快於RelativeLayout,可是若是LinearLayout中有weight屬性,則也須要進行兩次measure,但即使如此,應該仍然會比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。
Measure:2.058ms
Layout:0.296ms
draw:3.857ms
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); } } }
加了一個嵌套,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; } }
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等一些技巧。