Android布局优化(2)——ViewStub使用和源码梳理

时间:2023-02-15 15:03:33

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>

运行程序后,运行结果如下: Android布局优化(2)——ViewStub使用和源码梳理 点击按钮,运行结果如下: Android布局优化(2)——ViewStub使用和源码梳理 我们用工具查看下两次情况的布局结构。 viewstub替换前: Android布局优化(2)——ViewStub使用和源码梳理 viewstub替换后: Android布局优化(2)——ViewStub使用和源码梳理

问题总结

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