ViewPager2+Fragment 在不保留活动踩坑
一. 目标
利用viewpager和 fragment实现滑动切换不同页面的效果
二.实现
1.ViewPager + FragmentPagerAdapter
xml:
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
activity:
r.viewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
@NonNull
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
@Override
public int getCount() {
return fragmentList == null ? 0 : fragmentList.size();
}
});
r.viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
2. ViewPager2 + FragmentStateAdapter
xml:
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
activity:
r.viewPager.setAdapter(new FragmentStateAdapter(getSupportFragmentManager(), getLifecycle()) {
@Override
public int getItemCount() {
return fragmentList == null ? 0 : fragmentList.size();
}
@NonNull
@Override
public Fragment createFragment(int position) {
return fragmentList.get(position);
}
});
r.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
}
});
三.区别
FragmentStateAdapter 和 FragmentPagerAdapterd区别
1. FragmentPagerAdapter区别会缓存fragment,非前台fragment依旧存在内存中。而FragmentStateAdapter则是非前台fragment会被销毁,在滑动变成前台时会重新创建,比较节约内存。举个例子,有个fragment在播放视频,在onPause()里面,我会释放播放器资源,但是在用FragmentPagerAdapter时,切换到其他fragment时,并没有调用onPause()导致资源没有释放.
2. 在不保留活动或者其他情况导致activity重建时,FragmentPagerAdapter没有实现restoreState,但实际上重新构建好的activity里面初始化viewpager时,FragmentPagerAdapter的getItem()方法并不会调用,原因是再加载时候它会从FragmentManager里面先读取,而FragmentManager里面是会重新构建Fragment的。
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
3. 在不保留活动或者其他情况导致activity重建时,FragmentStateAdapter有重写restoreState()方法,然后从savedState里面读取数据,包括Fragment的SavedState也会重建。
for (String key : bundle.keySet()) {
if (isValidKey(key, KEY_PREFIX_FRAGMENT)) {
long itemId = parseIdFromKey(key, KEY_PREFIX_FRAGMENT);
Fragment fragment = mFragmentManager.getFragment(bundle, key);
mFragments.put(itemId, fragment);
continue;
}
if (isValidKey(key, KEY_PREFIX_STATE)) {
long itemId = parseIdFromKey(key, KEY_PREFIX_STATE);
Fragment.SavedState state = bundle.getParcelable(key);
if (containsItem(itemId)) {
mSavedStates.put(itemId, state);
}
continue;
}
throw new IllegalArgumentException("Unexpected key in savedState: " + key);
}
四.问题
由于在不保留活动时,Fragment是重新构建的,并不是由初始化时从FragmentList里面读取的,所以在activity和fragment通信时,如果用了fragmentList里面的对象去通信,会发现对应fragment并没有被加载activity里面。
五。解决办法
先尝试暴力重写onSaveInstanceState()方法,使其在重新构建activity时不重构fragment。看起来似乎解决了问题,现在每次都会调用我们初始化方法,使用fragmentList里面的fragment对象,我们又能通过fragmentList的直接与fragment通信了。但是后面发现,如果在onActivityResult()里面需要跟fragment通信,又出现问题了。因为onActivityResult()方法执行是在onResume()之前的,此时你的fragment还没有加载到activity里面,所以还是不行.
转换思路,既然有缓存fragment时,都是从FragmentManager里面加载fragment的,那么在通信的时候我们也可以直接从FragmentManager里面找我们要的fragment即可,通过findFragmentByTag()方法即可。然后在Fragment的onSaveInstanceState()方法里面主动将我们需要恢复的数据存在Bundle里面。
至此,解决了Fragment的重建导致的缓存问题.