Reprint address: http://blog.csdn.net/yuanzeyao/article/details/52895029
If you like my article, you can pay attention to the left. WeChat Public number, I will regularly push articles and new ones.
In the previous article, we analyzed Fragment's life cycle in detail, and explained how Activity controls Fragment's life cycle. According to the plan, this article will analyze the life logic of add,replace,remove,hide,show and other api s, if you haven't read it yet. Fragment Operating Mechanism Analysis (I) So I suggest you read it first.
Before we analyze the source code, let's see how we use these APIs in our daily life. For example, add:
FragmentManager mFragmentManager = getSupportFragmentManager();
mFragmentManager.beginTransaction()
.add(R.id.fl_container,HomeFragment.newInstance(), HomeFragment.class.getSimpleName())
.commit();
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
We found that adding a Fragment to Activity is very simple, just four steps:
- Get the Fragment Manager in Activity, and through the analysis of the previous article, we know that the Fragment Manager Impl object is returned.
- Calling beginTransaction () returns a FragmentTransaction, which is just an abstract class. We will analyze its subclasses later.
- Call the add method, the first parameter Fragment will add the container id, the second parameter is to add the Fragment, the third parameter is the tag of the Fragment, after setting the tag, you can find the Fragment through the method findFragmentByTag, mainly for the automatic recovery of the Fragment.
-
Execute the commit method.
Similarly, APIs such as replace,remove,hide,show are invoked in exactly the same way (of course, sometimes addToBackStack is also called).
Next, let's start with step 2 and look at what each step does. Go to Fragment Manager Impl and see the beginTransaction method logic:
@Override
public FragmentTransaction beginTransaction() {
return new BackStackRecord(this);
}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
As you can see here, the implementation class of FragmentTransaction is BackStackRecord. Let's look at the inheritance relationship of this kind.
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, Runnable
- 1
- 2
- 1
- 2
You will find that BackStackRecord not only inherits the FragmentTransaction class, but also implements the Runnable interface, indicating that BackStackRecord should be a thread.
After getting FragmentTransaction in Step 2, we call the add method, and we go to the add method of BackStackRecord: (Note:add method has multiple overloads, but the logic is basically the same, so I will only analyze one of them)
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
doAddOp(containerViewId, fragment, tag, OP_ADD);
return this;
}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
The original add method only calls a doAddOp method, and we can guess from the name that the function of this method is to add an Add. Operation. The add operation returns this description to support chain invocation.
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
fragment.mFragmentManager = mManager;
if (tag != null) {
if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
throw new IllegalStateException("Can't change tag of fragment "
+ fragment + ": was " + fragment.mTag
+ " now " + tag);
}
fragment.mTag = tag;
}
if (containerViewId != 0) {
if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
throw new IllegalStateException("Can't change container ID of fragment "
+ fragment + ": was " + fragment.mFragmentId
+ " now " + containerViewId);
}
fragment.mContainerId = fragment.mFragmentId = containerViewId;
}
Op op = new Op();
op.cmd = opcmd;
op.fragment = fragment;
addOp(op);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
Next, we will analyze this method carefully.
- Save the Fragment Manager Impl object to Fragment.mFragment Manager.
- Check whether Fragment has set a tag, and throw an exception if it has been set and does not match the tag in the parameter. If not, save the tag.
- Check Fragment, if the containerId in Fragment is no longer equal to 0 and is inconsistent with the containerId of the parameter, then throw an exception, and if it is equal to 0, save the containerId
- Create an Op object in which the cmd and Fragment parameters are stored, where cmd The value of OP_ADD.
-
Call the addOp method and pass in the Op object created above.
Let's follow the lead and get into addOp.
void addOp(Op op) { if (mHead == null) { mHead = mTail = op; } else { op.prev = mTail; mTail.next = op; mTail = op; } op.enterAnim = mEnterAnim; op.exitAnim = mExitAnim; op.popEnterAnim = mPopEnterAnim; op.popExitAnim = mPopExitAnim; mNumOp++; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
Learned data structure Students can see at a glance that Op objects are added to the end of the list. ok, add method is finished. The original add method just created an "add operation" and put it at the end of the list. Next, we analyze the last step: commit method:
public int commit() {
return commitInternal(false);
}
- 1
- 2
- 3
- 1
- 2
- 3
It's too simple to go directly into commitInternal without explanation.
int commitInternal(boolean allowStateLoss) {
if (mCommitted) throw new IllegalStateException("commit already called");
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Commit: " + this);
LogWriter logw = new LogWriter(TAG);
PrintWriter pw = new PrintWriter(logw);
dump(" ", null, pw, null);
}
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
The logic of this method is also very clear: first, check whether the commit method has been executed, and if this commit method is executed many times, an exception will be thrown. Determine whether addBackToStack has been invoked (we do not call it here), if so, Fragment Manager needs to assign an index, otherwise return - 1, and finally call mManager.enqueueAction(this, AllowStateLoss; Method.
Here we see that if we want to see the relevant Log of Fragment Manager during debugging, we can call the enableDebugLogging method of Fragment Manager to open the Log and filter it through Fragment Manager.
Next, go to the Fragment Manager Impl. enqueueAction method.
public void enqueueAction(Runnable action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
synchronized (this) {
if (mDestroyed || mHost == null) {
throw new IllegalStateException("Activity has been destroyed");
}
if (mPendingActions == null) {
mPendingActions = new ArrayList<Runnable>();
}
mPendingActions.add(action);
if (mPendingActions.size() == 1) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
This method is also very simple, first check whether the state is allowed to be lost, here we pass in false, so first call checkStateLoss, this method I will not analyze, in fact, check whether the onSaveInstanceState method has been executed, if it is executed, then an exception will be thrown. After the check is passed, Runnable is added to a list of mPending Actions to be executed (based on the previous analysis, we know that this Runnable is the BackStackRecord object). Then put mExecCommit into Handler. In fact, mExecCommit is also a Runnable. Its run method is as follows:
Runnable mExecCommit = new Runnable() {
@Override
public void run() {
execPendingActions();
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
The run method calls the execPendingActions method with the following logic:
/**
* Only call from main thread!
*/
public boolean execPendingActions() {
if (mExecutingActions) {
throw new IllegalStateException("Recursive entry to executePendingTransactions");
}
if (Looper.myLooper() != mHost.getHandler().getLooper()) {
throw new IllegalStateException("Must be called from main thread of process");
}
boolean didSomething = false;
while (true) {
int numActions;
synchronized (this) {
if (mPendingActions == null || mPendingActions.size() == 0) {
break;
}
numActions = mPendingActions.size();
if (mTmpActions == null || mTmpActions.length < numActions) {
mTmpActions = new Runnable[numActions];
}
mPendingActions.toArray(mTmpActions);
mPendingActions.clear();
mHost.getHandler().removeCallbacks(mExecCommit);
}
mExecutingActions = true;
for (int i=0; i<numActions; i++) {
mTmpActions[i].run();
mTmpActions[i] = null;
}
mExecutingActions = false;
didSomething = true;
}
if (mHavePendingDeferredStart) {
boolean loadersRunning = false;
for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null && f.mLoaderManager != null) {
loadersRunning |= f.mLoaderManager.hasRunningLoaders();
}
}
if (!loadersRunning) {
mHavePendingDeferredStart = false;
startPendingDeferredFragments();
}
}
return didSomething;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
Actually, it's to execute BackStackRecord, right, so we need to go into the run method of BackStackRecord and study it. The run method is quite long, and there is a switch statement in it:
case OP_ADD: {
Fragment f = op.fragment;
f.mNextAnim = enterAnim;
mManager.addFragment(f, false);
} break;
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
The case I posted is the api of the corresponding OP_ADD operation. From the previous analysis, we can see that when we execute the add operation, the final call is the FragmentManager.addFragment method.
public void addFragment(Fragment fragment, boolean moveToStateNow) {
if (mAdded == null) {
mAdded = new ArrayList<Fragment>();
}
if (DEBUG) Log.v(TAG, "add: " + fragment);
makeActive(fragment);
if (!fragment.mDetached) {
if (mAdded.contains(fragment)) {
throw new IllegalStateException("Fragment already added: " + fragment);
}
mAdded.add(fragment);
fragment.mAdded = true;
fragment.mRemoving = false;
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
}
if (moveToStateNow) {
moveToState(fragment);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
The main work of addFragment is as follows:
-
Put the current fragment into the mActivie list and assign an index, which is achieved by calling makeActive
void makeActive(Fragment f) { if (f.mIndex >= 0) { return; } if (mAvailIndices == null || mAvailIndices.size() <= 0) { if (mActive == null) { mActive = new ArrayList<Fragment>(); } f.setIndex(mActive.size(), mParent); mActive.add(f); } else { f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1), mParent); mActive.set(f.mIndex, f); } if (DEBUG) Log.v(TAG, "Allocated fragment index " + f); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
-
Put fragment into the mAdd list. Since the second parameter of addFragment is passed in false, the moveToState method will not be executed at this time. (MoveToState has been analyzed in detail in the previous article.) When will this method be executed? Let's go back to the run method of BackStackRecord and call it at the end.
mManager.moveToState(mManager.mCurState, transition, transitionStyle, true); if (mAddToBackStack) { mManager.addBackStackState(this); }
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
OK, so far you have been very clear about the detailed add operation. Using the same analysis method, let's look at the logic of remove. You will find that remove is also the run method of BackStackRecord, which corresponds to the following case:
case OP_REMOVE: {
Fragment f = op.fragment;
f.mNextAnim = exitAnim;
mManager.removeFragment(f, transition, transitionStyle);
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
The FragmentManager.removeFragment method is called:
public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
final boolean inactive = !fragment.isInBackStack();
if (!fragment.mDetached || inactive) {
if (mAdded != null) {
mAdded.remove(fragment);
}
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
}
fragment.mAdded = false;
fragment.mRemoving = true;
moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
transition, transitionStyle, false);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- removeFragment First checks if this fragment has called addBackToStack, and if not, inactive is true
-
Check whether fragment.mDetached or inactive is true, if true, remove fragment from the mAdded list and call the moveToState method
Some students may wonder if moveToState will execute twice: both BackStackRecord.run and removeFragmnet methods will be called, but if you analyze carefully, you will find that when removeFragment executes, Fragment will be removed from mActive, so when moving ToState executes the second time, Fragm will be found. null, so don't do anything.
As for other APIs (replace, hide, show) You can analyze it yourself, but the logic is the same. I won't analyze it here.