Android布局优化(2)——ViewStub使用和源码梳理
问题背景
本文主要讨论安卓开发layout布局文件时的部分优化手段,上一篇文章中中初步介绍了include和merge两种方式,具体可参考 https://blog.51cto.com/baorant24/6057402 ,本文主要是介绍安卓布局优化的第三种手段,ViewStub的使用和梳理。
问题分析
很多同学在之前工作和学习过程中或多或少都使用过ViewStub,大都知道ViewStub是一个轻量级的视图控件,而实际开发中在合适的场景中使用,可以提高渲染速度,占用的内存更少,从而提高App的UI性能。话不多少,先梳理一波ViewStub的源码,对整体原理有个初步的了解先。 (1)ViewStub是一个继承了View类的视图
@RemoteView
public final class ViewStub extends View {
    private int mInflatedId;
    private int mLayoutResource;
(2)ViewStub是不可见的,实际上是把宽高都设置为0
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 长宽都设置为0
        setMeasuredDimension(0, 0);
    }
    @Override
    public void draw(Canvas canvas) {
        // 不绘制
    }
    @Override
    protected void dispatchDraw(Canvas canvas) {
        // 不分发
    }
(3)可以通过布局文件的android:inflatedId或者调用ViewStub的setInflatedId方法为懒加载视图的跟节点设置ID
    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context);
        final TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.ViewStub, defStyleAttr, defStyleRes);
        saveAttributeDataForStyleable(context, R.styleable.ViewStub, attrs, a, defStyleAttr,
                defStyleRes);
        // 通过自属性inflatedId来获取加载的视图根节点ID,默认返回NO_ID,代表没有赋值
        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
        // 需要加载的视图资源ID
        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
        mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
        a.recycle();
        setVisibility(GONE);
        setWillNotDraw(true);
    }
(4)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方法代码
                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");
        }
    }
android.view.ViewStub#replaceSelfWithView
    private void replaceSelfWithView(View view, ViewGroup parent) {
        final int index = parent.indexOfChild(this);
        // 从父节点中移除View
        parent.removeViewInLayout(this);
        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
        if (layoutParams != null) {
            parent.addView(view, index, layoutParams);
        } else {
            parent.addView(view, index);
        }
    }
(5)setVisibility()方法
    @Override
    @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
    public void setVisibility(int visibility) {
        // 如果对待加载视图的软引用不为空,说明已经执行过inflate方法了
        if (mInflatedViewRef != null) {
            View view = mInflatedViewRef.get();
            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的基本源码和原理,下面结合demo,分析一般的使用。 页面activity代码如下:
import android.os.Bundle
import android.view.ViewStub
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_view_stub.*
class ViewStubActivity2 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_stub)
        dianji.setOnClickListener {
            (viewstub as ViewStub).inflate()
        }
    }
}
页面布局文件如下,主要包括一个按钮和一个viewstub组件:
<?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:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ViewStubActivity">
  <Button
      android:id="@+id/dianji"
      android:text="点击显示图片 "
      android:layout_gravity="center_horizontal"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"/>
  <ViewStub
      android:id="@+id/viewstub"
      android:layout_marginTop="20dp"
      android:layout="@layout/view_stub"
      android:inflatedId="@+id/inflatedid_viewstub"
      android:layout_width="match_parent"
      android:layout_height="wrap_content" />
  </LinearLayout>
ViewStub引用view_stub布局文件如下,包括一个图片和一个文字框的线性布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/image_name"
        android:layout_gravity="center"
        android:src="@drawable/bg_ranking"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:text="加油"
        android:textColor="@color/blue_unable"
        android:layout_gravity="center"
        android:textSize="18sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>
运行程序后,运行结果如下:
点击按钮,运行结果如下:
我们用工具查看下两次情况的布局结构。
viewstub替换前:
viewstub替换后:

问题总结
本文主要讨论安卓开发layout布局文件时的部分优化手段,上一篇文章中中初步介绍了include和merge两种方式,具体可参考 https://blog.51cto.com/baorant24/6057402 ,本文主要是介绍安卓布局优化的第三种手段,ViewStub的使用和梳理。有兴趣的同学可以进一步深入研究。










