From the perspective of source code, why does Fragment overlap occur?

The version of the support package analyzed in this article is 23.3.0, and the Fragment overlap bug mentioned in the official article has been fixed on 24.0.0 and above.

In the process of using Fragment, we sometimes find that the Fragment that has been performing normally suddenly overlaps!

Under what circumstances will Fragment overlap occur?

Generally, the following two conditions are met to overlap:

1. A page restart occurred (forcibly killed and restarted due to rotating screen, insufficient memory, etc.).
2. Repeat replace| addFragment or use show, hidecontrol Fragment;

Why does Fragment overlap occur?

From the perspective of source code, why does the overlap occur after the page restarts? (When loading Fragment in add mode)

We know that there is a onSaveInstanceState()method in the Activity, which will be called back when the Activity is about to be killed (for example, it will be called when it enters the background, before the screen rotates, or jumps to the next Activity).

When the Activity only executes the onPause method (transparent Activity), if the targetVersion set by the App is greater than 11, the onSaveInstanceState method will not be executed.

At this time, the system saves a Bundle type of data for us. We can manually save some data such as playback progress according to our own needs. Then, if the page restarts, we can get the data in or to restore the status of playback progress onRestoreInstanceState(). onCreate().

The reason for Fragment overlap is related to the mechanism of saving state. The general reason is that the system saves the state of Fragment for us before the page restarts, but when it is restored after restarting, the visible state of the view is not saved for us, and Fragment The default is the show state, so Fragment overlap occurs.

Analysis:
Let's first look at the relevant source code of FragmentActivity:

public class FragmentActivity extends ... {
    
    
    final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        ...省略
        if (savedInstanceState != null) {
    
    
            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
            mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
    
    
        super.onSaveInstanceState(outState);
        Parcelable p = mFragments.saveAllState();
        ...省略
    }
}

As can be seen from the above source code, FragmentActivity does help us save the state of Fragment, and it will help us restore it after the page restarts!

Among them mFragmentsis FragmentController, which is a Controller that indirectly controls FragmentManagerImpl internally through FragmentHostCallback.
The relevant code is as follows:

public class FragmentController {
    
    
    private final FragmentHostCallback<?> mHost;

    public Parcelable saveAllState() {
    
    
        return mHost.mFragmentManager.saveAllState();
    }

    public void restoreAllState(Parcelable state, List<Fragment> nonConfigList) {
    
    
        mHost.mFragmentManager.restoreAllState(state, nonConfigList);
    }
}

public abstract class FragmentHostCallback<E> extends FragmentContainer {
    
    
    final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
}

It can be seen from the above code that FragmentController controls the recovery work through the FragmentManagerImpl object in FragmentHostCallback.

Let's see what FragmentManagerImpl does:

final class FragmentManagerImpl extends FragmentManager {
    
    
    Parcelable saveAllState() {
    
    
        ...省略 详细保存过程
        FragmentManagerState fms = new FragmentManagerState();
        fms.mActive = active;
        fms.mAdded = added;
        fms.mBackStack = backStack;
        return fms;
    }

    void restoreAllState(Parcelable state, List<Fragment> nonConfig) {
    
    
        // 恢复核心代码
        FragmentManagerState fms = (FragmentManagerState)state;
        FragmentState fs = fms.mActive[i];
        if (fs != null) {
    
    
            Fragment f = fs.instantiate(mHost, mParent);
    }
}

We have saveAllState()seen the key saving code. It turns out that the state of the Fragment, the subscript of the Fragment stack where it is located, and the state of the rollback stack are saved through FragmentManagerState.

When restoring, the Fragment is restored restoreAllState()through the method of FragmentState in FragmentManagerState (see the analysis below to understand)instantiate()

Let's look at FragmentManagerState:

final class FragmentManagerState implements Parcelable {
    
    
    FragmentState[] mActive;           // Fragment状态
    int[] mAdded;                      // 所处Fragment栈下标
    BackStackState[] mBackStack;       // 回退栈状态
    ...
}

We only look at FragmentState, which also implements Parcelable and saves the Fragment's class name, subscript, id, Tag, ContainerId, and Arguments:

final class FragmentState implements Parcelable {
    
    
    final String mClassName;
    final int mIndex;
    final boolean mFromLayout;
    final int mFragmentId;
    final int mContainerId;
    final String mTag;
    final boolean mRetainInstance;
    final boolean mDetached;
    final Bundle mArguments;
    ...

    //  在FragmentManagerImpl的restoreAllState()里被调用
    public Fragment instantiate(FragmentHostCallback host, Fragment parent) {
    
    
        ...省略
        mInstance = Fragment.instantiate(context, mClassName, mArguments);
    }
}

So far, we understand that the Fragment saved by the system for us actually exists in the form of FragmentState.

At this point, let's think about why Fragment overlap occurs after the page restarts? In fact, the answer is already obvious. According to the above source code analysis, we will find that there is no Hidden state field in FragmentState!

The Hidden state corresponds to Fragment mHidden, the value defaults to false...

public class Fragment ... {
    
    
    boolean mHidden;
}

I think you should understand that in the scenario where the Fragment is loaded in the add mode, when the system restores the Fragment, , that is, the mHidden=falseshow state, so that after the page is restarted, the Fragment in the Activity is displayed in the show state, and if you do not For processing, Fragment overlap will occur!

Why does repeating replace| addFragment or using show, hidecontrol Fragment cause overlap?
  • **Repeat replace| addFragment**
    We know that there are 2 ways to load Fragment: replace()and add().
    Either way, repeated loading of Fragments will cause overlap. This is well understood. Of course, if you load the same Fragment twice, it will overlap; the question is where do we repeatedly load Fragments?
    Under normal circumstances, we will load the root Fragment in the Activity onCreate()or Fragment onCreateView(). If there is no judgment of page restart here, it may lead to repeated loading of Fragments and cause overlap. The correct way of writing should be:
  @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        ...
        // 判空, Fragment同理
        if(findFragmentByTag(RootFragment) == null){
    
    
              // 这里replace或add 根Fragment
        }
    }

Here you must saveInstanceState==nullload the Fragment at the right time, because after the above analysis, when the page is restarted, the state of the Fragment will be saved and restored, and loading the Fragment at this time will repeat the loading, which will cause the stack to already have the Fragment. Load a Fragment, resulting in overlapping!

  • Use show, hidecontrol Fragment
    When we use show(), hide()we use addthe way to load Fragment, add and hide make the view of Fragment change to GONE state; and replace is to destroy the view of Fragment.
    When the page is restarted, all Fragments of add will go through the life cycle and create a view; while the non-top Fragments of replace will not go through the life cycle, and only when Back, will they go through the life cycle of the Fragments on the top of the stack one by one to create a view.

Combined with the above source code analysis, when using replace to load Fragment, after the page restarts, the Fragment view has not been created yet, so it is mHiddenmeaningless and no overlapping will occur;
while using add to load, the views exist and are superimposed together. After the page is restarted mHidden=false, all Fragments will be displayed in the show state (ie VISIBLE), resulting in overlapping Fragments!

Final & Solution

Through the above analysis, I think friends should fully understand the reason for Fragment overlap!

Reprint: https://www.jianshu.com/p/78ec81b42f92

Guess you like

Origin blog.csdn.net/gqg_guan/article/details/130084409