series

【 back to Jetpack】 The main components of Jetpack dependencies and transfer relationships 【 back to Jetpack】 The changes in using Activities and Fragments under Android x 【 Back to Jetpack】 Do you really use fragments? Fragments FAQ and new postures for using fragments on androidx

preface

I have seen a lot of source code analysis of the article, often affixed a large section of code, this way is easy to interrupt the reader’s ideas, so many times read this kind of article lamenting good good article, but feel what did not remember, or silently join the collection but I do not know when to carefully study.

So this article will not introduce the details of the source code too much, more is to throw a brick to attract jade, if you read this article can follow the ideas of this article after their own look at the source code BELIEVE that you will not have my experience.

This article assumes that you have an understanding of the life cycle of a fragment, and understand its origin and responsibilities. This part of the basic content can be moved to the fragment official document

So instead of talking about “what”, we’ll talk about “how” and talk about “why”.

Androidx Fragment = androidX fragment

Androidx Fragment official source code address

This article is based on androidX Fragment 1.2.2 source analysis

implementation "Androidx. Fragments: fragments - KTX: 1.2.2." "
Copy the code

This article mainly introduces the fragment startup process, other content, such as return stack, will be updated later, please pay attention to. Feel free to discuss it in the comments section below. In this paper, the demo

Now that we know what, let’s think about how.

Thinking before analysis

Consider this: We know that the life cycle of a fragment is closely related to the life cycle of its host activity. In other words, each lifecycle callback of an activity causes a similar callback for each fragment.

So, if we were to implement such an operation, what would we do?

Guess: At each node in the activity lifecycle, manipulate the Fragment to execute the corresponding lifecycle method.

The idea has, carries on the confirmation of a few details below.

  1. Activities need to be able to operate on fragments, and fragments can also operate on fragments, so we need to abstract a model for managing fragments
  2. An activity is a set of actions that operate on a fragment and should be reversible to each other. For example, after adding a fragment, it should also be possible to remove the fragment
  3. Each group operation of an activity on a fragment should not be single. For example, you can add two fragments in different positions of the activity in one operation. At the same time, the operation should also meet 2 and be reversible

For the first one, we abstract a model that can manage fragments and add a relationship between superiors and subordinates. That is, activities can manage their internal fragments, and fragments can also manage their internal fragments. So fragments act as both the manager and the managed

For those of you who have studied databases in college, one structure will come to mind: 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

Thus, two of the most important concepts in fragments have emerged, FragmentManager and FragmentTransaction

FragmentManager encapsulates methods for handling fragments, addFragment removeFragment, etc. The FragmentActivity operates on the FragmentManager through the FragmentController

FragmentTransaction Encapsulates a fragment operation performed on a Fragment container. For example, adding a fragment to container 1 and replacing the fragment in container 2.

They are abstract classes that require concrete implementation classes.

The FragmentManager implementation class is FragmentManagerImpl, and its internal logic has been moved to FragmentManager. It is an empty implementation.

The Implementation class for FragmentTransaction is BackStackRecord, which internally references an instance of FragmentManager and overwrites the parent class’s four commit related methods.

The seemingly simplest startup process

Now let’s look at some code that normally fills a fragment in an activity like this

 override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        // Avoid overlapping fragments in scenes such as rotating screens
        if (savedInstanceState == null) {
            supportFragmentManager/ / step 1
                .beginTransaction()/ / step 2
                .add(R.id.container, BlankFragment.newInstance())/ / step 3
                .commitNow()/ / step 4}}Copy the code
  • Step 1, instantiate the FragmentManagerImpl object (some internal conversion, see source code or see demo notes for details)

  • Step 2, instantiate the BackStackRecord object and pass in the FragmentManager instance in the constructor

  • In step 3, invoke the transaction method to perform operations on the Fragment container. In this example, add BlankFragment to the Container

  • Step 4, commit the transaction to FragmentManager for processing

In Terminal, type ADB shell setprop log.tag.FragmentManager VERBOSE to enable the FragmentManager log function and filter the FragmentManager logs as follows:

The green part is the log manually added by the author, and the gray and blue parts are the logs in the fragment source code

Based on the log flow, our guess seems to be correct: “At each node of the activity lifecycle, operate the fragment to execute the corresponding lifecycle method.”

This is interfering because we create and commit FragmentTransaction in the activity’s onCreate method. What if we call the FragmentTransaction in onResume?

WTF!!!!

Maybe our guess is wrong? It seems that the life process of calling commitNow fragments is spontaneous

What if we move the call to onPause?

Start the activity and press the home button

I know curious readers will give it a try in onStop and be surprised. Manual funny.

From these logs, it can be seen that the fragment spontaneously enters its own lifecycle flow after committing a transaction, and that the fragment lifecycle changes when its host activity lifecycle changes.

