background
This article is based on the Fragment on androidx
What if an interviewer asks you, How does Fragment manage transactions? Fragment loading, life cycle, return stack, state saving, etc. If you want to understand the transaction management of a Fragment, you need to understand the source code of the Fragment. The previous questions are child’s play.
There are two types of interview questions. One is to ask typical questions. For example, the interviewer asks you a HashMap, which covers many aspects and can be expanded greatly. There is another is to directly ask a certain depth of questions, if you can answer, explain the grasp of a certain degree, simple questions can be ignored, I can not answer the front simple questions.
To understand a Fragment, you first need to understand its lifecycle, and its association with the Activity lifecycle is also important.
Fragment life cycle
Fragment and Activity lifecycle association
Fragment is attached to the Activity, so the Fragment lifecycle are linked to the life cycle of the Activity, in the life cycle of each Activity will eventually call FragmentManagerImpl. DispatchXXX notice (), Then call to FragmentManagerImpl. DispatchStateChange (int nextState), fragments have multiple status value to show what cycle’s status
static final int INITIALIZING = 0; // Not yet created. 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. // The Fragment defaults to INITIALIZING, which is the current life-cycle state int mState = INITIALIZING;Copy the code
Note that this article uses the androidx version of the fragment, not the normal support package fragment(which has six states). It ends up calling the moveToState method, then changing those states and calling the fragment’s life-cycle methods.
How does a Fragment manage transactions
Start by looking at the actions for FragmentTransaction in the Fragment
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;
Copy the code
All transaction operations are encapsulated into an Op class for unified management
static final class Op {
int mCmd;
Fragment mFragment;
int mEnterAnim;
int mExitAnim;
int mPopEnterAnim;
int mPopExitAnim;
Lifecycle.State mOldMaxState;
Lifecycle.State mCurrentMaxState;
Op() {
}
Op(int cmd, Fragment fragment) {
this.mCmd = cmd;
this.mFragment = fragment;
this.mOldMaxState = Lifecycle.State.RESUMED;
this.mCurrentMaxState = Lifecycle.State.RESUMED;
}
Op(int cmd, @NonNull Fragment fragment, Lifecycle.State state) {
this.mCmd = cmd;
this.mFragment = fragment;
this.mOldMaxState = fragment.mMaxState;
this.mCurrentMaxState = state; }}Copy the code
Let’s take a look at the process of adding transactions
First is through the add/replace FragmentTransaction/show/hide/remove/attach operation, add these operations (Op) to the ArrayList < Op > mOps = new ArrayList < > (), The FragmentTransaction class is abstract and the real implementation class is BackStackRecord. The commit class determines whether the user is added to the rollback stack and passes the action to the FragmentManagerImpl. The group that does the transaction joins the queue, starts processing the set of transactions, and eventually moves to the moveToState method, which executes the Fragment’s life-cycle methods.
ArrayList<BackStackRecord> mBackStackIndices;
ArrayList<Integer> mAvailBackStackIndices; // Available index for rollback
Copy the code
The two arrays are managed only when they are added to the rollback stack. The addition of a rollback stack requires a manual call to the concrete method.
One is used to manage all transactions in the rollback stack, and one is used to record indexes. As shown in the figure above, for example, there are 5 backstackrecords in the mBackStackIndices array. When you remove 1,3 and 2 (null at the same time), The index is then added to the mAvailBackStackIndices array (at the first and second locations), which is equivalent to recording the two locations and adding them to the return stack next time. MAvailBackStackIndices can be used to determine which positions are empty in mBackStackIndices. Add a new BackStackRecord into the empty positions to improve the reuse efficiency of arrays. The previous version used linked list to manage. But the efficiency is not as high as this way.
Transactions, a group of operations all or none, are tied together to ensure that continuous operations are atomic.
The differences between the four Commits for FragmentTransaction
commit() commitAllowingStateLoss() commitNow() commitNowAllowingStateLoss()
The COMMIT is not performed immediately; it is sent to the main thread’s task queue and executed when the main thread is ready to execute it.
//BackStackRecord.java
//commit
@Override
public int commit(a) {
return commitInternal(false);
}
//commitInternal
int commitInternal(boolean allowStateLoss) {... mCommitted =true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
Copy the code
Then there is the enqueueAction method in FragmentImpl
public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
if(! allowStateLoss) { checkStateLoss(); }synchronized (this) {
if (mDestroyed || mHost == null) {
if (allowStateLoss) {
// This FragmentManager isn't attached, so drop the entire transaction.
return;
}
throw new IllegalStateException("Activity has been destroyed");
}
if (mPendingActions == null) {
mPendingActions = newArrayList<>(); } mPendingActions.add(action); scheduleCommit(); }}Copy the code
ScheduleCommit method
void scheduleCommit(a) {
synchronized (this) {
booleanpostponeReady = mPostponedTransactions ! =null && !mPostponedTransactions.isEmpty();
booleanpendingReady = mPendingActions ! =null && mPendingActions.size() == 1;
// Send it through handler
if(postponeReady || pendingReady) { mHost.getHandler().removeCallbacks(mExecCommit); mHost.getHandler().post(mExecCommit); updateOnBackPressedCallbackEnabled(); }}}Copy the code
PopBackStack () does the same, sending tasks to the main thread queue, which means they are all asynchronous.
CommitNow is synchronous and guaranteed to execute immediately, but it is not added to the rollback stack because it may disrupt the order of the contents in the rollback stack. See this article for more details on the other two methods.
@Override
public void commitNow(a) {
disallowAddToBackStack();
mManager.execSingleAction(this.false);
}
public void execSingleAction(OpGenerator action, boolean allowStateLoss) {
···
ensureExecReady(allowStateLoss);
if (action.generateOps(mTmpRecords, mTmpIsPop)) {
mExecutingActions = true;
try {
removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
} finally {
cleanupExec();
}
}
updateOnBackPressedCallbackEnabled();
doPendingDeferredStart();
burpActive();
}
Copy the code
Then there is the ensureExecReady method
private void ensureExecReady(boolean allowStateLoss) {... mExecutingActions =true;
try {
executePostponedTransaction(null.null);
} finally {
mExecutingActions = false; }}Copy the code
Final execution to
public void completeTransaction(a) {
final boolean canceled;
canceled = mNumPostponed > 0;
FragmentManagerImpl manager = mRecord.mManager;
final int numAdded = manager.mAdded.size();
for (int i = 0; i < numAdded; i++) {
final Fragment fragment = manager.mAdded.get(i);
fragment.setOnStartEnterTransitionListener(null);
if(canceled && fragment.isPostponed()) { fragment.startPostponedEnterTransition(); } } mRecord.mManager.completeExecute(mRecord, mIsBack, ! canceled,true);
}
Copy the code
Save and restore the Fragment state
State of preservation
When the Activity is the process of recycling or App in the background in the Sleep state while waiting for special circumstances invoked ActivityThread. CallActivityOnSaveInstanceState = > Instrumentation.callActivityOnSaveInstanceState => Activity.performSaveInstanceState => Activity.onSaveInstanceState => FragmentActivity onSaveInstanceState to save the data, will ultimately save data to ActivityClientRecord member variables in the state.
back
When the Activity recovery to create a new Activity, execute FragmentActivity. OnCreate, Then execute FragmentController. Restore = > FragmentManagerImpl. RestoreSaveState recover data, Then FragmentController. DispatchonCreate = > FragmentManagerImpl. DispatchCreate ` retrace fragments of life cycle.
Preloading and lazy loading of fragments
Fragments preload
In mainstream apps, there are generally four buttons at the bottom of the home page. Click different interfaces at the bottom to display different interfaces (fragments). In this case, ViewPager+ multiple fragments are usually used. If you want to preload multiple fragments, you usually use the following method to preload fragments
viewPager.setOffscreenPageLimit(int count);
Copy the code
Preload viewpager. SetOffscreenPageLimit (), the value of this setting has two meanings: one is the viewpager preloading pages; Second, ViewPager will cache 2n+1 pages (n is the set value), but if you don’t want to preload data, how about 0? If you pass in a value less than 1, the ViewPager will set the number of preloads to the default value, which is 1, so even if you pass in a value of 0, the ViewPager will still preload an interface.
Fragments lazy loading
The so-called lazy loading is to determine whether the fragment is visible based on its life cycle and whether to load data according to the visible state.
Plain Fragment(not on AndroidX)
Use setUserVisibleHint or onHiddenChanged to assist in judgment. Instead of calling the lifecycle method, onHiddenChanged will be called for show and hide. See this article for details
Androidx lazy loading
Androidx has deprecated the setUserVisibleHint method, which is recommended to use the setMaxLifecycle method of FragmentTransaction, and androidX has massively refactored the lifecycle of fragments. It’s quite different from the previous version, so let’s look at the picture below
The diagram shows that switching between Fragment states will execute the Lifecycle and the corresponding Fragment State.State. The setMaxLifecycle method requires that the State passed in be at least CREATED
/** * Set a ceiling for the state of an active fragment in this FragmentManager. If fragment is * already above the received state, it will be forced down to the correct state. * * <p>The fragment provided must currently be added to the FragmentManager to have it's * Lifecycle state capped, or previously added as part of this transaction. The * {@link Lifecycle.State} passed in must at least be {@link Lifecycle.State#CREATED}, otherwise
* an {@link IllegalArgumentException} will be thrown.</p>
*
* @param fragment the fragment to have it's state capped.
* @param state the ceiling state for the fragment.
* @return the same FragmentTransaction instance
*/
@NonNull
public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
@NonNull Lifecycle.State state) {
addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
return this;
}
Copy the code
Looking at the lazy-loaded scenario, we can see that the FragmentPagerAdapter constructor is obsolete
public TabFragmentPagerAdapter(FragmentManager fm, List<Fragment> list) {
super(fm); // This method is obsolete
this.mlist = list;
}
Copy the code
You can see that two more arguments are called internally to the method, so let’s take a look at this new constructor
public FragmentPagerAdapter(@NonNull FragmentManager fm,
@Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
Copy the code
There is an extra argument of type int
@Deprecated
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
Copy the code
The default BEHAVIOR_SET_USER_VISIBLE_HINT constructor is one parameter. If you use a two-parameter constructor, the BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT is passed in, The setMaxLifecycle() method is called to set the state of the last Fragment to STARTED and the state of the Fragment to be displayed to RESUMED; If the BEHAVIOR_SET_USER_VISIBLE_HINT is set to a BEHAVIOR_SET_USER_VISIBLE_HINT, the setUserVisibleHint() method is called.
Calling the two-argument constructor will call the onResume method whenever the Fragment becomes visible, which we can use to implement lazy loading.
- Putting the logic for the Fragment to load data into the onResume() method ensures that the Fragment is not loaded until it is visible.
- Declare a variable to mark whether the onResume() method is executed for the first time since the onResume() method is executed every time the Fragment changes from invisible to visible, and you need to prevent data from being reloaded. In addition, if we use FragmentPagerAdapter, the onDestory() and onDetach() methods will not be executed when the Fragment is destroyed. Instead, the onDestroyView() method will be executed. So in the onDestroyView() method we also need to reset this variable, otherwise the data won’t be reloaded when the Fragment is visible again.
Fragments pass data between fragments
Starting with Fragment 1.3.0-Alpha04, every FragmentManager implements FragmentResultOwner. This means that the FragmentManager can act as a central store for Fragment results. This change lets individual fragments communicate with each other by setting the Fragment results and listening for those results without requiring the fragments to reference each other directly. The implementation can be seen here.
Pass results between parent and child fragments. If you want to pass results from child fragments to parent fragments, Parent fragments in the calling setFragmentResultListener getChildFragmentManager should be used when the () () rather than getParentFragmentManager (). You can also transmit the fragment by using ChildFragment => ParentFragment.
Data can also be passed through shared ViewModels, EventBus, LiveDataBus, etc.
The difference between FragmentPagerAdapter, FragmentStatePagerAdapter
In a FragmentPagerAdapter, even if the fragment is not visible, its view may destory(execute onViewDestory, depending on the value set by setOffscreenPageLimit). But its instance will still exist in memory. A large number of fragments occupy large memory.
In the FragmentSatePagerAdapter, instances of the fragment may be destroyed if the fragment is not visible (execute onDestory, depending on the value set by the setOffscreenPageLimit method). Therefore, the memory overhead will be smaller, suitable for multi-fragment situation.
We saw earlier that the setOffscreenPageLimit method is set to a default value of 1. The value of this setting has two meanings: one is that the ViewPager will preload several pages; Second, ViewPager caches 2n+1 pages (n is the set value).
Now that you understand the meaning of the setOffscreenPageLimit method, you can understand the Fragment lifecycle in ViewPager: The value of setOffscreenPageLimit in the FragmentPagerAdapter affects the onViewDestory method. When cached fragments exceed the value set by setOffscreenPageLimit, the onViewDestory method of those fragments calls back. In FragmentStatePagerAdapter, when the cache fragments than setOffscreenPageLimit after setting the value. The onDestory methods of those fragments are called back.
Whether the fragment in a ViewPager executes onViewDestory or onDestory depends on the value set by the setOffscreenPageLimit method.
Some of the new features in androidx
FragmentContainerView, FragmentFactory, see this article, and Google suggests new features for using these fragments
Refer to the article
Blog.csdn.net/u011151791/…
Juejin. Cn/post / 684490…
Blog.csdn.net/tongsiw/art…
Yuweiguocn. Making. IO/androidx – fr…