- Fragment 1.3.4 version of this analysis, different versions of the source will be different!
- Fragment official document
dependencies {
val fragment_version = "1.3.4"
// Java language implementation
implementation("androidx.fragment:fragment:$fragment_version")
// Kotlin
implementation("androidx.fragment:fragment-ktx:$fragment_version")
// Testing Fragments in Isolation
debugImplementation("androidx.fragment:fragment-testing:$fragment_version")}Copy the code
- Fragment, whether used alone or with Viewpager, is very familiar to Android development. However, the life cycle of Fragment is generally a picture seen on the Internet.
- The Fragment lifecycle when called, how to commit after the lifecycle of each method, need to see the source code to know, this article analyzed.
1.supportFragmentManager
- SupportFragmentManager is available only if you use FragmentActivity.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.test_activity3_activity)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, TestActivity3Fragment.newInstance())
.commitNow()
}
}
Copy the code
1.1 getSupportFragmentManager
- MFragments in FragmentActivity are not Fragment arrays. They are fragmentControllers. This is important to remember.
/ * FragmentActivity class * /
/ / create FragmentController
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
/** * Return the FragmentManager for interacting with fragments associated * with this activity. */
@NonNull
public FragmentManager getSupportFragmentManager(a) {
return mFragments.getSupportFragmentManager();
}
Copy the code
1.2 FragmentController class
- Pass in new HostCallbacks() and get SupportFragmentManager.
public class FragmentController {
private finalFragmentHostCallback<? > mHost;/**
* Returns a {@link FragmentController}.
*/
@NonNull
public static FragmentController createController(@NonNullFragmentHostCallback<? > callbacks) {
return new FragmentController(checkNotNull(callbacks, "callbacks == null"));
}
private FragmentController(FragmentHostCallback
callbacks) {
mHost = callbacks;
}
/** * Obtain FragmentManager from mHost * Returns a {@link FragmentManager} for this controller.
*/
@NonNull
public FragmentManager getSupportFragmentManager(a) {
returnmHost.mFragmentManager; }}Copy the code
1.3 FragmentHostCallback class
- Get the mFragmentManager from FragmentHostCallback.
- FragmentManagerImpl inherits from FragmentManager, but does nothing.
public abstract class FragmentHostCallback<E> extends FragmentContainer {
@Nullable private final Activity mActivity;
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
private final int mWindowAnimations;
final FragmentManager mFragmentManager = newFragmentManagerImpl(); }// Inherit FragmentManager, but do nothing
class FragmentManagerImpl extends FragmentManager {}Copy the code
- Get a fragmentManager here.
2.beginTransaction
- Each time a Fragment transaction is processed, a new BackStackRecord fallback stack is created and can only be used once.
@NonNull
public FragmentTransaction beginTransaction(a) {
return new BackStackRecord(this);
}
Copy the code
2.1 FragmentTransaction class
- The BackStackRecord class inherits the abstract FragmentTransaction class, which has the very important individual OP constants, as well as the inner OP class.
- Add,hide,remove, and so on are also in this class, and they are all wrapped in Op and stored in the array mOps.
public abstract class FragmentTransaction {
static final int OP_NULL = 0;
static final int OP_ADD = 1;
static final int OP_REPLACE = 2;
static final int OP_REMOVE = 3;
static final int OP_HIDE = 4;
static final int OP_SHOW = 5;
static final int OP_DETACH = 6;
static final int OP_ATTACH = 7;
static final int OP_SET_PRIMARY_NAV = 8;
static final int OP_UNSET_PRIMARY_NAV = 9;
static final int OP_SET_MAX_LIFECYCLE = 10;
.
.
ArrayList<Op> mOps = new ArrayList<>();
}
Copy the code
2.2 the Op class
- The Op class is important for add,hide,remove, etc.
static final class Op {
int mCmd;
Fragment mFragment;
int mEnterAnim;
int mExitAnim;
int mPopEnterAnim;
int mPopExitAnim;
Lifecycle.State mOldMaxState;
Lifecycle.State mCurrentMaxState;
Op() {
}
.
.
}
Copy the code
2.3 BackStackRecord class
- This class is the one that actually performs the commit transaction, which passes itself along to the FragmentManager.
- Also implements FragmentManager OpGenerator interface.
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry.FragmentManager.OpGenerator {
final FragmentManager mManager;
@Override
public int commit(a) {
return commitInternal(false);
}
@Override
public int commitAllowingStateLoss(a) {
return commitInternal(true);
}
@Override
public void commitNow(a) {
disallowAddToBackStack();
mManager.execSingleAction(this.false);
}
@Override
public void commitNowAllowingStateLoss(a) {
disallowAddToBackStack();
mManager.execSingleAction(this.true); }}Copy the code
3.add,hide,replace,remove
3.1 Packaging, addOp
- Here is the analysis of one replace, the others are similar.
- All you need to do here is wrap the constant for the id, Fragment, and tag operations in the Op class.
/*FragmentTransaction*/
/**
* Calls {@link #replace(int, Fragment, String)} with a null tag.
*/
@NonNull
public FragmentTransaction replace(@IdRes int containerViewId, @NonNull Fragment fragment) {
return replace(containerViewId, fragment, null);
}
@NonNull
public FragmentTransaction replace(@IdRes int containerViewId, @NonNull Fragment fragment,
@Nullable String tag) {
// Determine the validity of the data
if (containerViewId == 0) {
throw new IllegalArgumentException("Must use non-zero containerViewId");
}
doAddOp(containerViewId, fragment, tag, OP_REPLACE);
return this;
}
void doAddOp(int containerViewId, Fragment fragment, @Nullable String tag, int opcmd) {.// encapsulate the Op
addOp(new Op(opcmd, fragment));
}
/** * The mOps array holds these actions * to animate the fragment entry and exit, * and can be seen in the setCustomAnimations method */
void addOp(Op op) {
mOps.add(op);
op.mEnterAnim = mEnterAnim;
op.mExitAnim = mExitAnim;
op.mPopEnterAnim = mPopEnterAnim;
op.mPopExitAnim = mPopExitAnim;
}
Copy the code
4.commit
4.1 Four submission methods
- There are four types of committed transactions, respectively. The four differences will not be analyzed for this time.
1. commit()
2. commitAllowingStateLoss()
3. commitNow()
4. commitNowAllowingStateLoss()
Copy the code
4.2 Committing a Transaction
- The add, hide, and replace operations can only be committed once.
- Committing a transaction is essentially throwing itself into the FragmentManager for execution.
@Override
public int commit(a) {
return commitInternal(false);
}
int commitInternal(boolean allowStateLoss) {
// Commit only once; otherwise, throw an exception
if (mCommitted) throw new IllegalStateException("commit already called");
if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
Log.v(TAG, "Commit: " + this);
LogWriter logw = new LogWriter(TAG);
PrintWriter pw = new PrintWriter(logw);
dump("", pw);
pw.close();
}
mCommitted = true;
// Whether to join the rollback stack
if (mAddToBackStack) {
// Put the index of the rollback flag
mIndex = mManager.allocBackStackIndex();
} else {
mIndex = -1;
}
// join the queue
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
Copy the code
4.3 handler to send
- Commit is performed asynchronously on the main thread, that is, through a handler.
/*FragmentManager*/
// Save the operation
private final ArrayList<OpGenerator> mPendingActions = new ArrayList<>();
void enqueueAction(@NonNull OpGenerator action, boolean allowStateLoss) {
if(! allowStateLoss) {if (mHost == null) {
if (mDestroyed) {
//FragmentManager has been destroyed
throw new IllegalStateException("FragmentManager has been destroyed");
} else {
//FragmentManager is not bound yet
throw new IllegalStateException("FragmentManager has not been attached to a "
+ "host.");
}
}
checkStateLoss();
}
/ / lock
synchronized (mPendingActions) {
if (mHost == null) {
/ / commitAllowingStateLoss will go here
if (allowStateLoss) {
// This FragmentManager isn't attached, so drop the entire transaction.
return;
}
throw new IllegalStateException("Activity has been destroyed");
}
// Add to array
mPendingActions.add(action);
// Finally get herescheduleCommit(); }}Copy the code
- So here we finally see the handler, and we send it out with post.
void scheduleCommit(a) {
synchronized (mPendingActions) {
booleanpostponeReady = mPostponedTransactions ! =null && !mPostponedTransactions.isEmpty();
boolean pendingReady = mPendingActions.size() == 1;
if (postponeReady || pendingReady) {
/ / handlermHost.getHandler().removeCallbacks(mExecCommit); mHost.getHandler().post(mExecCommit); updateOnBackPressedCallbackEnabled(); }}}Copy the code
4.4 perform execPendingActions
private Runnable mExecCommit = new Runnable() {
@Override
public void run(a) {
execPendingActions(true); }};/** * Only call from main thread! * /
boolean execPendingActions(boolean allowStateLoss) {
ensureExecReady(allowStateLoss);
boolean didSomething = false;
// Put the transaction into temporary variables
while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
mExecutingActions = true;
try {
// Optimize the collation transaction
removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
} finally {
cleanupExec();
}
didSomething = true; }...return didSomething;
}
Copy the code
4.5 Sorting Out Transactions
- RemoveRedundantOperationsAndExecute has a large section of the notes, and comments the meaning of this method to delete redundant operation, merger, repetitive operation is optimized
- Focus on the Record.expandOps method.
- Take out the operations recorded in the Op, process different operations, add or remove, replace is removed first and then add, you can see the source code, here will not paste all, a little too much.
/*FragmentManager*/
/** * Remove redundant BackStackRecord operations and executes them. This method merges operations * of proximate records that allow reordering. See ... * /
private void removeRedundantOperationsAndExecute(@NonNull ArrayList<BackStackRecord> records,
@NonNull ArrayList<Boolean> isRecordPop) {... executeOpsTogether(records, isRecordPop, startIndex, recordNum); .private void executeOpsTogether(@NonNull ArrayList<BackStackRecord> records,
@NonNull ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
// unpackoldPrimaryNav = record.expandOps(mTmpAddedFragments, oldPrimaryNav); . }// unpack
Fragment expandOps(ArrayList<Fragment> added, Fragment oldPrimaryNav) {
for (int opNum = 0; opNum < mOps.size(); opNum++) {
final Op op = mOps.get(opNum);
switch (op.mCmd) {
/ / add
case OP_ADD:
case OP_ATTACH:
added.add(op.mFragment);
break;
/ / remove
case OP_REMOVE:
case OP_DETACH: {
added.remove(op.mFragment);
if (op.mFragment == oldPrimaryNav) {
mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, op.mFragment));
opNum++;
oldPrimaryNav = null; }}break;
/ / replace
case OP_REPLACE: {
...
}
break;
case OP_SET_PRIMARY_NAV: {
...
}
break; }}return oldPrimaryNav;
}
Copy the code
4.6 Executing a Transaction
- Here FragmentManager executeOps method, and then enter the BackStackRecord. ExecuteOps method, according to the Op information, processing FragmentManager again. MoveToState method.
/*FragmentManager*/
private void executeOpsTogether(@NonNull ArrayList<BackStackRecord> records,
@NonNull ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {... executeOps(records, isRecordPop, startIndex, endIndex); . }private static void executeOps(@NonNull ArrayList<BackStackRecord> records,
@NonNull ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {.. record.executeOps();/*BackStackRecord*/
void executeOps(a) {
final int numOps = mOps.size();
for (int opNum = 0; opNum < numOps; opNum++) {
final Op op = mOps.get(opNum);
finalFragment f = op.mFragment; .switch (op.mCmd) {
case OP_ADD:
f.setAnimations(op.mEnterAnim, op.mExitAnim, op.mPopEnterAnim, op.mPopExitAnim);
mManager.setExitAnimationOrder(f, false);
mManager.addFragment(f);
break; ..}}if(! mReorderingAllowed && ! FragmentManager.USE_STATE_MANAGER) {// Added fragments are added at the end to comply with prior behavior.
mManager.moveToState(mManager.mCurState, true); }}Copy the code
4.7 Status Change
- There are only 5 state constants in the Fragment version, for example 1.2.0. There are only 5 state constants in the Fragment version, for example 1.2.0.
public class Fragment implements.{
static final Object USE_DEFAULT_TRANSITION = new Object();
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 VIEW_CREATED = 2; // View Created.
static final int AWAITING_EXIT_EFFECTS = 3; // Downward state, awaiting exit effects
static final int ACTIVITY_CREATED = 4; // Fully created, not started.
static final int STARTED = 5; // Created and started, not resumed.
static final int AWAITING_ENTER_EFFECTS = 6; // Upward state, awaiting enter effects
static final int RESUMED = 7; // Created started and resumed.
int mState = INITIALIZING;
}
Copy the code
- The moveToState code is the key.
- NewState because = Math. Min (newState because, fragmentStateManager.com puteExpectedState ()), associated with the official offer lazy loading, here can restrict the fragments to which stage, but now no matter him first.
- If (f.mstate <= newState), if true, you can see that the following code is the creation process, such as fragment. INITIALIZING, ATTACHED, CREATED, etc., otherwise it is the destruction process, put some code here. You can go to the source code to see all the processes.
void moveToState(int newState, boolean always) {... moveFragmentToExpectedState(f); . }void moveFragmentToExpectedState(@NonNull Fragment f) {... moveToState(f); . }void moveToState(@NonNull Fragment f) {
moveToState(f, mCurState);
}
void moveToState(@NonNull Fragment f, int newState) {
/ / get FragmentStateManager
FragmentStateManager fragmentStateManager = mFragmentStore.getFragmentStateManager(f.mWho);
if (fragmentStateManager == null) {
fragmentStateManager = new FragmentStateManager(mLifecycleCallbacksDispatcher,
mFragmentStore, f);
fragmentStateManager.setFragmentManagerState(Fragment.CREATED);
}
if (f.mFromLayout && f.mInLayout && f.mState == Fragment.VIEW_CREATED) {
newState = Math.max(newState, Fragment.VIEW_CREATED);
}
// This is related to the lazy loading provided by the authorities, which can limit the phase of the fragment execution
newState = Math.min(newState, fragmentStateManager.computeExpectedState());
if (f.mState <= newState) {
// If we are moving to the same state, we do not need to give up on the animation.
if(f.mState < newState && ! mExitAnimationCancellationSignals.isEmpty()) {// The fragment is currently being animated... but! Now we
// want to move our state back up. Give up on waiting for the
// animation and proceed from where we are.
cancelExitAnimation(f);
}
switch (f.mState) {
case Fragment.INITIALIZING:
if (newState > Fragment.INITIALIZING) {
fragmentStateManager.attach();
}
// fall through
case Fragment.ATTACHED:
if (newState > Fragment.ATTACHED) {
fragmentStateManager.create();
}
// fall through
case Fragment.CREATED:
// We want to unconditionally run this anytime we do a moveToState that
// moves the Fragment above INITIALIZING, including cases such as when
// we move from CREATED => CREATED as part of the case fall through above.
if (newState > Fragment.INITIALIZING) {
fragmentStateManager.ensureInflatedView();
}
if (newState > Fragment.CREATED) {
fragmentStateManager.createView();
}
// fall through. }}else if (f.mState > newState) {
switch (f.mState) {
case Fragment.RESUMED:
if (newState < Fragment.RESUMED) {
fragmentStateManager.pause();
}
// fall through
case Fragment.STARTED:
if (newState < Fragment.STARTED) {
fragmentStateManager.stop();
}
// fall through. }}if(f.mState ! = newState) {if (isLoggingEnabled(Log.DEBUG)) {
Log.d(TAG, "moveToState: Fragment state for " + f + " not updated inline; "
+ "expected state " + newState + " found "+ f.mState); } f.mState = newState; }}Copy the code
4.8 is finally executed in the Fragment lifecycle
- Here’s a FragmentStateManager that actually calls the Fragment’s life-cycle method, and we finally see the familiar shadow of the Fragment’s onCreateView method.
- So if you look at onCreateView, the most common method we use, and all the others, you can try to analyze it yourself.
case Fragment.CREATED:
if (newState > Fragment.CREATED) {
fragmentStateManager.createView();
}
/*FragmentStateManager*/
void createView(a) {... mFragment.performCreateView(layoutInflater, container, mFragment.mSavedFragmentState); . }/*Fragment*/
void performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
mChildFragmentManager.noteStateNotSaved();
mPerformedCreateView = true;
mViewLifecycleOwner = new FragmentViewLifecycleOwner(getViewModelStore());
mView = onCreateView(inflater, container, savedInstanceState);
if(mView ! =null) {
// Initialize the view lifecycle
mViewLifecycleOwner.initialize();
// Tell the fragment's new view about it before we tell anyone listening
// to mViewLifecycleOwnerLiveData and before onViewCreated, so that calls to
// ViewTree get() methods return something meaningful
ViewTreeLifecycleOwner.set(mView, mViewLifecycleOwner);
ViewTreeViewModelStoreOwner.set(mView, mViewLifecycleOwner);
ViewTreeSavedStateRegistryOwner.set(mView, mViewLifecycleOwner);
// Then inform any Observers of the new LifecycleOwner
mViewLifecycleOwnerLiveData.setValue(mViewLifecycleOwner);
} else {
if (mViewLifecycleOwner.isInitialized()) {
throw new IllegalStateException("Called getViewLifecycleOwner() but "
+ "onCreateView() returned null");
}
mViewLifecycleOwner = null; }}Copy the code
The flow chart of 5.
- It might look better with a flow chart.
- If there is a mistake to help point out, thank you.