An overview of the

Fragments are often used in development. Their appearance greatly liberates activities, making it easy to reuse code written in activities. Although Fragment was often used in the past, I did not delve deeply into how it was realized internally, nor did I delve deeply into the root cause of some problems encountered in the use. I take this opportunity to conduct an in-depth study on Fragment. The points involved are as follows

  • How is the Fragment lifecycle invoked
  • How are fragments stored and restored
  • Fragment and FragmentViewPagerNeed to pay attention to the use of
  • Some techniques Fragment uses

Fragment source code version: Androidx :1.1.0

What is the fragments

Fragment contains a View object that can be added to or removed from an Activity’s layout. It also has lifecycle, rollback, destruction and reconstruction mechanisms. These features make it a miniature Activity.

Advantages are:

  • Interface switching requires no interprocess communication, fast
  • It’s easy to reuse

Disadvantages are:

  • Use complex, life cycle thanActivityMore,FragmentIn theViewAnd of theFragmentThe life cycle is different
  • The reconstruction mechanism leads to error-prone use
  • Animation lag can occur if data rendering occurs during a transition animation, because data rendering causes a UI layout relayout

Fragment life cycle

The Fragment is held by the Activity, and the Activity calls the corresponding Fragment life cycle in each lifecycle method. Check the FragmentActivity code as follows:


//FragmentActivity.java

final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    mFragments.dispatchCreate(); // Distribute onCreate to Fragment
 }
 
 @Override
    protected void onStart(a) {
    mFragments.dispatchStart(); // Distribute onStart to Fragment}...// Other lifecycle methods are similar
Copy the code

Activities place fragment-related operations on the Fragment in the FragmentController. When the Activity lifecycle is called, the FragmentController distributes life-cycle events to the Fragment. We explore this in depth through the following common ways:

getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.container,new FragmentTest())
                .commit();
Copy the code

There are two related classes

  • FragmentManager
  • FragmentTransaction

FragmentManager

GetSupportFragmentManager () to get a FragmentManager object, it is used to manage fragments, contains all added to the current FragmentManager all fragments, fragments of the back stack, Call the Fragment life cycle, etc., but it’s an abstract class whose implementation is FragmentManagerImpl, focusing on a few useful member variables inside

// Add by addFragment or from the layout 
      
final ArrayList<Fragment> mAdded = new ArrayList<>();
/ / fragments back stack
ArrayList<BackStackRecord> mBackStack;
Copy the code

FragmentTransaction

GetSupportFragmentManager (). The beginTransaction () to get is a FragmentTransaction, it represents a transaction, said a series of actions to perform either complete, or are not complete, The meaning used in Fragment is as follows:

getSupportFragmentManager()
                .beginTransaction()
                .add()
                .add()
                .remove()
                .addToBackStack("")
                .commit();
Copy the code

We can add and remove multiple times at the same time in a single submission, and this series of operations is a transaction. When the code above is submitted, the back stack is added. At this time, clicking the back key on the mobile phone will perform the opposite operations, namely remove, remove and add, which shows the characteristics of the transaction. A transaction is a set of atomic operations that are indivisible as a whole, complete or incomplete, and can be rolled back to the state before completion

FragmentTransaction is also an abstract class whose implementation is BackStackRecord, which is the element stored in the fallback stack we saw above. There is one key variable

ArrayList<Op> mOps = new ArrayList<>();

static final class Op {
        int mCmd;
        Fragment mFragment;
}
Copy the code

MOps is a list of all actions in a transaction. MCmd represents operations on fragments, such as add, remove, etc. After commit(), mOps is traversed at a certain point in time to complete the response operation. This is the mechanism of transaction implementation.

Commit transactions We used commit above, which has a similar method

  • commit(): Submits via Handler, not immediately. If theonStoporonSaveInstanceStateThen the lift throws an exceptionthrow new IllegalStateException( "Can not perform this action after onSaveInstanceState");
  • commitAllowingStateLoss()And:commit()The difference with onStop or onSaveInstanceState is that the commit will execute normally without throwing an exception
  • commitNow(): compared with thecommit()The difference is that the commit is then executed immediately rather than through a Handler
  • commitNowAllowingStateLoss()Similar to:

Lifecycle callback

