preface
OK, here comes the pit filling article.
You probably have the wrong ViewPager.
Wrong ViewPager usage (continued), causing memory leaks? Memory overflow?
When I’m ready to begin to understand FragmentStatePagerAdapter open the official document. I feel like… Closed and dormant for decades, ready to reverse the Qing Dynasty and restore the Ming Dynasty; Clearance found that the qing has died…
What ghost, I still don’t know how to use, tm abandoned??
The body of the
This does not prevent us from seeing how it enhances the FragmentPagerAdapter. Help me up! I can still learn!
See FragmentStatePagerAdapter before, let’s you first look at the document
Here’s what the official website says about this class (which I translated in my poor English) :
This version is more efficient when there are lots of fragments. When fragments are not visible to the user, their entire Fragment may be destoryed, leaving only the state of the Fragment. Takes up less memory than the FragmentPagerAdapter.
It is used exactly the same as the FragmentPagerAdapter (FPA), which will not be expanded here. If you’re interested, you can go directly to the demo in the documentation.
From the document introduces, FragmentStatePagerAdapter provide less memory overhead. In the second article, we also learned that the FragmentPagerAdapter can have a large memory consumption problem under the FragmentManager architecture. So let’s take a look at, FragmentStatePagerAdapter is how to optimize the problem.
A, how to achieve less memory overhead?
FragmentStatePagerAdapter (hereinafter referred to as FSPA) implementation is simple, the solution is also very simple and crude. Let’s start with a key method called instantiateItem(), based on which we’ll look at the implementation principle in four steps:
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
/ / step 1
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if(f ! =null) {
returnf; }}// omit the code
/ / step 2
Fragment fragment = getItem(position);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if(fss ! =null) { fragment.setInitialSavedState(fss); }}/ / step 3
while (mFragments.size() <= position) {
mFragments.add(null);
}
// Omit some code
/ / step 4
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
// Omit some code
return fragment;
}
Copy the code
We can see that the instantiateItem() here is very different from FPA: there is no way to find existing fragments via FragmentManager! It can be concluded that FSPA has lost the logic cached on FPA, and we will look at the FSPA source code to understand the difference in logic.
1.1. Step 1 Analysis
Private ArrayList
mFragments = new ArrayList<>() A layer is wrapped on top of FragmentManager.
The handling here is also very simple. If you can find a Fragment in a Mfragment based on position, return it directly. There’s a point here, and we need to notice that this is a direct return. This means that the Fragment instance held by the mFragment is not detach from the FragmentManager and therefore does not need to be restate.
Another thing to watch out for: if (f! = null), which means that it is possible for mFragments to be null, so we can assume that mFragments have a dynamic holding relationship with fragments.
1.2. Step 2 Analysis
The familiar method calls getItem() when the cached Fragment is not found and the implementation initializes the Fragment itself.
Then perform an initSavedState operation on the current Fragment based on mSavedState.
Why does a new Fragment have a SavedState?
This is because mSavedState will hold all instantiated fragments, but mFragments will only hold the current attach Fragment. Therefore, it is possible that the Fragment that was initialized when getItem() was called has been initialized before, so its state should be restored in this case.
1.3. Step 3 Analysis
Step 3 does something more interesting:
while (mFragments.size() <= position) {
mFragments.add(null);
}
Copy the code
In plain English, it’s a placeholder. Seeing this step, we can see that mFragments are a “Map with position as the key and fragment as the value”.
When we get to a position very far back. The code to this we get mFragments List is likely to be like this: [fragment1 fragment2, null, null, null, the next thing to be add fragment6]
1.4. Step 4 Analysis
Step 4 is very simple: Add the Fragment generated by getItem.
After looking at these four steps, we will probably find that the code is not too difficult. Although we have only looked at one method, we can guess the principle of FSPA:
- Only the Fragment on the current attach is cached
- Cache all SaveState fragments attached to restore the state when new
It looks like the memory overhead is lower because there are fewer fragments in the cache… Even though there are fewer fragments cached by mFragments in FSPA, the FragmentStore cache still needs to be cached. FSPA cache even one more Fragment.
Now let’s look at another approach, how FSPA solves these problems.
2. Destroy fragments
The FragmentManager memory explosion is caused by the FragmentStore forcibly referencing all Fragment instances in mActive and not doing any reclamation.
Since FSPA claims to be less expensive, this problem must be addressed head-on. So let’s take a look at FSPA’s strategy for destroying fragments.
2.1, destroyItem ()
The main difference between FSPA and FPA is the implementation of destroyItem(). Let’s compare the two implementations:
// FSPA
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.set(position, null);
// Notice here
mCurTransaction.remove(fragment);
if (fragment.equals(mCurrentPrimaryItem)) {
mCurrentPrimaryItem = null; }}// FPA
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
// Notice here
mCurTransaction.detach(fragment);
if (fragment.equals(mCurrentPrimaryItem)) {
mCurrentPrimaryItem = null; }}Copy the code
FSPA calls the remove method, while FPA calls the detach method. So let’s see what the difference is. Both remove and detach go to executeOps() :
case OP_REMOVE:
f.setNextAnim(op.mExitAnim);
mManager.removeFragment(f);
break;
case OP_DETACH:
f.setNextAnim(op.mExitAnim);
mManager.detachFragment(f);
break;
Copy the code
But here either removeFrament() or detachFragment(). Nature of are mFragmentStore. RemoveFragment (fragments); Remove the Fragment from the mAdded list in the FragmentStore without touching the mActive list.
So for FSPA, it does not control memory overhead in this way. Let’s move on…
2.2. Control the Fragment state machine
After the above switch judgment is complete, it will go to the real driver state:
if (! mReorderingAllowed && op.mCmd ! = OP_ADD && f ! = null) {/ / there will go moveToState () mManager. MoveFragmentToExpectedState (f); } FragmentManager#moveToState() if (f.mState <= newState) { switch (f.mState) { case Fragment.INITIALIZING:{ if (newState > fragment.initializing) {// missing Fragment}} Case fragment.attached :{// missing Fragment} // missing Fragment}else if (f.mstate > newState) { switch (f.mState) { case Fragment.RESUMED: If (newState < fragment.resumed) {// omit some code} case fragment.created: If (newState < fragment.created) {// 提 示 this Boolean is removed = f.removing &&! f.isInBackStack(); if (beingRemoved || mNonConfig.shouldDestroy(f)) { makeInactive(fragmentStateManager); } // omit some code} // omit some code}Copy the code
The logic of the state machine, if you’re interested, you can read it for yourself. The logic for handling states here is pretty slutty. Let’s just focus on makeInactive(). The difference between remove and detach, as we will see above, is this method. Remove goes into this method:
private void makeInactive(@NonNull FragmentStateManager fragmentStateManager) {
// Omit some code
mFragmentStore.makeInactive(fragmentStateManager);
removeRetainedFragment(f);
}
void makeInactive(@NonNull FragmentStateManager newlyInactive) {
Fragment f = newlyInactive.getFragment();
for (FragmentStateManager fragmentStateManager : mActive.values()) {
if(fragmentStateManager ! =null) {
Fragment fragment = fragmentStateManager.getFragment();
if (f.mWho.equals(fragment.mTargetWho)) {
fragment.mTarget = f;
fragment.mTargetWho = null;
}
}
}
mActive.put(f.mWho, null);
if(f.mTargetWho ! =null) { f.mTarget = findActiveFragment(f.mTargetWho); }}Copy the code
You can see that mActive is reclaimed in the makeInactive() method. Thus, FSPA is optimized over FPA by removing “unnecessary” references to mActive.
I guess this should get you to the optimization point of FSPA, but… Now that we’ve removed mActive from FragmentManager, what about our cache?
Third, the cache is lost
Indeed, as we saw in the instantiateItem() implementation at the beginning, FSPA removes the logic to find the cache through FragmentManager.
We can see from the previous article that the FPA cache is based on the FragmentManager mActive cache, and that the FPA memory overflow is also due to the FragmentManager mActive cache.
So it’s easy to understand how FSPA optimizes, removing the mActive cache in FragmentManager.
There are some differences between FSPA and FPA:
- 1. FSPA will go getItem() to new Fragment whenever it is not in mAdd Fragment.
- 2. We can’t easily get the Fragment instance we want using FragmentManager. (FSPA adds fragments to mAdd based on ID)
3.1. Is it reasonable to fetch a specific Fragment instance in ViewPager
Let’s talk a little bit more here. I don’t know if you have noticed that neither FPA nor FSPA has Google actively provided a public method to obtain internal held fragments. Even in FSPA, any possible lines for such operations are removed.
From the perspective of this phenomenon alone, it is “unreasonable” to fetch internal fragments using ViewPager in disguise. But we also know that there is such a thing as need, if it is “reasonable” it is not called need… So this kind of operation is unavoidable. So, we need to know from the difference between FSPA and FPA who we should use…
- FPA is a good choice if we need FragmentManager to cache our fragments.
- If we have a lot of fragments in the ViewPager, then FSPA is a good choice.
Of course, since FSPA has been deprecated, the first choice for our project is ViewPager2. An analysis of ViewPager2 will come later…
The end of the
With today’s article, there have been three articles on the use of fragments in ViewPager.
Learn as much as you can and publish the right article as you can. Welcome to discuss in the comments section