Wednesday, July 28, 2021

 

Implementation of Fragment Lazy Loading in ViewPager2






Foreword
ViewPager2It is a new control that is officially launched. It can also be seen from the name that it is used to replace ViewPager. It is based onRecyclerViewRealized, so you can achieve some functions that ViewPager does not, the most practical point is to support vertical scrolling.
Although I heard about it very early, I have learned from some articles that some of the pits used by ViewPager2 have never been officially used. Not long ago ViewPager2 released the official version 1.0.0, I thought it was time to try it. Haha, probably because I have written two articles about lazy loading before, the first time I thought about it is not the use of the new features of ViewPager, but how to achieve lazy loading when working with Fragment. This article will specifically explore the lazy loading problem in ViewPager2. There have been many detailed articles on the use of ViewPager2, which is not the focus of this article, so it will not be specifically introduced.

Before embarking on the text, let’s emphasize,The analysis of this article is based on ViewPager2 version 1.0.0, Is under the androidx package, so you need to do the adaptation work of androidx before using ViewPager2.

Use ViewPager2 to load multiple fragments

The first step is to add the ViewPager2 dependency in the build.gradle file

implementation 'androidx.viewpager2:viewpager2:1.0.0'
  • 1

The second step, add ViewPager2 in the layout file

<?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">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager2"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

The third step, write Adapter
It should be noted that the Adapter class when loading Fragment in ViewPager2 needs to inherit fromFragmentStateAdapterInstead of FragmentStatePagerAdapter in ViewPager.

public class MyFragmentPagerAdapter extends FragmentStateAdapter {

    private List<Fragment> mFragments;

    public MyFragmentPagerAdapter(@NonNull FragmentActivity fragmentActivity, List<Fragment> fragments) {
        super(fragmentActivity);
        this.mFragments = fragments;
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        return mFragments.get(position);
    }

    @Override
    public int getItemCount() {
        return mFragments.size();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

The fourth step is to set the Adapter for ViewPager2

ViewPager2 mViewPager2 = findViewById(R.id.view_pager2);
List<Fragment> mFragments = new ArrayList<>();
mFragments .add(new FirstFragment());
mFragments .add(new SecondFragment());
mFragments .add(new ThirdFragment());
MyFragmentPagerAdapter mAdapter = new MyFragmentPagerAdapter(this, mFragments);
mViewPager2.setAdapter(mAdapter);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

After the above steps, we realized the use of ViewPager2 to load multiple Fragments, of course, I am here for a simple demonstration, I will not show the specific Fragment class.

Execution of life cycle methods when switching fragments

Next, let's take a specific look at the implementation of the lifecycle method when Fragment is switched. I added 6 Fragments in the test case, and print the execution status in the Fragment lifecycle callback method. The specific execution results are as follows:

  • The initial situation shows the first Fragment

It can be seen that only the first fragment is created at this time, and the life cycle method is executed.onResume(), Several other fragments are not created.

  • Switch to the second Fragment

At this time, a second fragment is created, and the life cycle method is also executed toonResume(), Meanwhile, the first Fragment is executedonPause()method.

  • Switch to the third Fragment

Same as the previous case, create a third fragment and execute toonResume()Method, while the second Fragment is executedonPause()method.

  • Switch to the fourth Fragment

Same as the previous two cases, the same is to create the current fragment, the life cycle method is executed toonResume()And the last Fragment is executedonPause()method. The difference is that the first fragment will be destroyed at this time and executed in turnonStop()onDestroyView()onDestroy()withonDetach()method.

  • Switch to the fifth Fragment

As in the previous case, a fifth fragment is created, and the life cycle method is executed toonResume(), The fourth Fragment is executedonPause()Method, while destroying the second Fragment.

  • Switch to the sixth (last) Fragment

It can be seen that the sixth fragment is created at this time, and the life cycle method is executed toonResume(), The fifth Fragment is executedonPause()Method, according to the execution results of the above two cases, the third Fragment should be destroyed at this time, but it is not.
From the implementation of the Fragment lifecycle method in the above cases, it is not difficult to see that ViewPager2 will not create the next Fragment in advance by default. But at the same time, the destruction of Fragment makes me a little puzzled. If we don't look at the situation of switching to the last Fragment, we can guess that because of the cache mechanism of RecyclerView inside ViewPager2, there can be up to three Fragments, but switch to The situation of the last Fragment violates our guess. Obviously, the previous Fragment is not destroyed at this time. Next, we will analyze several problems of ViewPager2 loading Fragment based on the above results.

ViewPager2setOffscreenPageLimit()method

Through the execution results in the example, we can find that ViewPager2 will not pre-load Fragments on both sides like ViewPager by default. This is why, we may think of a method related to preloading in ViewPager:setOffscreenPageLimit()This method is also defined in ViewPager2, let's take a look at the difference between them.
First look at thesetOffscreenPageLimit()method:

private static final int DEFAULT_OFFSCREEN_PAGES = 1;
private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;

public void setOffscreenPageLimit(int limit) {
    if (limit < DEFAULT_OFFSCREEN_PAGES) {
        Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
                + DEFAULT_OFFSCREEN_PAGES);
        limit = DEFAULT_OFFSCREEN_PAGES;
    }
    if (limit != mOffscreenPageLimit) {
        mOffscreenPageLimit = limit;
        populate();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

The method passes in an integer value, which indicates the number of preloads on both sides of the current Fragment. Many people may know that the default preload number of ViewPager is 1, which means that a Fragment on the left and right sides of the current Fragment will be created in advance. From the code, we can see that if the value we pass in is less than 1, the number of preloads will still be set to 1, which also causes the ViewPager to cancel the preload, and therefore the Fragment's lazy loading solution is needed.
Next, let’s take a look at thesetOffscreenPageLimit()method:

public static final int OFFSCREEN_PAGE_LIMIT_DEFAULT = -1;
private int mOffscreenPageLimit = OFFSCREEN_PAGE_LIMIT_DEFAULT;

public void setOffscreenPageLimit(@OffscreenPageLimit int limit) {
    if (limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT) {
        throw new IllegalArgumentException(
                "Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0");
    }
    mOffscreenPageLimit = limit;
    // Trigger layout so prefetch happens through getExtraLayoutSize()
    mRecyclerView.requestLayout();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

We can see that the default preload number mOffscreenPageLimit in ViewPager2 isOFFSCREEN_PAGE_LIMIT_DEFAULTThat is, -1, we can pass the default value or an integer greater than 1 to set the number of preloads. Next, let's take a look at where mOffscreenPageLimit is used. Through a global search, we can find the inner class in ViewPager2LinearLayoutManagerImplmiddlecalculateExtraLayoutSpace()Passed in the methodgetOffscreenPageLimit()The method obtained mOffscreenPageLimit.

@Override
protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,
                                         @NonNull int[] extraLayoutSpace) {
    int pageLimit = getOffscreenPageLimit();
    if (pageLimit == OFFSCREEN_PAGE_LIMIT_DEFAULT) {
        // Only do custom prefetching of offscreen pages if requested
        super.calculateExtraLayoutSpace(state, extraLayoutSpace);
        return;
    }
    final int offscreenSpace = getPageSize() * pageLimit;
    extraLayoutSpace[0] = offscreenSpace;
    extraLayoutSpace[1] = offscreenSpace;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

calculateExtraLayoutSpace()The method is defined inLinearLayoutManagerIn the extra space used to calculate the layout of LinearLayoutManager, that is, the space outside the display range of RecyclerView, the calculation result is stored in the extraLayoutSpace parameter, which is an integer array of length 2, extraLayoutSpace[0] represents the top/left Extra space, extraLayoutSpace[1] means extra space at the bottom/right side (depending on orientation). LinearLayoutManagerImpl rewrites this method, the method first judges the value of mOffscreenPageLimit, if it is equal to the default valueOFFSCREEN_PAGE_LIMIT_DEFAULT, Then directly call the parent class method without setting additional layout space; if the value of mOffscreenPageLimit is greater than 1, then set the additional space on both sides (or up and down) togetPageSize() * pageLimit, Equivalent to preloading the Fragment on both sides.
Seeing this, we know why ViewPager2 does not preload the fragments on both sides by default, because the default preload number is -1. Like ViewPager, we can callsetOffscreenPageLimit()Method, pass in a value greater than 1 to set the preload quantity.
In the previous example, we add the following code:

mViewPager2.setOffscreenPageLimit(1);
  • 1

The result printed when the first fragment is displayed for the first time is as follows:

It can be seen that ViewPager2 will create the next Fragment in advance, which is the same as the default situation of ViewPager.

Caching and prefetching mechanism in RecyclerView

Next, let's take a look at the destruction of Fragment, and explore why in the above example, ViewPager2 did not destroy the previous Fragment when switching to the last Fragment. Before this, we must first understand RecyclerView's caching mechanism and prefetching mechanism.
RecyclerView's caching mechanism is an old-fashioned issue, the core is in one of its internal classesRecyclerIn Recycler, the recycling and reuse of Item are all carried out by Recycler. The RecyclerView cache can be divided into multiple levels. Since I understand it very clearly, I will not introduce it in detail here. You can view related articles by yourself. Let's directly look at the cache related to Fragment recycling in ViewPager2-mCachedViews, Its type isArrayListThe ViewHolder corresponding to the item moved out of the screen will be cached in the container first. There is a member variable in the Recycler classmViewCacheMax, Indicating the maximum number of caches in mCachedViews, the default value is 2, we can call RecyclerViewsetItemViewCacheSize()Method to set the cache size.
Back to our specific scene, by viewingFragmentStateAdapterThe source code of the class, we can see that the ViewHolder type saved in mCachedViews at this time isFragmentViewHolder, The root layout of its view is a FrameLayout, Fragment will be added to the corresponding FrameLayout, so cache ViewHolder is actually equivalent to cache Fragment, for simplicity, I will later be called cache Fragment, everyone clearly said that it is not Just be accurate. In the above example, we used ViewPager2 to load 6 Fragments. When switching to the fourth Fragment, since only two Fragments can be cached at most, this timemCachedViewsThe second and third fragments are cached in the cache, so the first fragment will be destroyed, and the situation will be the same after switching to the fifth fragment. At this time, the third and fourth fragments will be cached, so The second fragment is destroyed. The next question is coming, if you follow this explanation, the third fragment should be destroyed when switching to the sixth fragment. The above example is obviously not, why?
This involves the prefetching of RecyclerView (Prefetch) Mechanism, it is a function officially introduced in the support v25 version package. The specific performance is that when the RecyclerView slides, the next Item will be preloaded. To be precise, the ViewHolder corresponding to the next Item will be created in advance. By default, the prefetch function is turned on, we can call the following code to turn off:

mRecyclerView.getLayoutManager().setItemPrefetchEnabled(false);
  • 1

So what effect will the prefetching mechanism have on the destruction of Fragment in ViewPager2, let's briefly analyze it from the perspective of source code. First look at RecyclerViewonTouchEvent()method:
RecyclerView's onTouchEvent() method

@Override
public boolean onTouchEvent(MotionEvent e) {
    // ...
    switch (action) {
        // ...
        case MotionEvent.ACTION_MOVE: {
            // ...
            if (mGapWorker != null && (dx != 0 || dy != 0)) {
                mGapWorker.postFromTraversal(this, dx, dy);
            }
        }
        break;
        // ...
    }
    // ...
    return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

You can see that mGapWorker will be called when RecyclerView slidespostFromTraversal()In this method, the horizontal and vertical displacements are passed in through parameters, which are used to calculate the prefetched Item position later. The type of mGapWorker isGapWorker, Let's see itpostFromTraversal()method:
GapWorker's postFromTraversal() method

/**
 * Schedule a prefetch immediately after the current traversal.
 */
void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {
    // ...
    recyclerView.post(this);
    // ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

From the comment of the method, we can also see that it is related to the prefetch of RecyclerView, and RecyclerView will be called inside the method.post()Method, the parameter is passed into this, which is the current GapWorker object. By looking at the definition of the GapWorker class, you can see that it implements Runnable, so here is to submit a task to the message queue of the main thread. Next we look at what GapWorker implementsrun()method:

@Override
public void run() {
    // ...
    long nextFrameNs = TimeUnit.MILLISECONDS.toNanos(latestFrameVsyncMs) + mFrameIntervalNs;
    prefetch(nextFrameNs);
    // ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Called inside the methodprefetch()Method, seeing the method name probably presumes that prefetch related logic will be performed next, let's look at it next.

void prefetch(long deadlineNs) {
  	// Build prefetch task
    buildTaskList();
  	// Start prefetch task
    flushTasksWithDeadline(deadlineNs);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

prefetch()The method will be called firstbuildTaskList()The method to construct the prefetching task is mainly to determine the position of the prefetching based on the horizontal and vertical displacements passed before, and then callflushTasksWithDeadline()Methods to perform prefetch tasks, we only look at herebuildTaskList()The method is just fine.

private void buildTaskList() {
    final int viewCount = mRecyclerViews.size();
    int totalTaskCount = 0;
    for (int i = 0; i < viewCount; i++) {
        RecyclerView view = mRecyclerViews.get(i);
        if (view.getWindowVisibility() == View.VISIBLE) {
            // key code
            view.mPrefetchRegistry.collectPrefetchPositionsFromView(view, false);
            totalTaskCount += view.mPrefetchRegistry.mCount;
        }
    }
    // ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Next, it will call mPrefetchRegistry in RecyclerViewcollectPrefetchPositionsFromView()Method, the type of mPrefetchRegistry isLayoutPrefetchRegistryImpl, It is an inner class in GapWorker, let's look at itscollectPrefetchPositionsFromView()method.

void collectPrefetchPositionsFromView(RecyclerView view, boolean nested) {
    mCount = 0;
    // ...
    final RecyclerView.LayoutManager layout = view.mLayout;
    if (view.mAdapter != null
            && layout != null
            && layout.isItemPrefetchEnabled()) {
        // ...
        // momentum based prefetch, only if we trust current child/adapter state
        if (!view.hasPendingAdapterUpdates()) {
            layout.collectAdjacentPrefetchPositions(mPrefetchDx, mPrefetchDy,
                    view.mState, this);
        }

        if (mCount > layout.mPrefetchMaxCountObserved) {
            layout.mPrefetchMaxCountObserved = mCount;
            layout.mPrefetchMaxObservedInInitialPrefetch = nested;
            view.mRecycler.updateViewCacheSize();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

Inside the method, first of all, the member variables in LayoutPrefetchRegistryImplmCountSet to 0, then passisItemPrefetchEnabled()Method to determine whether RecyclerView has prefetching enabled, which is enabled by default, and layout will be executed nextcollectAdjacentPrefetchPositions()Method, the layout here is the LayoutManager set by RecyclerView, we take LinearLayoutManager as an example, take a look at itcollectAdjacentPrefetchPositions()method.
LinearLayoutManager's collectAdjacentPrefetchPositions() method

@Override
public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
                                             LayoutPrefetchRegistry layoutPrefetchRegistry) {
    // ...
    collectPrefetchPositionsForLayoutState(state, mLayoutState, layoutPrefetchRegistry);
}

void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState,
                                            LayoutPrefetchRegistry layoutPrefetchRegistry) {
    // ...
    layoutPrefetchRegistry.addPosition(pos, Math.max(0, layoutState.mScrollingOffset));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

The method will be called againcollectPrefetchPositionsForLayoutState()Method, then call layoutPrefetchRegistryaddPosition()Method, layoutPrefetchRegistry here is from abovecollectPrefetchPositionsFromView()Passed in the method, you can see that the parameter is passed this, which is the LayoutPrefetchRegistryImpl object. We next look at LayoutPrefetchRegistryImpladdPosition()method:
The addPosition() method of LayoutPrefetchRegistryImpl

@Override
public void addPosition(int layoutPosition, int pixelDistance) {
    // ...
    mCount++;
}
  • 1
  • 2
  • 3
  • 4
  • 5

It can be seen that the method will increase mCount by 1, and the value of mCount becomes 1. Next we go backcollectPrefetchPositionsFromView()Method, look at the final judgment of the method.

if (mCount > layout.mPrefetchMaxCountObserved) {
    layout.mPrefetchMaxCountObserved = mCount;
    layout.mPrefetchMaxObservedInInitialPrefetch = nested;
    view.mRecycler.updateViewCacheSize();
}
  • 1
  • 2
  • 3
  • 4
  • 5

Here judged mCount andmPrefetchMaxCountObservedThe size relationship, mPrefetchMaxCountObserved is an integer variable defined in LayoutManager, the initial value is 0, so here will enter the if judgment. Then assign mCount to mPrefetchMaxCountObserved, then the value of mPrefetchMaxCountObserved becomes 1, and finally Recycler will be calledupdateViewCacheSize()Method, let's take a look at this method.
Recycler's updateViewCacheSize() method

void updateViewCacheSize() {
    int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0;
    mViewCacheMax = mRequestedCacheMax + extraCache;

    // first, try the views that can be recycled
    for (int i = mCachedViews.size() - 1;
         i >= 0 && mCachedViews.size() > mViewCacheMax; i--) {
        recycleCachedViewAt(i);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

The method first defines an integer variable extraCache, which is literally an additional cache, and its value is mPrefetchMaxCountObserved in the previous step, which is 1. The next step is important, willmRequestedCacheMax + extraCacheAssigned to mViewCacheMax, we mentioned earlier when introducing RecyclerView cache that mViewCacheMax represents the maximum number of mCachedViews. mRequestedCacheMax is the number of mCachedViews caches we set. The default value is 2, so the value of mViewCacheMax is set to 3, which is Say mCachedViews can save up to 3 ViewHolders (Fragment for our scene).
Seeing this, we can roughly understand the reason for the destruction of Fragment in the example. When switching from the first Fragment to the second Fragment, the prefetch logic we analyzed above will be executed. Set the maximum cache number of mCachedViews from 2 to 3 by default. For the case of switching to the third, fourth, and fifth Fragment, since the pre-fetched Fragment occupies a position in mCachedViews, it still appears to cache at most 2 Fragments.When switching to the sixth and final Fragment, there is no need to pre-fetch the next Fragment, but at this time the maximum cache number of mCachedViews is still 3, so the third Fragment can also be added to the cache, will not Was destroyed.
In order to verify the conclusion drawn, we first cancel the prefetch mechanism of RecyclerView inside ViewPager2 through the code:

((RecyclerView) mViewPager2.getChildAt(0)).getLayoutManager().setItemPrefetchEnabled(false);
  • 1

Then, let's run the previous sample program and directly look at the situation of switching to the last fragment.

It can be seen that when switching to the last Fragment, the third Fragment will be destroyed. At this time, the cached Fragment is the fourth and fifth. This is because we have turned off the prefetching mechanism. , In the implementation of LayoutPrefetchRegistryImplcollectPrefetchPositionsFromView()Method is not satisfiedlayout.isItemPrefetchEnabled()The condition is true, the following logic will not be executed, so the maximum cache number of mCachedViews is always 2, which verifies that our conclusion is correct.

Lazy loading scheme in ViewPager2

Since ViewPager2 does not preload Fragments on both sides by default, it is equivalent to lazy loading by default, so if we do not passsetOffscreenPageLimit()Method to set the pre-loading quantity, without any additional processing. However, for many cases of Fragment, the number of Fragments that RecyclerView in ViewPager2 can cache is limited, so it will cause multiple destruction and creation of Fragment, how to solve this problem? Let me introduce my solution below.
First set the number of ViewPager2 preloads, let ViewPager2 create all Fragments in advance, to prevent frequent destruction and creation caused by switching.

mViewPager2.setOffscreenPageLimit(mFragments.size());
  • 1

Through the execution of the life cycle method when the Fragment is switched in the previous example, it is not difficult to find that no matter whether the Fragment will be created in advance, it will only be executed when it is visible.onResume()Method, we can use this rule to achieve lazy loading, the specific implementation method and I have introduced beforeFragment lazy loading scheme in androidxThe same, let me briefly talk about it here.

  • Put the logic of Fragment loading data intoonResume()In the method, this ensures that the data will only be loaded when the fragment is visible.
  • Declare whether a variable tag is executed for the first timeonResume()Method, because every time Fragment changes from invisible to visible, it will be executedonResume()The method needs to prevent repeated loading of data.
    According to the above two points, we can encapsulate our lazy loading fragment, the complete code is as follows:
public abstract class LazyFragment extends Fragment {

    private Context mContext;
    private boolean isFirstLoad = true; // Whether to load for the first time

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = getActivity();
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = LayoutInflater.from(mContext).inflate(getContentViewId(), null);
        initView(view);
        return view;
    }

    @Override
    public void onResume() {
        super.onResume();
        if (isFirstLoad) {
            // Put the data loading logic in the onResume() method
            initData();
            initEvent();
            isFirstLoad = false;
        }
    }

    /**
           * Set layout resource id
     *
     * @return
     */
    protected abstract int getContentViewId();

    /**
           * Initialize the view
     *
     * @param view
     */
    protected void initView(View view) {

    }

    /**
           * Initialization data 
     */
    protected void initData() {

    }

    /**
           * Initialization event
     */
    protected void initEvent() {

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

Of course, this is just a plan that I think is better. If there is any problem in consideration, or everyone has their own opinions, they are welcome to put forward.

to sum up

This article explores the implementation of the lifecycle method when using ViewPager2 to load Fragment, and then derives the implementation of ViewPager2 lazy loading:
In simple terms, you can do nothing at all. ViewPager2 implements lazy loading by default. But if you want to avoid the overhead caused by Fragment frequent destruction and creation, you can passsetOffscreenPageLimit()Method to set the number of preloads, put the data loading logic into the FragmentonResume()Method.
Although the research object of this article is ViewPager2, but most of the article is analyzing RecyclerView, I have to sigh that RecyclerView is indeed a very important control. How to use it is basically familiar to everyone. Yes, but the things related to the principle are different. My understanding of RecyclerView is also very shallow. If I have time, I still need to learn more.

Reference article

https://www.programmersought.com/article/39134390787/

ViewPager2 major update, support offscreenPageLimit
Learn if you don’t move! Learn more about ViewPager2
RecyclerView preload mechanism source code analysis