Q: How to implement lazy loading of fragments in ViewPager?

When asked, the first thing that comes to mind is setUserVisiblity

As follows, onVisible is called when the Fragment is visible to load asynchronously

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (getUserVisibleHint()) {
        isVisible = true;
        onVisible();
    } else {
        isVisible = false; onInVisible(); }}Copy the code

That would have been OK two years ago, but it might not be OK today in 2021.

Android-review.googlesource.com/c/platform/…

AndroidX has added a new method for FragmentTransaction called setMaxLifeCycle from 1.1.0 to Alpha07, which is recommended to replace setUserVisibleHint. This will provide the following benefits:

  1. Lifecycle based lazy loading is more scientific and can be used in MVVM architectures with components such as Livedata
  2. SetMaxLifeCycle does not require an additional Fragment base class to be used without incursion

Use setMaxLifecycle for lazy loading

FragmentPagerAdapter constructor of a new behaviors parameters, when set to FragmentPagerAdapter. BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, The life cycle of the Fragment is limited by setMaxLifecycle: onResume() is executed only when the Fragment is displayed on the screen.

This allows lazy loading of data and so on to be done in onResume().

The code is as follows:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle? , persistentState:PersistableBundle?). {
        super.onCreate(savedInstanceState, persistentState)
        setContentView(R.layout.activity_main)
        val viewPager: ViewPager = findViewById(R.id.viewpager)
        val fragmentList: MutableList<Fragment> = ArrayList()
        fragmentList.add(Fragment1())
        fragmentList.add(Fragment2())
        fragmentList.add(Fragment3())
        / / set FragmentPagerAdapter for MyPagerAdapter adapter. BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT parameters
        val myPagerAdapter: MyPagerAdapter = MyPagerAdapter(
            getSupportFragmentManager(),
            FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, fragmentList
        )
        viewPager.setAdapter(myPagerAdapter)
        // Set the preload to 3 pages to test if lazy loading succeeds
        viewPager.offscreenPageLimit = 3
    }


    class MyPagerAdapter(
        fm: FragmentManager,
        behavior: Int.val fragmentList: List<Fragment>
    ) :
        FragmentPagerAdapter(fm, behavior) {

        override fun getCount(a) = fragmentList.size
        override fun getItem(position: Int) = fragmentList[position]

    }
}
Copy the code

The FragmentPagerAdapter calls setMaxLifecycle based on the behavior after the Fragment is created.

//FragmentPagerAdapter.java

 public FragmentPagerAdapter(@NonNull FragmentManager fm,
        @Behavior int behavior) {
    mFragmentManager = fm;
    mBehavior = behavior;
}

@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {...if(fragment ! = mCurrentPrimaryItem) { fragment.setMenuVisibility(false);
        // new logic is used when mBehaviour is 1
        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            // Limit the lifecycle of an item to STARTED when it is initialized
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
        } else {
            // Compatible with older logic
            fragment.setUserVisibleHint(false); }}return fragment;
}

@Override
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    Fragment fragment = (Fragment)object;
    if(fragment ! = mCurrentPrimaryItem) {if(mCurrentPrimaryItem ! =null) {
            mCurrentPrimaryItem.setMenuVisibility(false);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                ...
                // Lifecycle will become non-main item and set it to STARTED
                mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
            } else {
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
        }
        fragment.setMenuVisibility(true);
        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            ...
            Lifecycle of the newly dropped main item is RESUMED
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
        } else {
            fragment.setUserVisibleHint(true); } mCurrentPrimaryItem = fragment; }}Copy the code

Without behavior, it is equivalent to call setMaxLifecycle directly when you build Framgent in a custom Adapter.

SetMaxLifecycle implementation principles

SetMaxLifecycle is easy to use, so take a look at the source code to see how it works (1.3.0-RC01), and you’ll feel comfortable if the interviewer asks you how it works.

OP_SET_MAX_LIFECYCLE

We know that all operations of FramgentTransition on fragments will be converted into an Op. There is also a new Op for setMaxLifecycle — OP_SET_MAX_LIFECYCLE, which sets the upper limit of the lifecycle.

@NonNull
public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
        @NonNull Lifecycle.State state) {
    addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
    return this;
}
Copy the code

When FramgentTransition adds OP_SET_MAX_LIFECYCLE to Frament, In the implementation class BackStackRecord, FragmentManager iterates through the Op list of a Transaction

void executeOps(a) {
    final int numOps = mOps.size();
    for (int opNum = 0; opNum < numOps; opNum++) {
        final Op op = mOps.get(opNum);
        final Fragment f = op.mFragment;
        / /...
        switch (op.mCmd) {
            / /...
            // The new Op type introduced here sets the upper limit of the Fragment's allowed lifetime
            case OP_SET_MAX_LIFECYCLE:
                mManager.setMaxLifecycle(f, op.mCurrentMaxState);
                break;
            / /...}}Copy the code

When OP_SET_MAX_LIFECYCLE is encountered, the mMaxState of the fragment is set by calling the setMaxLifeCycle method of the FragmentManager to mark its lifecycle upper limit

void setMaxLifecycle(@NonNull Fragment f, @NonNull Lifecycle.State state) {
    / /...
    f.mMaxState = state;
}
Copy the code

FragmentStateManager

FragmentManager Advances the Fragment life cycle through the FragmentStateManager. Push the process according to mMaxState on the life cycle

It’s worth noting that FragmentStateManager is a new class that has been added since 1.3.0-Alpha08. It pulls the original state-related logic out of FragmentManager, reducing its coupling to fragments and making it more monolithic.

Take a look at how the Fragment life cycle is specifically advanced in the FragmentStateManager:

void moveToExpectedState(a) {
    try{...// Loop computes to declare whether the cycle can advance
        while((newState = computeExpectedState()) ! = mFragment.mState) {if (newState > mFragment.mState) {
                // The life cycle moves forward
                int nextStep = mFragment.mState + 1;
                / /...
                switch (nextStep) {
                    / /...
                    case Fragment.ACTIVITY_CREATED:
                        / /...
                    case Fragment.STARTED:
                        start();
                        break;
                    / /...
                    case Fragment.RESUMED:
                        resume();
                        break; }}else {
                // If the due lifecycle is smaller than the current one, back up
                int nextStep = mFragment.mState - 1;
                / /...
                switch (nextStep) {
                   // Similar to the switch above
                   / /...}}}... }... }Copy the code
int computeExpectedState(a) {
    // Other expected state logic to calculate maxState
    / /...
    
    // mMaxState limits the lifecycle
    switch (mFragment.mMaxState) {
        case RESUMED:
            break;
        case STARTED:
            maxState = Math.min(maxState, Fragment.STARTED);
            break;
        case CREATED:
            maxState = Math.min(maxState, Fragment.CREATED);
            break;
        default:
            maxState = Math.min(maxState, Fragment.INITIALIZING);
    }
    
    // Other expected state logic to calculate maxState
    // ...
    return maxState;
}
Copy the code

The overall flow chart is as follows

The last

In addition to using the default BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, We can even set the Fragment’s MaxLifecycle to CREATED in the instantiateItem of our custom Adapter, This allows the Fragment to go only onCreate and delay more operations, such as the inflate in onCreateView and some operations in onViewCreated. Fragment 1.3.0-RC01 Supports setting the maximum life cycle to INITIALIZED