ViewPager + Fragment 与 IllegalStateException

281 阅读2分钟

总结

  1. 无论何种场景,都不能直接引用 Fragment 的实例,更不能使用该实例去进行任何操作

  2. 所有 Fragment 到 Fragment 的通信都是通过共享的 ViewModel 或关联的 Activity 来完成的。两个 Fragment 绝不能直接通信参考官网说法

  3. 如果需要双向通信,使用中介者模式,各 Frg 向 ViewModel 注册自己,detach 时从 Frg 中取消注册。这样各 Frg 相当于间接持有其余 Frg,而且不会影响其余 Frg 的生命周期

  4. Fragment 中经常会出现一堆奇怪的问题(比如出现重叠现象),首先考虑是否由重建导致的问题

    • 对于重叠现象,最简单的处理方式是如果是 Activity 恢复,就不能再添加 Frg
  5. 自己手动添加 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。