FragmentTransaction is simply a record of a transaction, and the final execution is to call the relevant FragmentManager methods to add, remove, show, hide, and so on. In the case of Add, after committing (), after passing a series of methods, call FragmentManager’s executeOps() to add the Fragment to the mAdded list mentioned above, and then call moveToState() to handle the fragment-related lifecycle. So let’s take a look before WE get into moveToState()

    static final int INITIALIZING = 0;     // Not yet created.
    static final int CREATED = 1;          // Created.
    static final int ACTIVITY_CREATED = 2; // Fully created, not started.
    static final int STARTED = 3;          // Created and started, not resumed.
    static final int RESUMED = 4;          // Created started and resumed.
Copy the code

So these are the current state of the Fragment, and one of the confusing points here is why the state is only resume when the answer is in moveToState,

void moveToState(Fragment f, int newState, int transit, int transitionStyle,
                     boolean keepActive) {
     // newState is the state of the FragmentManager that follows the Activity
    if (f.mState <= newState) { 
        switch (f.mState) {
            case Fragment.INITIALIZING:
                if (newState > Fragment.INITIALIZING) {
                    f.performAttach();
                    if (!f.mIsCreated) {
                        f.performCreate(f.mSavedFragmentState); 
                    }
                }

            case Fragment.CREATED:
                if (newState > Fragment.CREATED) {
                    ViewGroup container = null;
                    f.mContainer = container;
                    f.performCreateView(f.performGetLayoutInflater(
                            f.mSavedFragmentState), container, f.mSavedFragmentState);
                    if(f.mView ! =null) {
                        if(container ! =null) {
                            container.addView(f.mView);
                        }

                        f.onViewCreated(f.mView, f.mSavedFragmentState);
                    }
                    f.performActivityCreated(f.mSavedFragmentState);
                    if(f.mView ! =null) { f.restoreViewState(f.mSavedFragmentState); }}case Fragment.ACTIVITY_CREATED:
                if (newState > Fragment.ACTIVITY_CREATED) {
                    f.performStart();
                }

            case Fragment.STARTED:
                if(newState > Fragment.STARTED) { f.performResume(); }}}}Copy the code

Compare the current state of the Fragment with newState to determine what life state it should reach. Note that there is no break in the above case, so when the Fragment reaches its target state, it will call the corresponding life cycle method and finally reach the target state. We also see that after the onCreateView call, the View created by this method is added to the container containing the Fragment. The above is just to onResume status, there are still some codes not posted, take a look

		// newState is the state of the FragmentManager that follows the Activity
        else if (f.mState > newState) {  
            switch (f.mState) {
                case Fragment.RESUMED:
                    if (newState < Fragment.RESUMED) {
                        f.performPause();
                    }
                case Fragment.STARTED:
                    if (newState < Fragment.STARTED) {
                        f.performStop();
                    }
 
                case Fragment.ACTIVITY_CREATED:
                    if (newState < Fragment.ACTIVITY_CREATED) {
                        if(f.mView ! =null) {
                            if (mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) {
                                saveFragmentViewState(f);
                            }
                        }
                        f.performDestroyView();
                        if(f.mView ! =null&& f.mContainer ! =null) {
                            if (f.getParentFragment() == null| |! f.getParentFragment().mRemoving) { f.mContainer.removeView(f.mView); } } f.mContainer =null;
                        f.mView = null;
                        f.mViewLifecycleOwner = null;
                        f.mViewLifecycleOwnerLiveData.setValue(null);
                        f.mInnerView = null;
                        f.mInLayout = false;
                    }
                case Fragment.CREATED:
                    if (newState < Fragment.CREATED) {
                        if(f.getAnimatingAway() ! =null|| f.getAnimator() ! =null) {
                            newState = Fragment.CREATED;
                        } else {
                            booleanbeingRemoved = f.mRemoving && ! f.isInBackStack(); f.performDestroy(); }else{ f.mState = Fragment.INITIALIZING; } f.performDetach(); }}}Copy the code

After seeing DestroyView(), any View held in the Fragment will be null.

And then there’s another point,FragmentAs the otherFragmentThe container when the parentFragmentThe lifecycle is distributed to childrenFragment. So that’s just a quick overview of what I think are the key points,commit()The completion process is as follows:Above fromAndroidX Fragment1.2.2 Source code analysis

The Fragment life cycle has been briefly analyzed above, but a deeper understanding is still needed to read the source code

How are fragments stored and restored

