性能優化之一就是layout的優化,
as 常識:
佈局是否合理主要影響的是頁面測量時間的多少,我們知道一個頁面的顯示測量和繪製過程都是通過遞歸來完成的,多叉樹遍歷的時間與樹的高度h有關,其時間複雜度 O(h),如果層級太深,每增加一層則會增加更多的頁面顯示時間,所以佈局的合理性就顯得很重要。
那佈局優化有哪些方法呢,主要通過減少層級、減少測量和繪製時間、提高複用性三個方面入手。總結如下:
- 減少層級。合理使用 RelativeLayout 和 LinerLayout,合理使用Merge。
- 提高顯示速度。使用 ViewStub,它是一個看不見的、不佔佈局位置、佔用資源非常小的視圖對象。
- 佈局複用。可以通過includ標籤來提高複用。
- 儘可能少用wrap_content。wrap_content 會增加布局 measure 時計算成本,在已知寬高爲固定值時,不用wrap_content 。
- 刪除控件中無用的屬性
看下viewstub。這個標籤吊的地方是需要顯示的時候在加載,其實就是把耗時點移動了,而並不是不耗時。
使用的話develop上有很清晰的講解:點擊打開鏈接
https://developer.android.com/reference/android/view/ViewStub.html
但是有個小細節,如果深扣或者說完美主義的話:
以develop上的例子:
<ViewStub android:id="@+id/stub" android:inflatedId="@+id/subTree" android:layout="@layout/mySubTree" android:layout_width="120dip" android:layout_height="40dip" />
這裏layout也不要寫:
<ViewStub android:id="@+id/stub" android:inflatedId="@+id/subTree" android:layout_width="120dip" android:layout_height="40dip" />
等到需要顯示的時候在set,這樣更符合優化的初衷。
viewStub.setLayoutResource(R.layout.mySubTree); if (view == null) { view = viewStub.inflate(); }
其次就是使用注意點,一旦viewstub inflate了或者setvisibility了,則不能再次調用inflate否則會發生crash。
原理也很簡單:
1.首先看爲什麼layout用的時候在設置更好:如果一開始設置了,則會進行加載,不設置則直接0,這就是區別;
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewStub, defStyleAttr, defStyleRes); mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID); a.recycle(); setVisibility(GONE); setWillNotDraw(true); }
2.看看這個viewstub如何做到需要的時候在顯示的,其他的view爲什麼直接加載了?
看到1中最後兩句setvisibility gone和willnotdraw true;
visibility都知道,gone不顯示不佔地方,那個後面這個呢,源碼如下,也就是設置flag,這樣在:
public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }
解釋如下:也就是不會調用ondraw,從而優化
/**
* This view won't draw. {@link #onDraw(android.graphics.Canvas)} won't be
* called and further optimizations will be performed. It is okay to have
* this flag set and a background. Use with DRAW_MASK when calling setFlags.
* {@hide}
*/
static final int WILL_NOT_DRAW = 0x00000080;
不過,都知道,除了ondraw以爲還有onmeasure和layout呢,這個也是耗時點:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(0, 0); }
都0了,很乾脆,所以也不用考慮了。
以上這兩點就是viewstub沒加載的原因。
接下來,看下,inflate或者setvisibility怎麼又顯示的?
根據基本知識,就知道,肯定有param和addview等操作了。
@Override @android.view.RemotableViewMethod public void setVisibility(int visibility) { if (mInflatedViewRef != null) { View view = mInflatedViewRef.get(); if (view != null) { view.setVisibility(visibility); } else { throw new IllegalStateException("setVisibility called on un-referenced view"); } } else { super.setVisibility(visibility); if (visibility == VISIBLE || visibility == INVISIBLE) { inflate(); } } }
setvisibility也是通過inflate,所以一個意思,看inflate:
部分code:
final ViewParent viewParent = getParent(); if (viewParent != null && viewParent instanceof ViewGroup) { if (mLayoutResource != 0) { final ViewGroup parent = (ViewGroup) viewParent; final LayoutInflater factory; if (mInflater != null) { factory = mInflater; } else { factory = LayoutInflater.from(mContext); } final View view = factory.inflate(mLayoutResource, parent, false); if (mInflatedId != NO_ID) { view.setId(mInflatedId); } final int index = parent.indexOfChild(this); parent.removeViewInLayout(this); final ViewGroup.LayoutParams layoutParams = getLayoutParams(); if (layoutParams != null) { parent.addView(view, index, layoutParams); } else { parent.addView(view, index); }
1.必須有parent,否則沒法添加(合理,本來就是某個佈局裏的某個控件或者子佈局沒加載)
2.inflate layout,所以在inflate之前設置layout就ok。
factory.inflate(mLayoutResource, parent,
3.從parent中移除之前(0,0)大小的自己,在重新把解析完了帶param或者不帶param的自己add進去。