ViewStub---加载性能优化全解析

一种最为常用的View加载优化方案

Posted by yourzeromax on October 29, 2018

写在前面

国际惯例,写一篇博客之前先来唠唠最近的一些感悟和收获,首先,又是好久没写博客啦!!!哈哈哈哈,其次,自从上次被辞退到现在,不光收获了一份满意的Offer,也大大拓展了自己的技术眼界,换了租住房,待遇也还提升了不少,还拿到了半个月的补偿金,同事之间也能很好地沟通和交流,一扫上家公司的工作沉闷氛围,好像被辞退也不是很亏啊!!!哈哈哈!!!

好了,废话打住,来介绍一下今天本博客的主人公—ViewStub,对于初学者来说可能比较陌生,并且常规定义View视图也并不需要在xml文件中添加它,但是,它却在View绘制性能优化方面有着举足轻重的地位!

基础介绍

作为一名优秀的Android工程师,一定要学会从Google Android官网去查看第一手资料,所以,我先贴一段官方的介绍:

ViewStub

A ViewStub is an invisible, zero-sized View that can be used to lazily inflate layout resources at runtime. When a ViewStub is made visible, or when inflate() is invoked, the layout resource is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views. Therefore, the ViewStub exists in the view hierarchy until setVisibility(int) or inflate() is invoked. The inflated View is added to the ViewStub’s parent with the ViewStub’s layout parameters. Similarly, you can define/override the inflate View’s id by using the ViewStub’s inflatedId property.for example:

看不懂没关系,再来翻译一下:

ViewStub控件是一个不可见,0尺寸的懒性控件。当ViewStub控件设置可见,或者调用inflate(),在执行完成之后,ViewStub所指定的layout资源就会被加载。这个ViewStub就会被新加载的layout文件代替。ViewStub也会从其父控件中移除。因此ViewStub在View视图树中存在的周期直到setVisibility(int)或者inflate()被调用。被加载的Layout文件伴随着ViewStub的属性设置一起被加入ViewStub的父控件中。 你可以定义或者重写被加载layout文件的id属性。例如:

    <ViewStub 
            android:id="@+id/stub"
            android:inflatedId="@+id/subTree"
            android:layout="@layout/mySubTree"
            android:layout_width="120dip"
            android:layout_height="40dip" />

这是Google官方的示例代码,也是最为标准的使用方法,各属性的功能如下:

属性名 功能
android:id=”@+id/stub” 寻找到本ViewStub的id
android:inflatedId=”@+id/subTree” 加载完成后寻找到被加载View的id
android:layout=”@layout/mySubTree” 将要被加载的视图xml文件
layout_width layout_height ViewStub的大小,会留给加载后的View

没懂没关系,写在这里的原因,一方面是为了告诉大家Google爸爸写的文章的重要性和权威性,一方面则是方便大家在遗忘后查阅。

基础用法

用法一共三步走:

序号 描述
1 添加xml控件布局
2 java代码中获取ViewStub对象,调用inflate()加载真正的View
3 获取真正的View引用,之后进行日常操作

首先在某个需要延迟加载的xml布局之中添加上节中的xml代码(可以当成普通的View添加),然后像个普通的View一样去findViewById获取ViewStub的引用,然后调用inflate()去获取真正View的引用,结束~

     ViewStub stub = (ViewStub) findViewById(R.id.stub);
     View inflated = stub.inflate();

添加几个坑点:

  1. 请注意看,xml中是android:layout!!!不是layout,和include不一样!
  2. inflate()加载返回的View类型,是layout文件最外层的View类型
  3. 好好理解ViewStub的属性会传递给加载后的View这句话

别问我为什么知道这些坑,血的教训。

源码分析

我在刚接触到它时,就感到非常神奇,为什么它就能做到延迟加载,为什么它就能优化性能呢?于是对它的源码产生了好奇,但是后来才发现ViewStub的关键代码非常简单,不过里面有一些很重要的思想可以学习,帮助我们提高代码架构能力。

ViewStub继承自View,所以它其实就是一个普通的View控件,拥有View的一切属性和方法,能够被添加到xml中,这些代码没什么值得我们研究的,那么我们就从inflate()方法入手:

    //ViewStub.java

public final class ViewStub extends View {
    //省略一大堆代码
    
        public View inflate() {
        final ViewParent viewParent = getParent();

        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) {
                final ViewGroup parent = (ViewGroup) viewParent;
                final View view = inflateViewNoAdd(parent);
                replaceSelfWithView(view, parent);

                mInflatedViewRef = new WeakReference<>(view);
                if (mInflateListener != null) {
                    mInflateListener.onInflate(this, view);
                }

                return view;
            } else {
                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
            }
        } else {
            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
        }
    }
}

可以看出来,其实也没干啥事,无非就是传递自己的父布局,通过inflateViewNoAdd()加载真正的View,再用它来通过replaceSelfWithView()替换自己的位置:

    //ViewStub.java

    private View inflateViewNoAdd(ViewGroup parent) {
        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);
        }
        return view;
    }
    
    private void replaceSelfWithView(View view, ViewGroup parent) {
        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);
        }
    }
    

加载过程如下:
mLayoutResource由Android:layout指定,mInflatedId就是在xml中传递的inflateId,加载完View后就赋值这个id。

至于替换过程:
可以看到被加载View的index和LayoutParams属性都是从ViewStub那里直接赋值过来的,流程看着也特别清爽,是吧~

总结

实在太简单,就没啥总结的啦~当然如何高效地应用ViewStub,只能告诉大家,多写,多看官方文档,多看开源代码。

拜拜~

欢迎关注:
我的博客