【Android】性能优化——懒加载控件ViewStub简易使用与源码分析

前言

为什么ViewStub可以提高加载性能?
ViewStub使用的是惰性加载的方式,即使将其放置于布局文件中,如果没有进行加载那就为空,不像其它控件一样只要布局文件中声明就会存在。 
那ViewStub适用于场景呢?通常用于网络请求页面失败的显示。一般情况下若要实现一个网络请求失败的页面,我们是不是使用两个View呢,一个隐藏,一个显示。试想一下,如果网络状况良好,并不需要加载失败页面,但是此页面确确实实已经加载完了,无非只是隐藏看不见而已。如果使用ViewStub,在需要的时候才进行加载,不就达到节约内存提高性能的目的了吗?


正文 

MainActivity

public class MainActivity extends AppCompatActivity {

    Button mShowButton, mHideButton;
    ViewStub mViewStub;
    LinearLayout mFillView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mViewStub = findViewById(R.id.vs);
        mShowButton = findViewById(R.id.btn_show_stub);
        mHideButton = findViewById(R.id.btn_hide_stub);
        mShowButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    //inflate只能执行一次,多次会报错
                    //获得替换view的两种方式
                    //1.通过inflate返回值
                    mFillView = (LinearLayout) mViewStub.inflate();
                    //2.通过在xml中设置ViewStub的inflatedId属性也可获得替换view
                    //fillView = findViewById(R.id.inflated_start);
                } catch (Exception e) {
                    //加载过后显隐使用visible
                    //有2种方式控制显隐效果
                    //1.使用原本的viewStub
                    //mViewStub.setVisibility(View.VISIBLE); 使用mViewStub也可以控制内容显隐
                    //2.直接使用替换内容view
                    mFillView.setVisibility(View.VISIBLE);
                }
            }
        });
        mHideButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //mViewStub.setVisibility(View.GONE); 使用viewStub也可以控制内容显隐
                mFillView.setVisibility(View.GONE);
            }
        });
    }
}

ps:加载也可以使用 viewStub.setVisibility(),内部源码还是inflate();

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ViewStub
        android:id="@+id/vs"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:inflatedId="@+id/inflated_start"
        android:layout="@layout/view_stub"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="展示stub"
        android:id="@+id/btn_show_stub"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="隐藏stub"
        android:id="@+id/btn_hide_stub"/>
</LinearLayout>

替换View

view_stub.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="我是来替它的" />
</LinearLayout>

预览 

 效果图

layout inspector分析 

1.在没点击“展示”时 是灰色的ViewStub

2.点击展示后被替换成自己的控件

3.再点击隐藏,灰掉的是替换后的控件

结论

ViewStub的使用是将自己的位置替换成它的内容view,不保留自己,所以替换过后,布局中将无法找到原来的ViewStub。

问题

在这次demo使用ViewStub的过程中,我希望大家能有几个问题?

1.为什么ViewStub可以提高性能?

2.visible:gone不是也没有渲染view,为什么还需要ViewStub?

3.在替换ViewStub占用位置为内容替换view的时候是如何准确替换位置的? (这是我的疑惑...)

4.为什么我在替换内容中使用的是满屏layout,加载出来只有那么一丢丢?

5.为什么ViewStub是去除自己,替换内容,却还能使用已经卸载掉的ViewStub控制内容显隐?

其上1.2两个问题都在以下的借鉴博客中找到,希望大家了解


那咱们就从源码角度分析一下3,4两个问题 

最新版源码

起点 viewStub.inflate();

 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);
                //替换ViewStub过程   笔者自加
                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");
        }
    }

private View inflateViewNoAdd(ViewGroup parent) {
        final LayoutInflater factory;
        if (mInflater != null) {
            factory = mInflater;
        } else {
            factory = LayoutInflater.from(mContext);
        }
        //第三个参数传入false即代表不添加到parent
        final View view = factory.inflate(mLayoutResource, parent, false);

        if (mInflatedId != NO_ID) {
            view.setId(mInflatedId);
        }
        return view;
    }

 咱们就来看一下关键代码,替换

private void replaceSelfWithView(View view, ViewGroup parent) {
        //使用index获得子控件(ViewStub)索引
        final int index = parent.indexOfChild(this); //之前从来没用过indexOfChild的我,面壁
        parent.removeViewInLayout(this);

        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
        if (layoutParams != null) {
            //使用index索引替换到指定位置
            parent.addView(view, index, layoutParams);
        } else {
            parent.addView(view, index);
        }
    }

解析

  1. 使用parent.indexOfChild可以获得子view在父view 的索引位置index
  2. 使用index定向addView( view , index , layoutParams) 装载替换view

这就是为什么能定点替换了。

第四问,为什么不按替换view样式显示呢?

  1. 我们设置了layoutParams,于是使用的是addView( view , index , layoutParams) 方法
  2. 跟随源码一路发现
 public void addView(View child, int index) {
        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }
        //不传就是使用子控件自己的,不过可想而知很少会出现不传的这个addView
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = generateDefaultLayoutParams();
            if (params == null) {
                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
            }
        }
        addView(child, index, params);
    }

public void addView(View child, int index, LayoutParams params) {
        ...省略
        addViewInner(child, index, params, false);
    }

private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        ...省略
        if (!checkLayoutParams(params)) {
            params = generateLayoutParams(params);
        }

        if (preventRequestLayout) {
            //被设置成传入的
            child.mLayoutParams = params;
        } else {
            child.setLayoutParams(params);
        }

        ...省略
    }

解析 

  1. replaceSelfWithView()中的layoutParams大概率都是存在的,于是会调用addView(View child, int index, LayoutParams params)
  2. 使用传入的params,也就是我们写在ViewStub中的wrap_content

第五问,为什么ViewStub是去除自己,替换内容,却还能使用已经卸载掉的ViewStub控制内容显隐?

   答:虽然ViewStub从view Tree中卸载,但是还处在内存中。所以ViewStub可以通过使用WeakReference弱引用持有内容view,再进行显隐,其中的setVisibility()源码就有使用到。

  public void setVisibility(int visibility) {
        if (mInflatedViewRef != null) {
            View view = mInflatedViewRef.get();//通过inflate()加载过的weakReference
            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();
            }
        }
    }

 

借鉴

使用ViewStub来提高加载性能吧

View Gone 与 ViewStub的区别 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值