We know that when an App is accidentally killed in the background not only will the Activity be rebuilt, but the original Fragment will also be restored. Let’s first look at when the Fragment is saved

	FragmentActivity.java

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        markFragmentsCreated();
        Parcelable p = mFragments.saveAllState();
        if(p ! =null) { outState.putParcelable(FRAGMENTS_TAG, p); }}Copy the code

In the FragmentActivity onSaveInstanceState() method, see Parcelable p = mfragments.saveAllState (); The Fragment state is saved here as follows

    @Nullable
    public Parcelable saveAllState(a) {
        return mHost.mFragmentManager.saveAllState();
    }
Copy the code

Final call FragmentManager. SaveAllState (), into the way we see the

Parcelable saveAllState(a) {
            // 1. Execute unfinished transactions and animations
            forcePostponedTransactions();
            endAnimatingAwayFragments();
            execPendingActions();

            mStateSaved = true;

            if (mActive.isEmpty()) {
                return null;
            }

            // 2. Save the Fragment in mActive
            int size = mActive.size();
            ArrayList<FragmentState> active = new ArrayList<>(size);
            boolean haveFragments = false;
            for (Fragment f : mActive.values()) {
            if(f ! =null) {

                haveFragments = true;

                FragmentState fs = new FragmentState(f);
                active.add(fs);
            }
        }

            ArrayList<String> added = null;
            BackStackState[] backStack = null;

            3. Save the Fragment in mAdded
            size = mAdded.size();
            if (size > 0) {
                added = new ArrayList<>(size);
                for(Fragment f : mAdded) { added.add(f.mWho); }}//4. Save the data in mBackStack
            if(mBackStack ! =null) {
                size = mBackStack.size();
                if (size > 0) {
                    backStack = new BackStackState[size];
                    for (int i = 0; i < size; i++) {
                        backStack[i] = new BackStackState(mBackStack.get(i));
                    }
                }
            }
			
            FragmentManagerState fms = new FragmentManagerState();
            fms.mActive = active;
            fms.mAdded = added;
            fms.mBackStack = backStack;
            if(mPrimaryNav ! =null) {
                fms.mPrimaryNavActiveWho = mPrimaryNav.mWho;
            }
            fms.mNextFragmentIndex = mNextFragmentIndex;
            return fms;
        }
Copy the code

The code above is a bit simplified, but the logic is simple: save existing data to FragmentManagerState as follows

  • savemActiveThe fragments
  • savemAddedThe fragments
  • savemBackStackThe data in the
  • Finally, save the data to aFragmentManagerStateObject,

MAdded and mBackStack are fragments that are added to the FragmentManager and returned to the stack, respectively. The mActive list is not clear, but it does not affect our understanding of the logic of saving.

Where the FragmentManagerState eventually save, return to see FragmentActivity. OnSaveInstanceState ()

@Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
     Parcelable p = mFragments.saveAllState();
        if(p ! =null) { outState.putParcelable(FRAGMENTS_TAG, p); }}Copy the code

As you can see, all fragments are stored in a Bundle object. This Bundle object is stored in the Bundle ICicle in the ActivityRecord managed by AMS through interprocess communication.

Fragment recover

The entry point for recovery is in fragmentActivit. onCreate(Bundle savedInstanceState)

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.attachHost(null /*parent*/);

        if(savedInstanceState ! =null) { Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); mFragments.restoreSaveState(p); }}Copy the code

Recovery is ultimately handled by the FragmentManager, and the detailed code is in void restoreSaveState(Parcelable State), where the logic is simple.

Is the Fragment really using the same object as before? There are two cases:

  • RetainInstance is set and the process is still running:

When RetainInstance is set, the Fragment is stored in nonConfig and will not be recreated.

  • RetainInstance not set, or the process has been killed:

In this case, there is no directly available Fragment, and reflection creates a new Fragment that must be a different object than the previous one.

From the daily asking | how fragments are stored and recovery? Have update

Precautions for using Fragments

  1. Ghosting problem

    The problem is that when the APP is rebuilt (after switching between vertical and horizontal screens or being killed due to lack of memory), it will find that another layer of fragment is added to the original fragment, resulting in gaping. The code is as follows

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    	getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.container, new FragmentTest())
                .commit();
    }
    Copy the code

    In normal cases, this is ok, but when the reconstruction occurs, according to the analysis of the Fragment recovery, the system has already done the restoration for us, and the above code also added the Fragment in the onCreate(), so it will cause the problem of adding the Fragment twice. Knowing the reason, we can correct it as follows

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    	if(savedInstanceState == null) {
            getSupportFragmentManager()
                    .beginTransaction()
                    .add(R.id.container, newFragmentTest()) .commit(); }}Copy the code
  2. Used in ViewPager