If this is a bit abstract, we can look at the log that displays the fragment in onPause. When the fragment enters the onStart life cycle, it should enter onResume if it is normal. But since the Activity enters onStop by pressing the home button, the Fragment also enters onStop

So we’re going to expand on our previous guess:

  1. At each node in the activity lifecycle, operate the Fragment to execute the corresponding lifecycle method
  2. After a FragmentTransaction is committed, the fragment enters its own lifecycle process, subject to 1

So our source code interpretation starts from two directions

Activity Operates the Fragment life cycle

An activity operates on a Fragment by manipulating the FragmentManager with the FragmentController.

Specifically, each activity lifecycle node calls each dispatch-method in the FragmentController and then each dispatch-method in the FragmentManager

//FragmentActivity.java
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

// The following code omits some logic
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    mFragments.dispatchCreate();
}

@Override
protected void onStart(a) {
    mFragments.dispatchStart();
}

//onResume completes the callback
@Override
protected void onPostResume(a) {
    mFragments.dispatchResume();
}
@Override
protected void onPause(a) {
    mFragments.dispatchPause();
}

@Override
protected void onStop(a) {
    mFragments.dispatchStop();
}

@Override
protected void onDestroy(a) {
    super.onDestroy();
    mFragments.dispatchDestroy();
}
Copy the code

So guess 1 is confirmed

The activity passes through each lifecycle nodeFragmentControllerIndirect invocationFragmentManagerThe variousdispatch-Method to affect the fragment’s life cycle

What about nested fragments?

Nested fragments should also be used by the host using various dispatch-methods in the FragmentManager. With this in mind we can look at calls to the dispatch-methods in the FragmentManager

You can see that there are two calls here, the second for the activity to be invoked indirectly through the FragmentController, and the first using mChildFragmentManager

This brings up two other important concepts in fragments,getParentFragmentManager()getChildFragmentManager()

Note: requireFragmentManager() and getFragmentManager are deprecated

GetChildFragmentManager () gets the mChildFragmentManager in the Fragment

GetParentFragmentManager () gets the mFragmentManager in the Fragment

MChildFragmentManager is the fragmentManager inside the fragment

// Private fragment manager for child fragments inside of this one.
@NonNull
FragmentManager mChildFragmentManager = new FragmentManagerImpl();
Copy the code

MFragmentManager is a little more complicated,

  1. If the fragment’s direct host is an activity, the fragment is returned in the activitygetSupportFragmentManager()The returnedfragmentManager
  2. If the immediate host of a fragment is a fragment, that is, the fragment is a child fragment of another fragment, then the parent fragment is returnedgetChildFragmentManager

So the life cycle of nested fragments is when the parent fragment passes through each lifecycle nodemChildFragmentManagercalldispatch-To affect the life cycle of its child fragments

This concludes our first part of the interpretation, and stops here, some of the details need to look at the source code yourself

The Fragment life cycle is autonomous

In the seemingly simplest startup process section, we observe the fragment lifecycle log by starting and committing transactions in onCreate, onResume, and onPause of the activity.

The fragment life cycle is autonomous without activity interference.

So let’s think about another question

How does the Fragment lifecycle execute sequentially?

From the above log, we can see a lot of “moveto-” logs,

We can go on to hazard a guess that one lifecycle node ends and then calls a method that goes to another lifecycle node

Based on this speculation, we confirm some details

A fragment should have its own state, either managing its own internal state, or a dedicated state-managing abstraction that encapsulates the logic of state transition

Another concept is introduced here, the FragmentStateManager

FragmentStateManager References the mFragment that holds the Fragment and the FragmentManager status mFragmentManagerState

The fragment state is:

static final int INITIALIZING = -1;    // Not yet attached.
static final int ATTACHED = 0;         // Attached to the host.
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

The FragmentStateManager also encapsulates methods for fragment state transitions, for example:

void activityCreated(a) {
    if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
        Log.d(TAG, "moveto ACTIVITY_CREATED: " + mFragment);
    }
    mFragment.performActivityCreated(mFragment.mSavedFragmentState);
}
void start(a) {
    if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
        Log.d(TAG, "moveto STARTED: " + mFragment);
    }
    mFragment.performStart();
}
Copy the code

The core logic for fragment lifecycle autonomy is encapsulated in void moveToState(@nonNULL Fragment F, int newState) in FragmentManager.

void moveToState(@NonNull Fragment f, int newState) {
    FragmentStateManager fragmentStateManager = mFragmentStore.getFragmentStateManager(f.mWho);

    newState = Math.min(newState, fragmentStateManager.computeMaxState());
    if (f.mState <= newState) {
        switch (f.mState) {
            case Fragment.INITIALIZING:
                if (newState > Fragment.INITIALIZING) {
                    fragmentStateManager.attach(mHost, this, mParent);
                }
            case Fragment.ATTACHED:
                if (newState > Fragment.ATTACHED) {
                    fragmentStateManager.create();
                }
            case Fragment.CREATED:
                if (newState > Fragment.INITIALIZING) {
                    fragmentStateManager.ensureInflatedView();
                }
                if (newState > Fragment.CREATED) {
                    fragmentStateManager.createView(mContainer);
                    fragmentStateManager.activityCreated();
                    fragmentStateManager.restoreViewState();
                }
            case Fragment.ACTIVITY_CREATED:
                if (newState > Fragment.ACTIVITY_CREATED) {
                    fragmentStateManager.start();
                }
            case Fragment.STARTED:
                if(newState > Fragment.STARTED) { fragmentStateManager.resume(); }}}}Copy the code

