1.减少布局的嵌套,这一点也是最重要的
搞android的都知道,android的整个UI布局文件最后也是要一层一层的解析成View对象的,如果层次太深的话,对导致递归的层次太深而极大的影响解析速度,所以,我们一定不能让布局文件的层次太深,要想做到布局文件的层次不深,通常用到的手段有:1)使用RelativeLayout代替LinearLayout。2)编写完布局文件时,可以使用HieracyView工具检查是否有多余的无用布局,如果有,则一定要去掉无用的布局。最好是10层以下,尽量不要超过15层。
2.布局重用。
一些可以公用的布局我们不必要每次都重写一遍,可以将其写为一个独立的布局文件,最后使用include标签将布局引用即可,然而,严格上来讲,布局重用只是减少了我们代码的编写量,并不能达到对应用的优化作用,而且,在使用include时会很容易产生第一点中的无用父布局
3.使用merge标签去消除include标签所引入的无用布局
顾名思义,就是合并、融合的意思。使用它可以有效的将某些符合条件的多余的层级优化掉。使用merge的场合主要有两处:
(1) 自定义View中使用,父元素尽量是FrameLayout,当然如果父元素是其他布局,而且不是太复杂的情况下也是可以使用的
(2) Activity中的整体布局,根元素需要是FrameLayout
使用Merge注意事项
但是使用merge标签还是有一些限制的,具体有以下几点:
(1)merge只能用在布局XML文件的根元素
(2)使用merge来inflate一个布局时,必须指定一个ViewGroup作为其父元素,并且要设置inflate的attachToRoot参数为true。(参照inflate(int, ViewGroup, boolean))
(3)不能在ViewStub中使用merge标签。最直观的一个原因就是ViewStub的inflate方法中根本没有attachToRoot的设置
还要说一下,因为Window窗体(比如Activity)加载时会自动添加PhoneWindow$DecorView和FrameLayout(id/content)两层布局,所以如果我们在Activity的自定义布局根元素中使用merge,而且想设置总体背景什么的,可以用(id/content)将FrameLayout取出来,再设置属性,可以这样实现:
//setContentView(R.layout.layout_showset);
//获取DecorView中的FrameLayout,我们平时setContentView的布局是添加到这个FrameLayout中的
FrameLayout frameLayout = (FrameLayout)this.getWindow().getDecorView().findViewById(android.R.id.content);
//设置背景 frameLayout.setBackgroundResource(R.drawable.bg_repeated_main);
//利用明儿个将我们的布局直接添加到系统的FrameLayout中 LayoutInflater.from(this).inflate(R.layout.layout_showset, frameLayout, true);
4.利用 ViewStub延迟加载一些用不到的布局,
这是什么玩应儿呢?其实就是一个轻量级的页面,我们通常使用它来做预加载处理,来改善页面加载速度和提高流畅性,ViewStub本身不会占用层级,它最终会被它指定的层级取代。
在一些场合取代android:visibility=”gone”的用法,因为被gone掉的布局不断是会同时创建对象的。那为什么使用ViewStub就高效呢,
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(0, 0);
}
@Override
public void draw(Canvas canvas) {
}
由onMeasure()方法和draw()方法可以看出, ViewStub的初始宽高都是零,所以他开始不会占用空间,其次draw()方法也没有执行任何的绘制,由这两个方法就可以看出,ViewStub的确很高效。
在代码中要操纵ViewStub的时候,要首先使用viewstub.inflate()方法,将其所拥有的View初始化进去。否则会报空指针错误。
但ViewStub也不是万能的,下面总结下ViewStub能做的事儿和什么时候该用ViewStub,什么时候该用可见性的控制。
首先来说说ViewStub的一些特点:
1. ViewStub只能Inflate一次,之后ViewStub对象会被置为空。按句话说,某个被ViewStub指定的布局被Inflate后,就不会够再通过ViewStub来控制它了。
2. ViewStub只能用来Inflate一个布局文件,而不是某个具体的View,当然也可以把View写在某个布局文件中。
基于以上的特点,那么可以考虑使用ViewStub的情况有:
1. 在程序的运行期间,某个布局在Inflate后,就不会有变化,除非重新启动。
因为ViewStub只能Inflate一次,之后会被置空,所以无法指望后面接着使用ViewStub来控制布局。所以当需要在运行时不止一次的显示和隐藏某个布局,那么ViewStub是做不到的。这时就只能使用View的可见性来控制了。
2. 想要控制显示与隐藏的是一个布局文件,而非某个View。
因为设置给ViewStub的只能是某个布局文件的Id,所以无法让它来控制某个View。
所以,如果想要控制某个View(如Button或TextView)的显示与隐藏,或者想要在运行时不断的显示与隐藏某个布局或View,只能使用View的可见性来控制。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_second"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.demo.SecondActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="click"
android:text="开始滑动"
/>
<ViewStub
android:id="@+id/view_stub"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout="@layout/stubbed_view"
/>
</LinearLayout>
主页面的布局文件,定义了一个ViewStub,并且使用layout属性指定了所要填充的布局。
然后看我们的stubbed_view
<?xml version="1.0" encoding="utf-8"?>
<com.demo.MyTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/my_tv"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#9e9e9e"
android:text="4"
android:textColor="@android:color/black" />
然后我们在代码中这样使用:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
viewStub = (ViewStub) findViewById(R.id.view_stub);
//设置监听
viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
//stub是用来填充view的ViewStub,inflated是被填充的View
}
});
}
public void click(View view){
viewStub.inflate();
}
点击按钮后让ViewStub加载出指定的布局。整个过程就是这样,下面我们从源码中去看看ViewStub是如何实现这些的。
public final class ViewStub extends View {
private int mInflatedId;
private int mLayoutResource;
private WeakReference<View> mInflatedViewRef;
private LayoutInflater mInflater;
private OnInflateListener mInflateListener;
public ViewStub(Context context) {
this(context, 0);
}
public ViewStub(Context context, @LayoutRes int layoutResource) {
this(context, null);
mLayoutResource = layoutResource;
}
public ViewStub(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 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);
//viewStub的id
mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
a.recycle();
//设置自身不可见(不参与measure和layout)
setVisibility(GONE);
//不绘制自身
setWillNotDraw(true);
}
.......省去一些无关紧要的代码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//设置自己的宽高为0,不占用空间
setMeasuredDimension(0, 0);
}
//不绘制内容
@Override
public void draw(Canvas canvas) {
}
@Override
protected void dispatchDraw(Canvas canvas) {
}
@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 {
//当设置ViewStub的visibility为下面的情况时会触发inflate方法
if (visibility == VISIBLE || visibility == INVISIBLE) {
inflate();
}
}
}
//填充布局方法
public View inflate() {
//获取ViewStub的parent
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
//是否设置了layout属性
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(mContext);
}
//填充ViewStub中layout属性指定的布局
final View view = factory.inflate(mLayoutResource, parent,
false);
//如果我们设置了inflateid属性,则设置给我们的view
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
//获取ViewStub在父控件的位置
final int index = parent.indexOfChild(this);
//在parent中移除自己
parent.removeViewInLayout(this);
//获取ViewStub的布局参数
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
//将要填充的布局添加到parent中
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
mInflatedViewRef = new WeakReference<View>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
//如果没有给viewstub指定布局文件,抛出此异常
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else {
//如果viewStub被移除之后,再次调用inflate方法会抛出此异常
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
5.减少不同层间的背景重绘,例如,如果某个view父布局的背景已经设置为了白色,则不需要再为此view设置背景色,通过这个简单的小技巧,可以非常多的提升速度。
6.如果整个App都使用了自定义的Title和background,则我们可以使用自定义的style,永久的去掉系统默认为我们的Activity所添加的title和background,也可以提高Activity的渲染速度。
7.选择合适的布局,这里转载一个链接。
RelativeLayout和LinearLayout性能对比
8.使用CompoundDrawable,相邻的ImageView和TextView可以使用一个TextView,然后设置drawXXX来实现。
10.使用Lint来检查布局,下面总结一下Lint给出的一些提示
I.Node can be replaced by a TextView with compound drawables(上面说的CompoundDrawable)
II.Layout has too many views:默认情况下,单个布局中View的最大个数是80个,应该想办法减少
III.Layout hierarchy is too deep:默认的最大层级深度是10,可以考虑使用RelativeLayout减少布局层次
III.Nested layout weights:weight属性会使LinearLayout对子View进行两次测量,当一个LinearLayout拥有非0dp值的layout_weight属性,这时如果将它嵌套在另一个拥有非0dp的layout_weight的LinearLayout,这时的测量次数将以指数级别增加。
IIII.Inefficient layout weight:当linearLayout中只有一个子View设置了layout_weight属性,更高性能的做法是给它的width或者height设置为0dp,这样这个子View就不需要测量它自身对应的大小。