FragmentPagerAdapter
PerformnotifyDataSetChanged
root cause refresh is invalid, the maintenance ofFragmentManager
caches added a Fragment cause, the solution naturally from theFragmentManager
start.
1. Problem scenario
Use ViewPager+Fragment to achieve the page-turning effect of the homepage. If you are not logged in, fill in the Frament that contains the jump login:FragmentLoginNeeded
Log is as follows:
Note: MainFragmemtMine has its own handling of not logged in
However, after logging in and performing an notifyDataSetChanged
operation, the page is not refreshed. The refresh code is as follows:
boolean login = isLogin();
if (login) {
FragmentMain fragmentMain = //...
fragments.add(fragmentMain);
MainFragmentPerson fragmentPerson = //。。。
fragments.add(fragmentPerson);
fragments.add(FragmentRecord.getInstance());
fragments.add(MainFragmentMessage.getInstance());
} else {
fragments.add(FragmentLoginNeeded.getInstance());
fragments.add(FragmentLoginNeeded.getInstance());
fragments.add(FragmentLoginNeeded.getInstance());
fragments.add(FragmentLoginNeeded.getInstance());
}
fragments.add(MainFragmentMine.getInstance("我的"));
mFragments.clear();
mFragments.addAll(fragments);
mFragmentAdapter.notifyDataSetChanged();
At this time, the log is as follows:
It can be seen from the above that although the destory
operation is performed, instantiateItem
the target of initialization is still the original object, so it is clear that the Adapter data source of ViewPager has changed, but the interface data has not changed. This is the existing problem.
Second, the source code view
The same is inheritance Adapter
. In our own implementation of Adapter, after the data source changes, the Adapter.notifyDataSetChanged()
data will be refreshed to the latest when executed ; but the FramentPagerAdapter
refresh is not executed, so we need to FramentPagerAdapter
check from the source code.
In the source code of FragmentPagerAdapter, no exception was found in other methods, but instantiateItem
the handling of Fragment in the code is somewhat interesting as follows:
Fragment processing is achieved through FragmentManager.findFragmentByTag()
(FragmentManager will cache the Fragment added to its transaction, and will be retrieved through TAG next time) The composition of the Tag is as follows:
Here, it can be seen that the Tag is determined by container.getId()
and getItemId()
, where container.getId()
The value of is the Id of the View in the Adapter, and getItemId()
the value of position is returned by default:
public long getItemId(int position) {
return position;
}
The combination of container ID and position controls whether to initialize a new Fragment, and does not give the Fragment any chance to change... Therefore, the way to solve the problem is to judge the Fragment each time the Item is initialized, but because the source code is correct Fragment judgment and operation involve cache optimization, so the next best thing is to clear the Fragment from the FragmentManager when destroying the item, and the problem is solved (only limited to the situation where the number is small and the initialization is completed at one time).
Three, problem solving
After the above analysis, the solution is ready at night. Rewrite the destoryItem()
method: as follows.
Finally, imitate the processing method in the FragmentPagerAdapter source code, and submit the transaction to OK.
@Override
public void finishUpdate(@NonNull ViewGroup container) {
super.finishUpdate(container);
if (mTransaction != null) {
mTransaction.commitNowAllowingStateLoss();
mTransaction = null;
}
}
Note: The above processing method is limited to mVpFragmentMain.setOffscreenPageLimit(4);
the situation where the entire list will be initialized at the first initialization. In the case of Fragment+ViewPager, it is basically used for the home page, so it can solve most scenarios