preface

My definition of lazy loading is that data is not loaded until the page is visible to the user, otherwise it will waste user traffic. There are many ways to implement lazy loading on the web, but most of them address lazy loading in scenario 1, which I describe below. This article also addresses lazy loading in Scenario 2.

If you don’t want to see the analysis below, you can simply import the class into your project. You need to lazily load fragments that inherit the class and override the corresponding method: Portal.

Scenario 1: Viewpager + Tablayout + Fragment

Scenario 1 is a situation that many of you have encountered. The whole interface is a combination of Viewpager, Tablayout, and Fragment, and you swipe left and right to show the data to the user. When you slide to the next page, the Fragment already has the data. Since Viewpager preloads adjacent Fragment pages by default, this number can be set as follows:

viewPager.setOffscreenPageLimit(0);
Copy the code

Doesn’t the above code set the number of preloads to 0? If you pass in a value less than 1, the ViewPager will set the number of preloads to the default value, which is 1, so even if you pass in a value of 0, ViewPager still preloads the left and right fragments of the current page.

Lazy loading principle

So what’s the solution? SetUserVisibleHint (Boolean isVisibleToUser) : setUserVisibleHint(Boolean visibletouser)

The setUserVisibleHint method is a callback function in the Fragment. If the Fragment is visible to the user, setUserVisibleHint() hints () when the Fragment is visible to the user and isVisibleToUser=true. If the Fragment is visible to the user, setUserVisibleHint() hints () when the Fragment is not visible or instantiated. Parameter isVisibleToUser=false.

Let’s take a look at when this method is called in the Fragment lifecycle:

  • 1. When the Fragment is instantiated, the Fragment is loaded into the ViewPager adapter and: setUserVisibleHint() ->onAttach() -> onCreate() -> onCreateView() -> onViewCreated() -> onActivityCreate() -> onStart() – > onResume (). The setUserVisibleHint() parameter is false at this point.
  • 2, when Fragmente is visible, that is, when ViewPager slides to the current page: setUserVisibleHint() Only the setUserVisibleHint method is called, because it’s already preloaded, and the Fragment has already gone to onResume() in its previous lifecycle. The setUserVisibleHint() parameter is true at this point.
  • 3, in the Fragment from visible to invisible, that is, the ViewPager from the current page to another page: setUserVisibleHint(). Only the setUserVisibleHint method is called because the current page preloading process is also maintained, at which point the parameter in setUserVisibleHint() is false.
  • 4. Click the TabLayout to jump directly to a page that is not preloaded. At this time, the callback process of the life cycle: setUserVisibleHint() -> setUserVisibleHint() -> onAttach() -> onCreate() -> onCreateView() -> onViewCreated() -> OnActivityCreate () -> onStart() -> onResume(). SetUserVisibleHint () is called back twice, once to mean false when initialized and once to mean true when visible.

You can see that setUserVisibleHint is always called when it is initialized, when it is visible, and when it is converted from visible to invisible.

Implementation approach

Here is the lazy load implementation for Scenario 1: We usually in fragments onActivityCreated load data, this time we can judge the Fragment is visible to the user, call fragments. GetUserVisibleHint isVisibleToUser values can be obtained (), If true, the data is visible, and the data is loaded. If not, the data is not loaded.

  @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if(isFragmentVisible(this) && this.isAdded()){
            if (this.getParentFragment() == null || isFragmentVisible(this.getParentFragment())) {
                onLazyLoadData();
                isLoadData = true;
                / /...}}}Copy the code

OnLazyLoadData () is a method that subclasses should override to load data. After loading data, set isLoadData to true to indicate that the data has been loaded.

So what I’m doing is I’m not loading data when the Fragment is not visible, and the Fragment’s life cycle goes to onResume, so when I slide to the Fragment, I’m just calling its setUserVisibleHint method, Then load the data in the setUserVisibleHint method as follows:

   @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if(isFragmentVisible(this) && !isLoadData && isViewCreated && this.isAdded()){
            onLazyLoadData();
            isLoadData = true; }}Copy the code

The isViewCreated field indicates whether the layout is initialized. It is assigned true in the onViewCreated method as follows:

 @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        isViewCreated = true;
    }

Copy the code

The onViewCreated method is called after the onCreateView method. When the onViewCreated method is called, the Fragment’s View layout must be created.

Go back to the setUserVisibleHint method. In if it determines that the Fragment is visible, that the data has not been loaded, that the layout has been created, and then loads the data. It sets isLoadData to true.

The sample application

Here’s what I used in my project:

As you can see, the data only loads when I slip the Fragment.

Scenario 2: FragmentManager + FragmentTransaction+ Fragment

In this scenario, you add several fragments to the FragmentManager using the Add method of FragmentTransaction. When switching fragments, you use the hide and show methods of FragmentTransaction together. Similar to the main interface of wechat, there is a TAB at the bottom, and then click TAB to switch the page.

When the Fragment is added to the Manager, the Fragment’s life cycle is already onResume, so when you switch between the hide and show methods on the Fragment, the Fragment already has data. In my project, WHAT I want to do is, When I click on this TAB, it only loads data for the Fragment, so I do lazy loading for this case.

Lazy loading principle

So how do you do that? Copy the implementation of Scenario 1? Unfortunately, no, because the setUserVisibleHint method will not be called in this case. OnHiddenChanged (Boolean hidden) :

The onHiddenChanged method is called when the Fragment’s hidden state changed. If the Fragment is not hidden, call the show method. The current onHiddenChanged callback is used with the parameter hidde=false. The hide method is called when the Fragment is hidden. The onHiddenChanged() callback is called with hidde=true. It is also important to note that when hide and show are used, none of the fragment’s lifecycle methods are called, except onHiddenChanged ().

Let’s take a look at when this method is called in the Fragment lifecycle:

  • 1, When Fragment is added to manager: onAttach() -> onCreate() -> onCreateView() -> onViewCreated() -> onActivityCreate() -> onHiddenChanged() -> onStart() -> OnResume (). The parameter in onHiddenChanged() is false at this time.
  • If the Fragment is hidden by the hide method onHiddenChanged(), only the onHiddenChanged method is called. SetUserVisibleHint () is true.
  • 3. If show displays the Fragment: onHiddenChanged(), only onHiddenChanged is called. Set setUserVisibleHint() to false.

As you can see, onHiddenChanged is always called at initialization, hide, and show.

Implementation approach

Scenario 1 plays with the setUserVisibleHint method, this time in the onHiddenChanged method, as follows:

  @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        //1, onHiddenChanged called before Resumed, so fragment may be added at this time, but not Resumed
        if(! hidden && !this.isResumed())
            return;
        //2. All lifecycle methods of the fragment are not called when hide and show are used, except onHiddenChanged ().
        if(! hidden && isFirstVisible &&this.isAdded()){
            onLazyLoadData();
            isFirstVisible = false; }}Copy the code

Start with comment 1, because when you add onHiddenChanged is called before onResume is executed. The Fragment is not visible to the user yet, so loading data at this time would not be useful. When the user sees the Fragmen, it’s already finished executing the data, so I’m going to add a judgment here, if the Fragment hasn’t resumed yet, I’m just going to return, and I’m not going to do anything.

If the Fragment is visible, you can use the hidden field to control lazy loading. Hidden is false, which means that the show method is called. If the Fragment is visible, it only loads once. OnActivityCreate will set this field to true, as follows:

  @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if(isFragmentVisible(this) && this.isAdded()){
            if (this.getParentFragment() == null || isFragmentVisible(this.getParentFragment())) {
                onLazyLoadData();
                isLoadData = true;
                if(isFirstVisible)
                    isFirstVisible = false; }}}Copy the code

The sample application

Here’s what I used in my project:

As you can see, when I click on this TAB, the Fragment loads the data.

conclusion

There are Fragment libraries that can do this, but it works the same way. We need to know why. The lazy loading class integrates scenario 1 and Scenario 2, and only has a few simple lines of code.

Reference article:

(3)

FragmentPagerAdapter and FragmentStatePagerAdapter distinction