[Android] Fragment source code analysis (SI) Fragment stack Management
The Fragment stack is a brilliant part of Fragment management. Its essential difference with the Activity stack lies in:
1. Fragment management is in the process space
2. Fragment management is generally performed in a Window.
Fragment management is easy to understand in a process space, because we know that Activity management is actually relatively complicated. Its management is called through IPC, and the end of IPC is our Client, as a Server, the Ams service is used. Activity management is based on Windows, while Fragment management is generally based on views in the same Window. In my opinion, Fragment management is undoubtedly the gospel of Android, because it is more lightweight and relatively faster. In addition, this Fragment registration can be implemented without registering AndroidManifest. xml, which means you can implement a very good plug-in system.
Maybe you still don't understand why zimo needs to be so strong at the beginning. It's because zimo wants everyone to try to manage the code structure to Fragment. Of course, I would like to remind you that Fragment is not a View, not a control, but not a View. It is a container that is lighter than Activity.
Back to the topic in this chapter, Fragment stack management, you may not be able to intuitively understand what the Fragment stack is. Let's introduce a piece of code:
FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction(); fragment = new TestFragment1(); ft.add(R.id.fragmentContainer, fragment, "test"); ft.setTransition( FragmentTransaction.TRANSIT_FRAGMENT_OPEN); ft.addToBackStack("test"); ft.commitAllowingStateLoss();
In the previous chapter, we wrote the Fragment transaction management. We know that the transaction we submit will be executed in the next UI thread message, I marked the addToBackStack code in red. In fact, the name parameter is dispensable. It is just an identifier During dump. Now we have a breakpoint on this method. We know that when we call this method, the direct result is that when we press Back, it will return to our previous transaction. We know that the Back button is processed in the onBackPressed callback of the Activity.
android.support.v4.app.FragmentManager:@Override public boolean popBackStackImmediate() { checkStateLoss(); executePendingTransactions(); return popBackStackState(mActivity.mHandler, null, -1, 0); }
FragmentActivity:/*** Take care of popping the fragment back stack or finishing the activity * as appropriate. */public void onBackPressed () {if (! MFragments. popBackStackImmediate () {finish ();}}
FragmentManager will call executePendingTransactions once during PopStack. As we mentioned in the previous chapter, the transaction-based Fragment model will store transactions in the queue, this method is to execute all the transactions in the queue. Fragment Transaction Management adopts a memorandum, so all your operations are recorded in its own data structure, and every data operation is reversible. This is one of the two points of Fragment, that is, when you perform the add operation, there must be a remove operation corresponding to it. Such operations are recorded in the popFromBackStack method of BackStackRecord. Let's first look at this part of logic:android.support.v4.app.BackStackRecord:public void popFromBackStack(boolean doStateMove) { ...switch (op.cmd) { case OP_ADD: { Fragment f = op.fragment; f.mNextAnim = op.popExitAnim; mManager.removeFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); }}
As I can see, in popFromBackStack, BackStackRecord performs a reverse operation on its records. This is why it can be used for Fragment management when you roll back the Fragment stack. Let's talk about FragmentManager later. To implement the Fragment rollback, we must first record the entire Fragment call process, and also call back the pop method of the BackStackRecord corresponding to the Fragment. One of the Fragment call entries is in boolean popBackStackState (Handler handler, String name, int id, int flags:boolean popBackStackState(Handler handler, String name, int id, int flags) { if (mBackStack == null) { return false; } if (name == null && id < 0 && (flags & POP_BACK_STACK_INCLUSIVE) == 0) { int last = mBackStack.size() - 1; if (last < 0) { return false; } final BackStackRecord bss = mBackStack.remove(last); bss.popFromBackStack(true); reportBackStackChanged(); } else { int index = -1; if (name != null || id >= 0) { // If a name or ID is specified, look for that place in // the stack. 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--; } if (index < 0) { return false; } if ((flags & POP_BACK_STACK_INCLUSIVE) != 0) { index--; // Consume all following entries that match. while (index >= 0) { BackStackRecord bss = mBackStack.get(index); if ((name != null && name.equals(bss.getName())) || (id >= 0 && id == bss.mIndex)) { index--; continue; } break; } } } if (index == mBackStack.size() - 1) { return false; } final ArrayList
states = new ArrayList
(); for (int i = mBackStack.size() - 1; i > index; i--) { states.add(mBackStack.remove(i)); } final int LAST = states.size() - 1; for (int i = 0; i <= LAST; i++) { states.get(i).popFromBackStack(i == LAST); } reportBackStackChanged(); } return true; }
We can see that the data structure of Fragment management Fragment storage is: mBackStack object. Its type is a strongly typed ArrayList. We can easily guess that it uses a linear table to simulate the Stack data structure. The blue part of the code is better understood, get the last state directly, and then end Fragment's management by calling back its pop method. Some may be confused. Fragment already has records in FragmentManager. Why should we create another BackStackRecord object to record it? In fact, this question is very similar to Activity management. The most intuitive answer I can give you is that the focus is different. The focus of FragmentManager is to manage the Fragment status, backStackRecord aims to record Fragment operations. To help you understand the logic of the red part, I will introduce a piece of code first:if (v == view1) { FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction(); fragment = new TestFragment1(); ft.add(R.id.fragmentContainer, fragment, "test"); ft.setTransition( FragmentTransaction.TRANSIT_FRAGMENT_OPEN); ft.addToBackStack("test"+index); ft.commitAllowingStateLoss(); index ++; } else { this.getSupportFragmentManager().popBackStack("test2", FragmentManager.POP_BACK_STACK_INCLUSIVE); }
When you add 10 Fragment entries to the BackStack and pop to the fragment at the test2 position, it will clear all the records after test2 in the BackStack, set the clearTop or startup parameters of the Activity. Of course, the flags and startup modes of the Intent of the Activity are a kind of thing, but they are packaged. We can get back to the Code through the phenomenon. It is nothing more than obtaining the corresponding BackStackRecord, recording it in a List, and then eliminating it in batches.Well, I believe you have a basic understanding of Fragment Stack management, but we still have no questions about how Fragment joins the Stack. We call back the addToStack method of BackStackRecord:
public FragmentTransaction addToBackStack(String name) { if (!mAllowAddToBackStack) { throw new IllegalStateException( "This FragmentTransaction is not allowed to be added to the back stack."); } mAddToBackStack = true; mName = name; return this; }Here, BackStackRecord is set to true for mAddToBackStack. An index number is assigned during Commit:int commitInternal(boolean allowStateLoss) { if (mCommitted) throw new IllegalStateException("commit already called"); mCommitted = true; if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(this); } else { mIndex = -1; } mManager.enqueueAction(this, allowStateLoss); return mIndex; }
In fact, the method for assigning an Index to a Manager is very simple:public int allocBackStackIndex(BackStackRecord bse) { synchronized (this) { if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) { if (mBackStackIndices == null) { mBackStackIndices = new ArrayList
(); } int index =
mBackStackIndices.size(); mBackStackIndices.add(bse); return index; } else { int index = mAvailBackStackIndices .remove(mAvailBackStackIndices.size() - 1); mBackStackIndices.set(index, bse); return index; } } }
Here there are two variables: mAvailBackStackIndices and mBackStackIndices. In fact, we can simply understand these two variables. When we pop out the Fragment, it will store its index in the mAvailBackStackIndices queue, when we need to apply for an index, if mAvailBackStackIndices exists, the index value stored in this object will be returned.