series

Back to Jetpack: Dependencies and transitive relationships of Jetpack’s major components

Changes to using activities and fragments under AdroidX

Can you really use a Fragment? Fragments FAQ and new postures for using fragments on androidx

AndroidX Fragment1.2.2 Source code analysis

Fragment returns the stack preparation section

preface

Last time we introduced OnBackPressedDispather, today let’s take a formal look at the fragment return stack from a source code point of view. Because the main flow and life cycle are similar, this article will analyze the source code for returning stacks in detail and insert a lot of source code. It is recommended that you read this article once you are familiar with the lifecycle processes. Single-return stack and multi-return stack demos are provided at the end of this article

If you are not familiar with activities’ understanding of task stacks and return stacks, you can move to Tasks and the Back Stack

Do you have a lot of friends?

Before analyzing the source code, let’s consider a few questions.

  • What are the elements in the return stack?
  • Who manages the return stack of fragments?
  • How to return?

What are the elements in the return stack?

The return stack, as the name implies, is a stack structure. So we need to figure out what this stack structure actually stores.

As we all know, using the fragment return stack requires calling the addToBackStack(“”) method

In the Fragment Lifecycle from a source perspective article, we mentioned FragmentTransaction, which is a model of “transactions” that can be rolled back to their previous state. So when the return operation is triggered, the previously committed transaction is rolled back.

The FragmentTransaction implementation class is BackStackRecord, so the fragment return stack actually holds the BackStackRecord

As the return of the stack elements, realized BackStackRecord FragmentManager. BackStackEntry interface

From the definition of BackStackRecord, we can see that BackStackRecord has three identities

  • inheritedFragmentTransaction, that is, a transaction, which holds all operations of the entire transaction
  • To achieve theFragmentManager.BackStackEntry, as an element for the rollback
  • To achieve theOpGenerator, can generateBackStackRecordList, more on this later

Who manages the return stack of fragments?

Now that we know that the fragment return stack actually stores the BackSrackRecord, who manages the fragment return stack?

The FragmentManager is used to manage fragments, so the Fragment return stack should also be managed by the FragmentManager

//FragmentManager.java
ArrayList<BackStackRecord> mBackStack;
Copy the code

There are two ways to trigger the return logic of a fragment

  • Develop return methods that actively call fragments

  • Triggered when the user presses the back key

What is the return stack logic in fragments

How to return?

We already know that the element in the return stack is BackStackRecord, and we know that the FragmentManager manages the return stack. So if we were to implement “return” logic, what would we do?

It is important to understand that a “return” is a rollback of the transaction, that is, a corresponding “reverse operation” is performed on the internal logic of the COMMIT transaction.

For example,

AddFragment please – removeFragment

ShowFragment please – hideFragment

AttachFragment please – detachFragment

What about replace?

The expandReplaceOps method replaces replace (the target fragment has been added) with the corresponding remove and add operations, or just replace (the target fragment has not been added) with add operations

PopBackStack series methods

The popBackStack family of methods is provided in the FragmentManager

Does it look familiar? Commit transactions have a similar API, the COMMIT family of methods

The synchronous and asynchronous methods are provided here, and you may wonder why the same operations on a transaction, one committed and one rolled back, are encapsulated in the FragmentManager and the other in the FragmentTransaction. Since these are all operations on transactions, they should all be placed in the FragmentManager. I think it’s possible that for API ease of use, the FragmentManager opens the transaction in a single chain call. Feel free to leave your thoughts in the comments section.

PopBackStack (String name, int flag)

Name is an argument to addToBackStack(String name), which is used to find the specific element to unstack. Flag can be 0 or fragmentManager.pop_back_stack_inclusive. 0 means that only all elements above this element are popped, and POP_BACK_STACK_INCLUSIVE means that all elements above this element and above are popped. The pop-ups here contain all elements that rollback these transactions. If that sounds abstract, look at the picture

If flag is passed in 0, eject all elements on ♥2
childFragmentManager.popBackStack("Has".0)
Copy the code

//flag pops POP_BACK_STACK_INCLUSIVE and above elements
childFragmentManager.popBackStack("Has",  androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE)
Copy the code

Into the source code

1. PopBackStack () logic

Before looking at the return stack source code, let’s review how the FragmentManager commits a transaction to the Fragment lifecycle

Let’s take a look at the popBackStack source

Wait, this enqueueAction looks familiar…

It appears that the process for committing and rolling back a transaction is essentially the same, except that the action is passed differently

OpGenerator is an interface with a single generateOps method that generates a list of transactions and whether or not the transaction pops up. There are two implementation classes

Commit calls generateOps on BackStackRecord, popBackStack calls generateOps on PopBackStackState

The logic of the former is simple: add data to the Records List and pass false to the isRecordPop list

records.add(this);
isRecordPop.add(false);
Copy the code

The latter has slightly more complicated logic and internally calls the popBackStackState method

If the popBackStack method is used, remove the top of the FragmentManager return stack list (mBackStack) and pass true to all isRecordPop lists

int last = mBackStack.size() - 1;
records.add(mBackStack.remove(last));
isRecordPop.add(true);
Copy the code

If the name or ID passed in has a value and flag is 0, find the first position in the return stack that matches the name or ID and add all backstackRecords above that position to the Record list. All isRecordPop lists are passed true

