性能优化之一就是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进去。