Implementation principle of Viewstub

preface

Recently, when the interviewer asked me how to use space occupying in ViewStub during the interview, I was confused. To tell the truth, I had seen very little about the internal code of some controls on the UI, so I couldn't answer at the moment. In fact, it's not very difficult, But we usually call it simply in the development process, but we don't have an in-depth understanding of its internal principle. In fact, the process of interview is a process of checking and filling vacancies, so that all our knowledge can be improved, and we can know why and why at the same time.

introduce

Some layout controls do not need to be displayed at the beginning. They are displayed according to the business logic after the program is started. The usual approach is to define them as invisible in xml, and then update their visibility in the code through setVisibility(). However, this will have an adverse impact on the programmability, because although the initial state of the control is invisible, However, it will still be created and drawn when the program starts, which increases the startup time of the program. It is precisely because of this situation that the Android system provides the ViewStub framework, which can easily implement "lazy loading" to improve program performance. This paper explains it from the two aspects of "use method" and "implementation principle", hoping to be helpful to you.

Usually, when we develop the Android interface, if we encounter controls or layouts that do not need to be displayed, we usually set them to view Gone or view Invisible to achieve the purpose we want. The advantage of doing so is simple logic and flexible control, but its disadvantage is that it consumes resources. In fact, it will also be parsed, instantiated and set attributes during internal Xml parsing, which will also consume system resources. At this time, ViewStub will appear on the stage. For better understanding, let's first look at its general usage, analyze the source code, and then summarize its conclusion

usage

First, we use two viewstubs in the layout file, and then set its id and layout file

<?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"
    android:orientation="vertical">

    <Button
        android:id="@+id/display"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="display/hide"
        android:textColor="@color/colorAccent" />

    <ViewStub
        android:id="@+id/style_1"
        android:layout_width="200dip"
        android:layout_height="200dip"
        android:layout="@layout/layout_content_first" />

    <ViewStub
        android:id="@+id/style_2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout="@layout/layout_content_second" />

</LinearLayout>
public class MainActivity extends AppCompatActivity {

    private ViewStub mViewStub1;
    private ViewStub mViewStub2;

    private View mViewStubContentView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //First, get the ViewStub according to the id
        mViewStub1 = findViewById(R.id.style_1);
        mViewStub2 = findViewById(R.id.style_2);
        //At the same time, initialize the layout of the ViewStub package when we need it. In fact, the delayed loading of ViewStub is based on this principle
        mViewStubContentView = mViewStub1.inflate();
        //At the same time, get the LayoutParams parameter of ViewStub
        ViewGroup.LayoutParams params = mViewStub1.getLayoutParams();
        Log.i("LOH", params.width + "...height..." + params.height);
        //We use the display button to control the display and hiding of the view after the ViewStub is loaded
        findViewById(R.id.display).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mViewStubContentView.getVisibility() == View.VISIBLE) {
                    mViewStubContentView.setVisibility(View.GONE);
                }else {
                    mViewStubContentView.setVisibility(View.VISIBLE);
                }
            }
        });
    }
}

The above is a very simple use case of ViewStub. If we need to display the layout file in ViewStub, we can call the inflate method or ViewStub Setvisible (view. Visible) can display the layout.

code analysis

Construction method analysis

   public final class ViewStub extends View {
   
   .......
   //Generally, if ViewStub is referenced in xml, it follows this construction method.
   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);
       //Get the inflateId number first
       mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
       //Then get the custom attribute inflatedId, that is, the layout file (xml file)
       mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
       //Gets the id number of the ViewStub definition
       mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
       //Remember to recycle here (because the compiler will prompt that there is a memory leak here)
       a.recycle();
       //This is the core key. First, set the current View to hide and set yourself not to participate in drawing. Then, we use the onDraw method
       setVisibility(GONE);
       setWillNotDraw(true);
   }
   
   @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       setMeasuredDimension(0, 0);
   }

   @Override
   public void draw(Canvas canvas) {
   }
}

Through the above code, we can clearly know that ViewStub does not participate in the drawing of View. First, set View Go, and then call setWillNotDraw in the View method. It does not participate in the interface drawing, and its own draw method has not been implemented. Finally, set your width and height to 0.

Display the interface through setVisible method

public void setVisibility(int visibility) {
        //First of all, the first call of mInflatedViewRef is null, so it enters the else branch
        if (mInflatedViewRef != null) {
            View view = mInflatedViewRef.get();
            if (view != null) {
                view.setVisibility(visibility);
            } else {
                throw new IllegalStateException("setVisibility called on un-referenced view");
            }
        } else {
            //First, the setVisibility method of view will be called directly
            super.setVisibility(visibility);
            //Then judge the visibility value we passed in. If it is still GONE, it will not be processed. Finally, we call inflate
            if (visibility == VISIBLE || visibility == INVISIBLE) {
                inflate();
            }
        }
    }

Through the above code analysis, we can conclude that the last call of setVisibility is inflate, so this method is the key.

public View inflate() {
    final ViewParent viewParent = getParent();
    //First, judge whether the ViewStub has a parent control and whether the parent control has a ViewGroup
    if (viewParent != null && viewParent instanceof ViewGroup) {
       //At the same time, the layout information of ViewStub must be set, and View cannot be set separately.
        if (mLayoutResource != 0) {
            final ViewGroup parent = (ViewGroup) viewParent;
            //Convert the layout file of ViewStub to View, but do not add it to the parent
            final View view = inflateViewNoAdd(parent);
            //Finally, find the location of the ViewStub in viewParen, and replace the View from the inflate d View with the 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 {
      //ViewStub cannot be used alone. For example, it is a child View of ViewGroup.
        throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
    }
  }

Finally, replace the position of ViewStub in viewParent with the instantiated view

private void replaceSelfWithView(View view, ViewGroup parent) {
     //First, get the position of ViewStub in the parent
     final int index = parent.indexOfChild(this);
     //It is also removed from the parent control
     parent.removeViewInLayout(this);
     //Note that the LayoutParams of ViewStub obtained here, that is, the parameters from layout have no effect. Only the parameters of ViewStub can be set.
     final ViewGroup.LayoutParams layoutParams = getLayoutParams();
     if (layoutParams != null) {
         parent.addView(view, index, layoutParams);
     } else {
         parent.addView(view, index);
     }
 }

analysis

The design of this class is actually very simple, and its code is only a few hundred lines. Through our analysis of the code above, we can summarize it as follows

ViewStub achieves the effect that it does not draw and render on the interface by setting go, setting the width and height to 0, and calling the function setWillNotDraw(true). In fact, it just means occupying a pit.
ViewStub can only call the setVisibility method once, and setVisibility finally calls the inflate method. In the indexOfChild(this) code in replaceSelfWithView, if the index is - 1 after the viewstub is removed, an exception will be thrown when addView.
ViewStub layoutParams is added to the loaded android:layotu view. Its root node layoutParams setting is invalid
summary
Through the above source code, we can analyze the principle of lazy loading of ViewStub. Firstly, ViewStub occupies the position and does not draw and display on the interface (see the analysis for the reason), and then we call the setVisible method and inflate method of ViewStub to display the page when necessary. Originally, it is to render the layout file into view and add it to its parent control (in ViewGroup), it mainly replaces the position occupied by the previous ViewStub to reach the display interface.
 

Keywords: Java Android Apache

Added by nicknax on Sat, 11 Dec 2021 02:48:11 +0200