Android 布局优化总结

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就不需要测量它自身对应的大小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值