Let’s start with a common piece of code


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_pager);
        viewPager = findViewById(R.id.viewPager);
        List<BaseFragment> list = new ArrayList<>();
        list.add(new FragmentOne());
        list.add(new FragmentTwo());
        list.add(new FragmentThree());
        viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), list));
    }

    private class ViewPagerAdapter extends FragmentPagerAdapter {
        private List<BaseFragment> list;
        public ViewPagerAdapter(FragmentManager fm, List<BaseFragment> list) {
            super(fm);
            this.list = list;
        }
        @Override
        public Fragment getItem(int position) {
            return list.get(position);
        }
        @Override
        public int getCount(a) {
            returnlist.size(); }}Copy the code

The above code looks fine, but when the app is rebuilt, the Fragment in the list may not be the same Fragment in the ViewPagerAdapter

  • ViewPager loadedFragmentMust:listIs different from
  • ViewPager is not loadedFragment: Because will callgetItem()So withlistIn the same

Here’s why:

@Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);
        
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if(fragment ! =null) {
            mCurTransaction.attach(fragment);
        } else{ fragment = getItem(position); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); }...return fragment;
    }
Copy the code

If the FragmentPagerAdapter looks for a Fragment, the FragmentPagerAdapter gets it from the FragmentManager first. If it doesn’t find one, the FragmentPagerAdapter calls getItem(Position). When rebuilding, the FragmentManager will restore any fragments that have been added. For fragments that already exist, So don’t call getItem(position), which is naturally different from the Fragment added to the list in onCreate().

For FragmentStatePagerAdapter, so long as understands the distinction with FragmentPagerAdapter, can the same train of thought to understand.

  1. ViewPager updates the problem

When calling FragmentPagerAdapter. NotifyDataSetChanged (), found that ViewPager does not change the source discovered finally call the following code:

void dataSetChanged(a) {
	for (int i = 0; i < mItems.size(); i++) {
            final ItemInfo ii = mItems.get(i);
            final int newPos = mAdapter.getItemPosition(ii.object);

			// 1. Key points
            if (newPos == PagerAdapter.POSITION_UNCHANGED) {
                continue;
            }

            if (newPos == PagerAdapter.POSITION_NONE) {
                mItems.remove(i);
                i--;
                // 2. Key points
                mAdapter.destroyItem(this, ii.position, ii.object);
            }
        }
        requestLayout();
}
Copy the code

NewPos == pagerAdapter.position_unchanged skipped the loop, and the PagerAdapter did not override getItemPosition(). The default is POSITION_UNCHANGED, so the update must be reloaded getItemPosition() as follows

 @Override
        public int getItemPosition(Object object) {
           return POSITION_NONE;
        }
Copy the code

Madapter.destroyitem (this, iI.position, iI.object); For FragmentStatePagerAdapter, destroy FragmentManager will remove fragments, when need the location of the fragments, call the getItem () to obtain, This case ViewPager no cache fragments update is ok, but only after the FragmentPagerAdapterdestroy fragments will detach, Fragment instance has not been removed, Therefore, when data changes, you need to clear the Fragment managed by the FragmentManager to achieve the update effect.

Some techniques Fragment uses

  • Use one without an interfaceFragmentLifecycle related data can be obtained, for exampleLifeCycle,GlideEtc.
  • useFragmentTo dynamically obtain permissions

conclusion

  1. FragmentThe life cycle by itself andActivityTo determine the state of
  2. FragmentActivitytheonSaveInstanceStateIs saved in theAMSIs recovered when the application is rebuilt due to configuration changes or low memory killing
  3. FragmentMost of the common problems have to do with the reconstruction mechanism, and they can be handled by understanding the reconstruction mechanism

reference

  1. When the Fragment encounters the ViewPager
  2. Every day an Activity is rebuilt, why do you Fragment to live?
  3. Daily asking | how fragments are stored and recovery? Have update
  4. Ask | daily Activity and fragments of those things, “use no problem, I have to go, you crash?”
  5. AndroidX Fragment1.2.2 Source code analysis
  6. Can you really use a Fragment? Fragments FAQ and new postures for using fragments on androidx
  7. Use Fragment to gracefully apply for runtime permissions
  8. ViewPager (ViewPager, ViewPager, ViewPager, ViewPager)