欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-26 13:54:47
...

前言

为什么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>

预览 

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

 效果图

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

layout inspector分析 

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

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

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

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

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

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

结论

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

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

第五问,为什么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的区别 

上一篇: python 画图总结

下一篇: