总结
-
无论何种场景,
都不能直接引用 Fragment 的实例,更不能使用该实例去进行任何操作 -
所有 Fragment 到 Fragment 的通信都是通过
共享的 ViewModel 或关联的 Activity 来完成的。两个 Fragment 绝不能直接通信。参考官网说法 -
如果需要
双向通信,使用中介者模式,各 Frg 向 ViewModel 注册自己,detach 时从 Frg 中取消注册。这样各 Frg 相当于间接持有其余 Frg,而且不会影响其余 Frg 的生命周期 -
Fragment 中经常会出现一堆奇怪的问题(比如出现
重叠现象),首先考虑是否由重建导致的问题- 对于重叠现象,最简单的处理方式是如果是 Activity 恢复,就不能再添加 Frg
-
自己手动添加 Frg 时
必须指定 tag,并通过findFragmentByTag() 复用系统重建的 Frg 实例。
现象
无论是 ViewPager 还是 ViewPager2 在 Activity 回收重建时都会恢复旧有的 Frg。因此,为 VP/VP2 设置新的 Adapter 并不能让新 Frg 被添加到 Activity 中,只不过是恢复的 Fragment 重新再执行一次生命周期。这里的新 Frg 指的是自己在代码中创建的 Frg。
因此,如果自己记录 Frg 实例并通过该实例使用 context 就会出现 IllegalStateException。因为记录的实例是最新的,但该实例并没有被 attach。
以下是相关代码:
// FragmentStateAdapter.java 用于 VP2
private void ensureFragment(int position) {
long itemId = getItemId(position);
// mFragments 在恢复时会恢复旧有数据
// 逻辑在 restoreState() 中
if (!mFragments.containsKey(itemId)) {
// TODO(133419201): check if a Fragment provided here is a new Fragment
Fragment newFragment = createFragment(position);
newFragment.setInitialSavedState(mSavedStates.get(itemId));
mFragments.put(itemId, newFragment);
}
}
// FragmentPagerAdapter#instantiateItem() 节选,用于 VP
String name = makeFragmentName(container.getId(), itemId);
// 可以看出它也是先寻找再新建
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
// 通过 makeFragmentName() 生成 Frg 对应的 Tag
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
通信
记录 Fragment 的实例主要想用于通信,因此需要解决 Fragment 之间通信问题。官方推荐使用 ViewModel,让 ViewModel 充当各 Frg 之间的中介者。
ViewModel+LiveData 只适合于单向通信,即 A 通知 B 要搞事,而不关注结果。如果需要结果,B 需要另外通过 LiveData 传递给 A,这样会将一次完整的方法调用拆成两个部分,同步变异步,恶心。
如果是父子 Frg,父 Frg 可以通过 findFragmentByTag 获取子 Frg 的实例,只不过使用 VP/VP2 时,tag 非自己指定的,需要结果 VP/VP2 源码处理,侵入性太大。
可以使用 ViewModel,ViewModel 中存在各个变量指向需要交互的各个 Frg。由各 Frg 在合适时机将自己赋值给相应的变量,并且在额外时机将相应的变量设置为 null,防止出现内存泄露。
使用者通过 ViewModel 拿到别的 Frg 实例,可以正常调用方法,只不过需要判断是否为 null。