index = mBackStack.size() - 1;
while (index >= 0) {
    BackStackRecord bss = mBackStack.get(index);
    if(name ! =null && name.equals(bss.getName())) {
        break;
    }
    if (id >= 0 && id == bss.mIndex) {
        break;
    }
    index--;
}

for (int i = mBackStack.size() - 1; i > index; i--) {
  records.add(mBackStack.remove(i));
  isRecordPop.add(true);
}
Copy the code

If the name or ID passed in has a value and flag is POP_BACK_STACK_INCLUSIVE, the last fetched position is iterated until the bottom of the stack or an unmatched exit loop is encountered, and all BackstackRecords are pushed out of the stack

// the index operation is the same as above. The first position in the return stack (in reverse order) matches the name or ID
if((flags & POP_BACK_STACK_INCLUSIVE) ! =0) {
    index--;
    // Continue traversing the mBackStack until the bottom of the stack or a mismatched exit loop is encountered
    while (index >= 0) {
        BackStackRecord bss = mBackStack.get(index);
        if((name ! =null && name.equals(bss.getName()))
                || (id >= 0 && id == bss.mIndex)) {
            index--;
            continue;
        }
        break; }}// The following stack logic is the same as above
Copy the code

It can be understood with the above GIF

The logic is basically the same for both loading and unloading, except for branching according to isPop, which calls executePopOps

As we mentioned earlier, the “return” logic is actually the “reverse operation” of the internal operation logic of the commit transaction

The following logic is clear: perform the corresponding inverse operation according to the mCmd

void executePopOps(boolean moveToState) {
    for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
        final Op op = mOps.get(opNum);
        Fragment f = op.mFragment;
        switch (op.mCmd) {
            case OP_ADD:
                mManager.removeFragment(f);
                break;
            case OP_REMOVE:
                mManager.addFragment(f);
                break;
            case OP_HIDE:
                mManager.showFragment(f);
                break;
            case OP_SHOW:
                mManager.hideFragment(f);
                break;
            case OP_DETACH:
                mManager.attachFragment(f);
                break;
            case OP_ATTACH:
                mManager.detachFragment(f);
                break;
            case OP_SET_PRIMARY_NAV:
                mManager.setPrimaryNavigationFragment(null);
                break;
            case OP_UNSET_PRIMARY_NAV:
                mManager.setPrimaryNavigationFragment(f);
                break;
            case OP_SET_MAX_LIFECYCLE:
                mManager.setMaxLifecycle(f, op.mOldMaxState);
                break;
            default:
                throw new IllegalArgumentException("Unknown cmd: " + op.mCmd);
        }
        if(! mReorderingAllowed && op.mCmd ! = OP_REMOVE && f ! =null) { mManager.moveFragmentToExpectedState(f); }}if(! mReorderingAllowed && moveToState) { mManager.moveToState(mManager.mCurState,true); }}Copy the code

The logic is exactly the same

2. How does a fragment intercept an activity’s return logic?

OnBackPressedDispatcher is a Fragment for Jetpack’s OnBackPressedDispatcher

The onBackPressed logic of the activity is divided into two parts. It checks whether all the registered OnBackPressedCallback has enabled, and if so, intercepts it without executing subsequent logic.

Otherwise the execution mFallbackOnBackPressed. The run (), its internal logic to invoke ComponentActivity onBackPressed method of the parent

So we just need to see how mOnBackPressedCallbacks (ArrayDeque<OnBackPressedCallback) are added and when is isEnabled set to true

A lookup shows that it calls addCallback in the attachController of the FragmentManager

 mOnBackPressedDispatcher.addCallback(owner,mOnBackPressedCallback)
Copy the code

And then executed

The mOnBackPressedCallback is set to false when initialized

IsEnadbled will return true if the number of stacks is greater than 0 and its mParent is PrimaryNavigation

The assignment to the return stack (mBackStack) is in the BackStackRecord’s generateOps method, and whether it is added to the return stack is controlled by the mAddToBackStack, a Boolean property

MAddToBackStack’s assignment is in the addToBackStack method, which explains why calling the addToBackStack method adds a transaction to the return stack, right

To sum up, the fragment intercepting activity returns the stack using OnBackPressedDispatcher. If you start a transaction and call addToBackStack, The isEnabled property of mOnBackPressedCallback is set to true, thus intercepting the activity’s return logic. After interception, the popBackStackImmediate method is executed

The popBackStack method will call the popBackStackState to construct the records and isRecordPop list, and the internal elements of isRecordPop will all be true and the subsequent process will be the same as committing the transaction, Execute the executePopOps or executeOps method depending on the isRecordPop value

The realization of single return stack and multi return stack

Ian Lake in Fragments: Past, Present, and Future (Android Dev Summit ’19)

There is mention of a future API for multiple return stacks

So how do you implement multiple return stacks with existing apis?

If you want to implement multiple return stacks, you’ll need multiple FragmentManagers. If you want to implement multiple return stacks, you’ll need multiple FragmentManagers. Multiple FragmentManagers correspond to multiple fragments

Therefore, we can create multiple host frament as navigation fragments so that we can use separate fragmentmanagers of different host fragments to manage their respective return stacks. If this is a bit abstract, see the following figure

In this diagram, there are four return stacks. There is an external host fragment, and inside there are four navigation fragments that manage their internal return stacks. The external host is responsible for coordinating how to switch to another return stack when each return stack is empty

It’s easy to just add the return stack on the same FragmentManager

Refer to demo for details

About me

I am a Fly_with24

  • The Denver nuggets

  • Jane’s book

  • Github