Note: there is no break in the switch

If you are a careful reader, you may notice that the fragment state is only resumed.

We can look at the dispatchPause method in FragmentManager

void dispatchPause(a) {
    dispatchStateChange(Fragment.STARTED);
}
Copy the code

Why dispatch STARTED? In fact, I’ve streamlined some of the moveToState method, leaving only the f.mState <= newState logic, i.e. the newState of dispatch is greater than or equal to the current State

And now a new state of dispatch is smaller than the current state value, then came the following logic, such as the current status of RESUMED, new transfer status to STARTED, performed fragmentStateManager. Pause ();

void moveToState(@NonNull Fragment f, int newState) {
    FragmentStateManager fragmentStateManager = mFragmentStore.getFragmentStateManager(f.mWho);

    newState = Math.min(newState, fragmentStateManager.computeMaxState());
    if (f.mState <= newState) {
    	/ / to omit...
    }else if (f.mState > newState) {
        switch (f.mState) {
            case Fragment.RESUMED:
                if (newState < Fragment.RESUMED) {
                    fragmentStateManager.pause();
                }
                
            case Fragment.STARTED:
                if (newState < Fragment.STARTED) {
                    fragmentStateManager.stop();
                }
                
            case Fragment.ACTIVITY_CREATED:
                if (newState < Fragment.ACTIVITY_CREATED) {
                    if (isLoggingEnabled(Log.DEBUG)) {
                        Log.d(TAG, "movefrom ACTIVITY_CREATED: " + f);
                    }
                    if(f.mView ! =null) {
                        if (mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) { fragmentStateManager.saveViewState(); }}if (mExitAnimationCancellationSignals.get(f) == null) {
                        destroyFragmentView(f);
                    } else{ f.setStateAfterAnimating(newState); }}case Fragment.CREATED:
                if (newState < Fragment.CREATED) {
                    booleanbeingRemoved = f.mRemoving && ! f.isInBackStack();if (beingRemoved || mNonConfig.shouldDestroy(f)) {
                        makeInactive(fragmentStateManager);
                    } else {
                        if(f.mTargetWho ! =null) {
                            Fragment target = findActiveFragment(f.mTargetWho);
                            if(target ! =null&& target.getRetainInstance()) { f.mTarget = target; }}}if(mExitAnimationCancellationSignals.get(f) ! =null) {
                        f.setStateAfterAnimating(newState);
                        newState = Fragment.CREATED;
                    } else{ fragmentStateManager.destroy(mHost, mNonConfig); }}case Fragment.ATTACHED:
                if(newState < Fragment.ATTACHED) { fragmentStateManager.detach(mNonConfig); }}}}Copy the code

Note: the switch still does not break

There is a detail here. Since the activity does not have an onDestroyView life cycle, the dispatchDestroyView in FragmentController is not called

The activity destroy method calls dispatchDestroy via fragmentController and calls dispatchStateChange(fragment.initializing) internally, The mState of the fragment is ACTIVITY_CREATED, so the moveToState method will go to the ACTIVITY_CREATED case and execute to the end

This completes the fragment’s life cycle for the simplest scenario

conclusion

Let’s conclude: Activities and fragments are called on the fragment’s parentFragmentManager (or childFragmentManager of the parent fragment and the activity’s) at each lifecycle node SupportFragmentManager) to influence the life cycle of the child fragment, which also has its own life cycle call chain (from state A to state B)

I have to say that many of the fragment apis are not very useful, as evidenced by the frequency of updates on androidx fragments. For example, if the life cycle of a view in a fragment is inconsistent with that of the fragment itself, there will be an onDestroyView case where the fragment does not destroy it

Ian Lake mentioned in Fragments: Past, Present, and Future (Android Dev Summit ’19) that the two will be officially merged in the Future, and the use of Fragments will be much simpler

I’ll refer to The Android Lifecycle Cheat Sheet — Part III: Fragments and The commit FragmentTransaction brain map I drew (slightly crude) to help you understand

The Android Lifecycle Cheat Sheet — Part III: Fragments

Again, it is highly recommended that you take a look at the source code for yourself

20200317 update

Future moveToState will pull away from the FragmentManager, see issuetracker.google.com/issues/1395…

Demo

Demo

About me

I am a Fly_with24

  • The Denver nuggets

  • Jane’s book

  • Github